readshp (empty) → 0.1.0.0
raw patch · 9 files changed
+676/−0 lines, 9 filesdep +basedep +binarydep +bytestringsetup-changed
Dependencies added: base, binary, bytestring, data-binary-ieee754, filepath, monad-loops
Files
- Geometry/Shapefile.hs +16/−0
- Geometry/Shapefile/Internal.hs +58/−0
- Geometry/Shapefile/MergeShpDbf.hs +30/−0
- Geometry/Shapefile/ReadDbf.hs +63/−0
- Geometry/Shapefile/ReadShp.hs +247/−0
- Geometry/Shapefile/Types.hs +207/−0
- LICENSE +20/−0
- Setup.hs +2/−0
- readshp.cabal +33/−0
+ Geometry/Shapefile.hs view
@@ -0,0 +1,16 @@+{-|+Module : Geometry.Shapefile+Description : Code for reading ESRI shapefiles.+ Developed for intelligence2integrity.+Author : Sam van Herwaarden <samvherwaarden@gmail.com>+-}+module Geometry.Shapefile ( module Geometry.Shapefile.MergeShpDbf,+ module Geometry.Shapefile.ReadDbf,+ module Geometry.Shapefile.ReadShp,+ module Geometry.Shapefile.Types+ ) where++import Geometry.Shapefile.MergeShpDbf+import Geometry.Shapefile.ReadDbf+import Geometry.Shapefile.ReadShp+import Geometry.Shapefile.Types
+ Geometry/Shapefile/Internal.hs view
@@ -0,0 +1,58 @@+{-|+Module : Geometry.Shapefile.Internal+Description : Some functions for reuse inside the library+Author : Sam van Herwaarden <samvherwaarden@gmail.com>+-}++module Geometry.Shapefile.Internal where++import Control.Monad ( replicateM )+import Data.Binary.Get+import Data.Binary.IEEE754 ( getFloat64le )+import qualified Data.ByteString.Char8 as C++-- | 8-bit Int value+getInt8 :: Get Int+getInt8 = fromIntegral <$> getWord8++-- | 32-bit big-endian Int+getIntBE :: Get Int+getIntBE = fromIntegral <$> getWord32be++-- | 16-bit big-endian Int+getInt16BE :: Get Int+getInt16BE = fromIntegral <$> getWord16be++-- | 32-bit little-endian Int+getIntLE :: Get Int+getIntLE = fromIntegral <$> getWord32le++-- | 16-bit little-endian Int+getInt16LE :: Get Int+getInt16LE = fromIntegral <$> getWord16le++-- | String of length `l`+getString :: Int -> Get String+getString l = filter (/= '\NUL') . C.unpack <$> getByteString l++-- | Char (presumably 8-bit)+getCharVal :: Get Char+getCharVal = C.head <$> getByteString 1++type Point = (Double, Double)++-- | Two doubles making up a point with two coordinates+getPoint :: Get Point+getPoint = do x <- getFloat64le+ y <- getFloat64le+ return (x, y)++-- | Get the increments between list values+steps :: Num a => [a] -> [a]+steps [] = []+steps (_:[]) = []+steps (x1:x2:xs) = x2 - x1 : steps (x2:xs)++-- | List of points of length `numPoints`+getPointList :: Int -> Get [Point]+getPointList numPoints = replicateM numPoints getPoint
+ Geometry/Shapefile/MergeShpDbf.hs view
@@ -0,0 +1,30 @@+{-|+Module : Geometry.Shapefile.MergeShpDbf+Description : Code for reading ESRI shapefiles with DBF data+Author : Sam van Herwaarden <samvherwaarden@gmail.com>+-}++module Geometry.Shapefile.MergeShpDbf ( readShpWithDbf ) where++import System.FilePath++import qualified Data.ByteString.Lazy as BL++import Geometry.Shapefile.ReadDbf+import Geometry.Shapefile.ReadShp+import Geometry.Shapefile.Types++-- | Read a shp-file from `fp`, and a dbf file from the same path but with+-- modified extension.+readShpWithDbf :: String -> IO ShpData+readShpWithDbf fp = do+ shpData <- readShpData <$> BL.readFile fp+ let dbfPath = dropExtension fp ++ ".dbf"+ dbfData <- readDbfData <$> BL.readFile dbfPath+ return shpData {+ dbfFieldDescs = Just $ dbfFields dbfData,+ shpRecs = zipWith addRecLabel (shpRecs shpData) (dbfRecords dbfData)+ }++addRecLabel :: ShpRec -> [DbfRecord] -> ShpRec+addRecLabel sr labels = sr { shpRecLabel = Just labels }
+ Geometry/Shapefile/ReadDbf.hs view
@@ -0,0 +1,63 @@+{-|+Module : Geometry.Shapefile.ReadDbf+Description : Code for reading DBF files.+ Not robust, handle with care. YMMV+ Based on http://www.dbf2002.com/dbf-file-format.html+Author : Sam van Herwaarden <samvherwaarden@gmail.com>+-}++module Geometry.Shapefile.ReadDbf ( readDbfFile,+ readDbfData+ ) where++import Control.Monad ( replicateM )+import Control.Monad.Loops ( whileM )+import Data.Binary.Get++import qualified Data.ByteString.Lazy as BL++import Geometry.Shapefile.Internal+import Geometry.Shapefile.Types++-- | Read dbf file at `fp` into resident data format `DbfData`+readDbfFile :: String -> IO DbfData+readDbfFile fp = readDbfData <$> BL.readFile fp++-- | Parse a ByteString containing DBF data+readDbfData :: BL.ByteString -> DbfData+readDbfData = runGet $ do+ _ <- getByteString 4 -- DBF File type+ numRecs <- getIntLE -- Number of records+ firstRecPos <- getInt16LE -- Position of first record+ _ <- getByteString 22 -- Rec len, table flags, code page mark+ -- Field descriptors+ let fieldsRemain = (< firstRecPos - 1) . fromIntegral <$> bytesRead+ fields <- whileM fieldsRemain getDbfFieldDesc+ _ <- getWord8 -- Skip spacer+ -- Records+ recs <- replicateM numRecs (getWord8 >> mapM getDbfRecord fields)+ -- Everything together+ return DbfData { dbfNumRecs = numRecs,+ dbfFields = fields,+ dbfRecords = recs }++-- | Read header field descriptor+getDbfFieldDesc :: Get DbfFieldDesc+getDbfFieldDesc = do+ name <- getString 11 -- Field name+ fieldT <- getCharVal -- Field type+ _ <- getIntLE -- Field displacement (?)+ len <- getInt8 -- Field length+ _ <- getByteString 15 -- Num decimal/field flags/autoincrement+ return DbfFieldDesc { fieldName = name,+ fieldType = fieldT,+ fieldLen = len }++-- | Read a record. Not all record types are (meaningfully) implemented.+getDbfRecord :: DbfFieldDesc -> Get DbfRecord+getDbfRecord field = let n = fieldLen field in+ case fieldType field of+ 'C' -> DbfString <$> getString n+ 'N' -> DbfNum <$> getString n+ _ -> DbfBS <$> getByteString n+
+ Geometry/Shapefile/ReadShp.hs view
@@ -0,0 +1,247 @@+{-|+Module : Geometry.Shapefile.ReadShp+Description : Code for reading the binary ESRI Shapefile format.+ https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf+Author : Sam van Herwaarden <samvherwaarden@gmail.com>+-}++module Geometry.Shapefile.ReadShp ( readShpFile,+ readShpData,+ ) where++import Control.Monad ( replicateM, when )+import Control.Monad.Loops ( whileM )+import Data.Binary.Get+import Data.Binary.IEEE754 ( getFloat64le )++import qualified Data.ByteString.Lazy as BL++import Geometry.Shapefile.Internal+import Geometry.Shapefile.Types++-- | Read shapefile at `fp` to resident data structure `ShpData`+readShpFile :: String -> IO ShpData+readShpFile fp = readShpData <$> BL.readFile fp++-- | Parse a shapefile ByteString+readShpData :: BL.ByteString -> ShpData+readShpData = runGet $ do+ shpH <- getShpHeader+ shpRs <- whileM (not <$> isEmpty) getShpRec+ return ShpData { shpHeader = shpH,+ dbfFieldDescs = Nothing, -- fill using readDbfData+ shpRecs = shpRs }++-- | Header of the shapefile+getShpHeader :: Get ShpHeader+getShpHeader = do+ fc <- getIntBE -- File code+ when (fc /= 9994) (error "getShpHeader: Input file is not a SHP file")+ _ <- replicateM 5 getWord32be -- Unused data+ shpLen <- getIntBE -- File length+ shpV <- getIntLE -- Version (little-endian!)+ shpT <- getShpType -- Shape type+ shpbb <- getShpBBox shpT -- Bounding box+ return ShpHeader { shpFileLength = shpLen,+ shpVersion = shpV,+ shpType = shpT,+ shpBB = shpbb }++-- | Get type of the record(s)+getShpType :: Get ShpType+getShpType = shpTypeFromId <$> getIntLE++-- | Read a bounding box for relevant record type.+-- Not all bounding boxes have Z/M coordinates.+getShpBBox :: ShpType -> Get ShpBBox+getShpBBox t = do+ [xMin, yMin, xMax, yMax] <- replicateM 4 getFloat64le+ [zMin, zMax] <- let ds = replicateM 2 getFloat64le+ in if t `elem` zTypes+ then map Just <$> ds+ else ds >> return [Nothing, Nothing]+ [mMin, mMax] <- let ds = replicateM 2 getFloat64le+ in if t `elem` mTypes+ then map Just <$> ds+ else ds >> return [Nothing, Nothing]+ return ShpBBox { shpXMin = xMin,+ shpXMax = xMax,+ shpYMin = yMin,+ shpYMax = yMax,+ shpZMin = zMin,+ shpZMax = zMax,+ shpMMin = mMin,+ shpMMax = mMax }++-- | Read a single record+getShpRec :: Get ShpRec+getShpRec = do+ recNum <- getIntBE -- Record number+ recLen <- getIntBE -- Record length+ recType <- getShpType -- Shape type+ recContents <- if recType == ShpNull -- Record contents+ then return Nothing+ else Just <$> getRecContents recType+ return ShpRec { shpRecNum = recNum,+ shpRecLen = recLen,+ shpRecContents = recContents,+ shpRecLabel = Nothing,+ shpRecType = recType }++-- | Get record contents for record of given type+getRecContents :: ShpType -> Get RecContents+getRecContents t = case t of++ ShpPoint -> do+ p <- getPoint+ return $ RecPoint p++ ShpPointM -> do+ p <- getPoint+ m <- getFloat64le+ return $ RecPointM { pmPoint = p, pmM = m }++ ShpPointZ -> do+ p <- getPoint+ z <- getFloat64le+ m <- getFloat64le+ return $ RecPointZ { pzPoint = p, pzZ = z, pzM = m }++ ShpMultiPoint -> do+ (bb, nPoints, points) <- getPointsData+ return RecMultiPoint { recMPBBox = bb,+ recMPNumPoints = nPoints,+ recMPPoints = points }++ ShpMultiPointM -> do+ (bb, nPoints, points) <- getPointsData+ (mMin, mMax, ms) <- getMData nPoints+ return RecMultiPointM { recMPMBBox = bb,+ recMPMNumPoints = nPoints,+ recMPMPoints = points,+ recMPMMRange = (mMin, mMax),+ recMPMMs = ms}++ ShpMultiPointZ -> do+ (bb, nPoints, points) <- getPointsData+ (zMin, zMax, zs) <- getZData nPoints+ (mMin, mMax, ms) <- getMData nPoints+ return RecMultiPointZ { recMPZBBox = bb,+ recMPZNumPoints = nPoints,+ recMPZPoints = points,+ recMPZZRange = (zMin, zMax),+ recMPZZs = zs,+ recMPZMRange = (mMin, mMax),+ recMPZMs = ms }++ ShpPolyLine -> do+ (bb, nParts, nPoints, partLengths, pntLists) <- getPolyData+ return RecPolyLine { recPolLBBox = bb,+ recPolLNumParts = nParts,+ recPolLNumPoints = nPoints,+ recPolLPartLengths = partLengths,+ recPolLPoints = pntLists }++ ShpPolyLineM -> do+ (bb, nParts, nPoints, partLengths, pntLists) <- getPolyData+ (mMin, mMax, ms) <- getMData nPoints+ return RecPolyLineM { recPolLMBBox = bb,+ recPolLMNumParts = nParts,+ recPolLMNumPoints = nPoints,+ recPolLMPartLengths = partLengths,+ recPolLMPoints = pntLists,+ recPolLMMRange = (mMin, mMax),+ recPolLMMs = ms }++ ShpPolyLineZ -> do+ (bb, nParts, nPoints, partLengths, pntLists) <- getPolyData+ (zMin, zMax, zs) <- getZData nPoints+ (mMin, mMax, ms) <- getMData nPoints+ return RecPolyLineZ { recPolLZBBox = bb,+ recPolLZNumParts = nParts,+ recPolLZNumPoints = nPoints,+ recPolLZPartLengths = partLengths,+ recPolLZPoints = pntLists,+ recPolLZZRange = (zMin, zMax),+ recPolLZZs = zs,+ recPolLZMRange = (mMin, mMax),+ recPolLZMs = ms }++ ShpPolygon -> do+ (bb, nParts, nPoints, partLengths, pntLists) <- getPolyData+ return RecPolygon { recPolBBox = bb,+ recPolNumParts = nParts,+ recPolNumPoints = nPoints,+ recPolPartLengths = partLengths,+ recPolPoints = pntLists }++ ShpPolygonM -> do+ (bb, nParts, nPoints, partLengths, pntLists) <- getPolyData+ (mMin, mMax, ms) <- getMData nPoints+ return RecPolygonM { recPolMBBox = bb,+ recPolMNumParts = nParts,+ recPolMNumPoints = nPoints,+ recPolMPartLengths = partLengths,+ recPolMPoints = pntLists,+ recPolMMRange = (mMin, mMax),+ recPolMMs = ms }++ ShpPolygonZ -> do+ (bb, nParts, nPoints, partLengths, pntLists) <- getPolyData+ (zMin, zMax, zs) <- getZData nPoints+ (mMin, mMax, ms) <- getMData nPoints+ return RecPolygonZ { recPolZBBox = bb,+ recPolZNumParts = nParts,+ recPolZNumPoints = nPoints,+ recPolZPartLengths = partLengths,+ recPolZPoints = pntLists,+ recPolZZRange = (zMin, zMax),+ recPolZZs = zs,+ recPolZMRange = (mMin, mMax),+ recPolZMs = ms }++ ShpNull ->+ return RecNull++ ShpMultiPatch ->+ error "getShpRec: MultiPatch type is not supported, sorry!"++-- | Recurring pattern of bounding box with a number of points+getPointsData :: Get (RecBBox, Int, [Point])+getPointsData = do+ bb <- getRecBBox+ nPoints <- getIntLE+ points <- replicateM nPoints getPoint+ return (bb, nPoints, points)++-- | Recurring pattern of a bounding box with polygon data+getPolyData :: Get (RecBBox, Int, Int, [Int], [[Point]])+getPolyData = do+ bb <- getRecBBox+ nParts <- getIntLE+ nPoints <- getIntLE+ partIndices <- replicateM nParts getIntLE+ let partLengths = steps $ partIndices ++ [nPoints]+ pntLists <- mapM getPointList partLengths+ return (bb, nParts, nPoints, partLengths, pntLists)++-- | Recurring pattern of M-valued data+getMData :: Int -> Get (Double, Double, [Double])+getMData nPoints = do+ [mMin, mMax] <- replicateM 2 getFloat64le+ ms <- replicateM nPoints getFloat64le+ return (mMin, mMax, ms)++-- | Recurring pattern of Z-valued data (same structure as M-valued data)+getZData :: Int -> Get (Double, Double, [Double])+getZData = getMData++-- | Record bounding boxes contain no information on M/Z+getRecBBox :: Get RecBBox+getRecBBox = do+ [xMin, yMin, xMax, yMax] <- replicateM 4 getFloat64le+ return RecBBox { recXMin = xMin,+ recXMax = xMax,+ recYMin = yMin,+ recYMax = yMax }+
+ Geometry/Shapefile/Types.hs view
@@ -0,0 +1,207 @@+{-|+Module : Geometry.Shapefile.Types+Description : Type definitions used by the library. Currently (24 Nov 2015)+ MultiPatch record types are not implemented. Only Point and+ Polygon are (somewhat) tested. YMMV+ https://www.esri.com/library/whitepapers/pdfs/shapefile.pdf+Author : Sam van Herwaarden <samvherwaarden@gmail.com>+-}++module Geometry.Shapefile.Types where++import qualified Data.ByteString as BS++import Geometry.Shapefile.Internal++-- * SHP Types++data ShpData =+ ShpData {+ shpHeader :: ShpHeader,+ dbfFieldDescs :: Maybe [DbfFieldDesc],+ shpRecs :: [ShpRec]+ } deriving (Eq, Show)++data ShpHeader =+ ShpHeader {+ shpFileLength :: Int,+ shpVersion :: Int,+ shpType :: ShpType,+ shpBB :: ShpBBox+ } deriving (Eq, Show)++data ShpType = ShpNull+ | ShpPoint+ | ShpPolyLine+ | ShpPolygon+ | ShpMultiPoint+ | ShpPointZ+ | ShpPolyLineZ+ | ShpPolygonZ+ | ShpMultiPointZ+ | ShpPointM+ | ShpPolyLineM+ | ShpPolygonM+ | ShpMultiPointM+ | ShpMultiPatch+ deriving (Eq, Show)++-- | Types with Z values+zTypes :: [ShpType]+zTypes = [ShpPointZ, ShpPolyLineZ, ShpPolygonZ, ShpMultiPointZ]++-- | Types with M values+mTypes :: [ShpType]+mTypes = [ShpPointM, ShpPolyLineM, ShpPolygonM, ShpMultiPointM]++-- | Numeric codes used for record types+shpTypeFromId :: Int -> ShpType+shpTypeFromId i =+ case i of 0 -> ShpNull+ 1 -> ShpPoint+ 3 -> ShpPolyLine+ 5 -> ShpPolygon+ 8 -> ShpMultiPoint+ 11 -> ShpPointZ+ 13 -> ShpPolyLineZ+ 15 -> ShpPolygonZ+ 18 -> ShpMultiPointZ+ 21 -> ShpPointM+ 23 -> ShpPolyLineM+ 25 -> ShpPolygonM+ 28 -> ShpMultiPointM+ 31 -> ShpMultiPatch+ _ -> error "shpTypeFromId: Unknown Shape Type"++data ShpBBox =+ ShpBBox {+ shpXMin :: Double,+ shpXMax :: Double,+ shpYMin :: Double,+ shpYMax :: Double,+ shpZMin :: Maybe Double,+ shpZMax :: Maybe Double,+ shpMMin :: Maybe Double,+ shpMMax :: Maybe Double+ } deriving (Eq, Show)+++data ShpRec =+ ShpRec {+ shpRecNum :: Int,+ shpRecLen :: Int,+ shpRecContents :: Maybe RecContents, -- Can be null type+ shpRecLabel :: Maybe [DbfRecord], -- Should be usable without dbf+ shpRecType :: ShpType+ } deriving (Eq, Show)++data RecContents =+ RecNull+ | RecPoint Point+ | RecPointM {+ pmPoint :: Point,+ pmM :: Double }+ | RecPointZ {+ pzPoint :: Point,+ pzZ :: Double,+ pzM :: Double }+ | RecMultiPoint {+ recMPBBox :: RecBBox,+ recMPNumPoints :: Int,+ recMPPoints :: [Point] }+ | RecMultiPointM {+ recMPMBBox :: RecBBox,+ recMPMNumPoints :: Int,+ recMPMPoints :: [Point],+ recMPMMRange :: (Double, Double),+ recMPMMs :: [Double] }+ | RecMultiPointZ {+ recMPZBBox :: RecBBox,+ recMPZNumPoints :: Int,+ recMPZPoints :: [Point],+ recMPZZRange :: (Double, Double),+ recMPZZs :: [Double],+ recMPZMRange :: (Double, Double),+ recMPZMs :: [Double] }+ | RecPolyLine {+ recPolLBBox :: RecBBox,+ recPolLNumParts :: Int,+ recPolLNumPoints :: Int,+ recPolLPartLengths :: [Int],+ recPolLPoints :: [[Point]] }+ | RecPolyLineM {+ recPolLMBBox :: RecBBox,+ recPolLMNumParts :: Int,+ recPolLMNumPoints :: Int,+ recPolLMPartLengths :: [Int],+ recPolLMPoints :: [[Point]],+ recPolLMMRange :: (Double, Double),+ recPolLMMs :: [Double] }+ | RecPolyLineZ {+ recPolLZBBox :: RecBBox,+ recPolLZNumParts :: Int,+ recPolLZNumPoints :: Int,+ recPolLZPartLengths :: [Int],+ recPolLZPoints :: [[Point]],+ recPolLZZRange :: (Double, Double),+ recPolLZZs :: [Double],+ recPolLZMRange :: (Double, Double),+ recPolLZMs :: [Double] }+ | RecPolygon {+ recPolBBox :: RecBBox,+ recPolNumParts :: Int,+ recPolNumPoints :: Int,+ recPolPartLengths :: [Int],+ recPolPoints :: [[Point]] }+ | RecPolygonM {+ recPolMBBox :: RecBBox,+ recPolMNumParts :: Int,+ recPolMNumPoints :: Int,+ recPolMPartLengths :: [Int],+ recPolMPoints :: [[Point]],+ recPolMMRange :: (Double, Double),+ recPolMMs :: [Double] }+ | RecPolygonZ {+ recPolZBBox :: RecBBox,+ recPolZNumParts :: Int,+ recPolZNumPoints :: Int,+ recPolZPartLengths :: [Int],+ recPolZPoints :: [[Point]],+ recPolZZRange :: (Double, Double),+ recPolZZs :: [Double],+ recPolZMRange :: (Double, Double),+ recPolZMs :: [Double] }+ -- | RecMultiPatch+ deriving (Eq, Show)++data RecBBox =+ RecBBox {+ recXMin :: Double,+ recXMax :: Double,+ recYMin :: Double,+ recYMax :: Double+ } deriving (Eq, Show)++-- * DBF Types++data DbfData =+ DbfData {+ dbfNumRecs :: Int,+ dbfFields :: [DbfFieldDesc],+ dbfRecords :: [[DbfRecord]]+ } deriving (Eq, Show)++data DbfFieldDesc =+ DbfFieldDesc {+ fieldName :: String,+ fieldType :: Char,+ fieldLen :: Int+ } deriving (Eq, Show)++-- | Not all record types are implemented. Use your favorite method to+-- convert any bytestring (and feel free to add functionality).+data DbfRecord = DbfString String+ | DbfNum String+ | DbfBS BS.ByteString+ deriving (Eq, Show)+
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015 Sam van Herwaarden++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ readshp.cabal view
@@ -0,0 +1,33 @@+name: readshp+version: 0.1.0.0+synopsis: Code for reading ESRI Shapefiles.+description: Provides code for binary parsing of ESRI shapefiles,+ where the .shp and the .dbf file are read. The code has+ not been tested on a large variety of shapefiles so+ your mileage may vary, but so far it has worked with+ no problems. At the moment only text and number fields+ in DBF files are read (the others are stored as+ ByteString).+license: MIT+license-file: LICENSE+author: Sam van Herwaarden+maintainer: samvherwaarden@gmail.com+category: GIS+build-type: Simple+cabal-version: >=1.10++library+ exposed-modules: Geometry.Shapefile,+ Geometry.Shapefile.MergeShpDbf,+ Geometry.Shapefile.ReadDbf,+ Geometry.Shapefile.ReadShp,+ Geometry.Shapefile.Types+ other-modules: Geometry.Shapefile.Internal+ build-depends: base >=4.8 && <4.9,+ binary,+ bytestring,+ data-binary-ieee754,+ filepath,+ monad-loops+ default-language: Haskell2010+ ghc-options: -Wall -fno-warn-unused-do-bind