{-# LANGUAGE RecordWildCards #-} module Database.Shapefile.Shx where import Database.Shapefile.Shp import Data.Word import Data.Binary.Get import Data.Binary.Put import Data.List import Control.Monad -- |offset and length of corresponding shape in 16-bit words data ShxRec = ShxRec { -- |Offset of the corresponding ShpRec in 16-bit words shxOffset :: Word32 , -- |Length of the corresponding ShpRec in 16-bit words shxLength :: Word32 } deriving (Eq, Show) -- |Construct a 'ShxRec' given the record offset and length in bytes shxRecBytes :: Integer -> Integer -> ShxRec shxRecBytes offBytes lenBytes | odd offBytes = error "shxRecBytes: odd byte offset" | offBytes > big = error "shxRecBytes: offset too large for Word32" | odd lenBytes = error "shxRecBytes: odd byte length" | lenBytes > big = error "shxRecBytes: length too large for Word32" | otherwise = ShxRec (b2w offBytes) (b2w lenBytes) where big = 2 * toInteger (maxBound :: Word32) b2w x = fromInteger (x `div` 2) shxOffsetBytes :: ShxRec -> Integer shxOffsetBytes = (*2) . toInteger . shxOffset shxLengthBytes :: ShxRec -> Integer shxLengthBytes = (*2) . toInteger . shxLength putShxRec :: ShxRec -> Put putShxRec ShxRec {..} = do {- 0: Offset -} putWord32be shxOffset {- 4: Length -} putWord32be shxLength {- 8 bytes total -} getShxRec :: Get ShxRec getShxRec = do {- 0: Offset -} shxOffset <- getWord32be {- 4: Length -} shxLength <- getWord32be {- 8 bytes total -} return ShxRec { shxOffset = shxOffset , shxLength = shxLength } -- |Construct an index for the provided .shp file contents shxFromShp :: ShpFileHeader -> [ShpRec] -> (ShpFileHeader, [ShxRec]) shxFromShp shpHdr shpRecs = (shxHdr, shxRecs) where nRecs = genericLength shpRecs shxHdr = shpHdr { shpFileLength = 50 + nRecs * 4} (shpFileLen, shxRecs) = mapAccumL mkShxRec 50 shpRecs shpLen = shpRecSize . shpRecHdr mkShxRec off shp = let len = shpLen shp in (off + 4 + len, ShxRec off len) putShxFile :: ShpFileHeader -> [ShxRec] -> Put putShxFile shxHdr shxRecs = do putShpFileHeader shxHdr mapM_ putShxRec shxRecs getShxFile :: Get (ShpFileHeader, [ShxRec]) getShxFile = do shxHdr <- getShpFileHeader let nWords = shpFileLength shxHdr divExact p q = case p `divMod` q of (d,0) -> return d _ -> fail ("getShxFile: size in header does not make sense (" ++ show p ++ " words)") nRecs <- (nWords - 50) `divExact` 4 shxRecs <- replicateM (fromIntegral nRecs) getShxRec return (shxHdr, shxRecs)