{-| Module : Database.Mbtiles Description : Haskell MBTiles client. Copyright : (c) Joe Canero, 2017 License : BSD3 Maintainer : jmc41493@gmail.com Stability : experimental Portability : POSIX This module provides support for reading, writing, and updating an mbtiles database. There is also functionality for reading metadata from the database. See the associated README.md for basic usage examples. -} {-# LANGUAGE OverloadedStrings #-} module Database.Mbtiles ( -- * Types MbtilesT , Mbtiles , MbtilesMeta , MBTilesError(..) , Z(..) , X(..) , Y(..) -- * Typeclasses , ToTile(..) , FromTile(..) -- * The MbtilesT monad transformer , runMbtilesT , runMbtiles -- * Mbtiles read/write functionality , getTile , writeTile , writeTiles , updateTile , updateTiles -- * Mbtiles metadata functionality , getMetadata , getName , getType , getVersion , getDescription , getFormat ) where import Control.Monad import Control.Monad.IO.Class import Control.Monad.Reader import qualified Data.ByteString.Lazy as BL import Data.HashMap.Strict ((!)) import qualified Data.HashMap.Strict as M hiding ((!)) import Data.Monoid import Data.Text (Text) import Database.Mbtiles.Query import Database.Mbtiles.Types import Database.Mbtiles.Utility import Database.SQLite.Simple import System.Directory -- | Given a path to an MBTiles file, run the 'MbtilesT' action. -- This will open a connection to the MBTiles file, run the action, -- and then close the connection. -- Some validation will be performed first. Of course, we will check if the -- MBTiles file actually exists. If it does, we need to validate its schema according -- to the MBTiles spec. runMbtilesT :: (MonadIO m) => FilePath -> MbtilesT m a -> m (Either MBTilesError a) runMbtilesT mbtilesPath mbt = do m <- validateMBTiles mbtilesPath either (return . Left) processMbt m where processMbt (c, d) = do m <- mkMbtilesData c d v <- runReaderT (unMbtilesT mbt) m closeAll m return $ Right v mkMbtilesData c d = MbtilesData <$> openStmt c getTileQuery <*> pure c <*> pure d closeAll MbtilesData{r = rs, conn = c} = closeStmt rs >> closeConn c type ValidationResult = (Connection, MbtilesMeta) validateMBTiles :: (MonadIO m) => FilePath -> m (Either MBTilesError ValidationResult) validateMBTiles mbtilesPath = liftIO $ doesFileExist mbtilesPath >>= ifExistsOpen >>= validator schema >>= validator metadata >>= validator tiles >>= validator metadataValues where ifExistsOpen False = return $ Left DoesNotExist ifExistsOpen True = Right <$> open mbtilesPath schema c = do valid <- mconcat $ map (fmap All) [doesTableExist c tilesTable, doesTableExist c metadataTable] if getAll valid then return $ Right c else return $ Left InvalidSchema metadata = columnChecker metadataTable metadataColumns InvalidMetadata tiles = columnChecker tilesTable tilesColumns InvalidTiles metadataValues c = do m <- getDBMetadata c if all (`M.member` m) requiredMeta then return $ Right (c, m) else return $ Left InvalidMetadata -- | Specialized version of 'runMbtilesT' to run in the IO monad. runMbtiles :: FilePath -> Mbtiles a -> IO (Either MBTilesError a) runMbtiles = runMbtilesT -- | Given a 'Z', 'X', and 'Y' parameters, return the corresponding tile data, -- if it exists. getTile :: (MonadIO m, FromTile a) => Z -> X -> Y -> MbtilesT m (Maybe a) getTile (Z z) (X x) (Y y) = MbtilesT $ do rs <- r <$> ask fmap unwrapTile <$> liftIO (do bindNamed rs [":zoom" := z, ":col" := x, ":row" := y] res <- nextRow rs reset rs return res) where unwrapTile (Only bs) = fromTile bs -- | Returns the 'MbtilesMeta' that was found in the MBTiles file. -- This returns all of the currently available metadata for the MBTiles database. getMetadata :: (MonadIO m) => MbtilesT m MbtilesMeta getMetadata = MbtilesT $ reader meta -- | Helper function for getting the specified name of the MBTiles from metadata. getName :: (MonadIO m) => MbtilesT m Text getName = findMeta "name" <$> getMetadata -- | Helper function for getting the type of the MBTiles from metadata. getType :: (MonadIO m) => MbtilesT m Text getType = findMeta "type" <$> getMetadata -- | Helper function for getting the version of the MBTiles from metadata. getVersion :: (MonadIO m) => MbtilesT m Text getVersion = findMeta "version" <$> getMetadata -- | Helper function for getting the description of the MBTiles from metadata. getDescription :: (MonadIO m) => MbtilesT m Text getDescription = findMeta "description" <$> getMetadata -- | Helper function for getting the format of the MBTiles from metadata. getFormat :: (MonadIO m) => MbtilesT m Text getFormat = findMeta "format" <$> getMetadata -- | Write new tile data to the tile at the specified 'Z', 'X', and 'Y' parameters. -- This function assumes that the tile does not already exist. writeTile :: (MonadIO m, ToTile a) => Z -> X -> Y -> a -> MbtilesT m () writeTile z x y t = writeTiles [(z, x, y, t)] -- | Batch write new tile data to the tile at the specified 'Z', 'X', and 'Y' parameters. -- This function assumes that the tiles do not already exist. writeTiles :: (MonadIO m, ToTile a) => [(Z, X, Y, a)] -> MbtilesT m () writeTiles = execQueryOnTiles newTileQuery -- | Update existing tile data for the tile at the specified 'Z', 'X', and 'Y' parameters. -- This function assumes that the tile does already exist. updateTile :: (MonadIO m, ToTile a) => Z -> X -> Y -> a -> MbtilesT m () updateTile z x y t = updateTiles [(z, x, y, t)] -- | Batch update tile data for the tiles at the specified 'Z', 'X', and 'Y' parameters. -- This function assumes that the tiles do already exist. updateTiles :: (MonadIO m, ToTile a) => [(Z, X, Y, a)] -> MbtilesT m () updateTiles = execQueryOnTiles updateTileQuery execQueryOnTiles :: (MonadIO m, ToTile a) => Query -> [(Z, X, Y, a)] -> MbtilesT m () execQueryOnTiles q ts = MbtilesT $ do c <- conn <$> ask liftIO $ executeMany c q $ map (\(z, x, y, t) -> (z, z, y, toTile t)) ts findMeta :: Text -> MbtilesMeta -> Text findMeta t m = m ! t