packages feed

vectortiles-1.2.0.3: test/Test.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeApplications #-}

module Main where

import qualified Data.ByteString as BS
import           Data.Hex
import           Data.ProtocolBuffers hiding (decode)
import           Data.Serialize.Get
import           Data.Serialize.Put
import qualified Geography.VectorTile.Protobuf.Internal as R
import           Test.Tasty
import           Test.Tasty.HUnit
import           Geography.VectorTile
import           Geography.VectorTile.Geometry
import qualified Data.Vector.Unboxed as U

---

main :: IO ()
main = do
  op <- BS.readFile "test/onepoint.mvt"
  ls <- BS.readFile "test/linestring.mvt"
  pl <- BS.readFile "test/polygon.mvt"
  rd <- BS.readFile "test/roads.mvt"
  cl <- BS.readFile "test/clearlake.mvt"
  defaultMain $ suite op ls pl rd cl

{- SUITES -}

suite :: BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString -> TestTree
suite op ls pl rd cl = testGroup "Unit Tests"
  [ testGroup "Protobuf"
    [ testGroup "Decoding"
      [ testCase "onepoint.mvt -> Raw.Tile" $ testOnePoint op
      , testCase "linestring.mvt -> Raw.Tile" $ testLineString ls
      , testCase "polygon.mvt -> Raw.Tile" $ testPolygon pl
      , testCase "roads.mvt -> Raw.Tile" $ testDecode rd
      , testCase "clearlake.mvt -> Raw.Tile" $ testDecode cl
      , testCase "onepoint.mvt -> VectorTile" $ tileDecode op
      , testCase "linestring.mvt -> VectorTile" $ tileDecode ls
      , testCase "polygon.mvt -> VectorTile" $ tileDecode pl
      , testCase "roads.mvt -> VectorTile" $ tileDecode rd
      , testCase "clearlake.mvt -> VectorTile" $ tileDecode cl
      ]
    , testGroup "Encoding"
      [ testGroup "RawVectorTile <-> VectorTile"
        [ testCase "One Point" $ encodeIso onePoint
        , testCase "One LineString" $ encodeIso oneLineString
        , testCase "One Polygon" $ encodeIso onePolygon
        , testCase "roads.mvt" . encodeIso . fromRight $ decode rd
        ]
      ]
    , testGroup "Serialization Isomorphism"
      [ --testCase "onepoint.mvt <-> Raw.Tile" $ fromRaw op
--      , testCase "linestring.mvt <-> Raw.Tile" $ fromRaw ls
--      , testCase "polygon.mvt <-> Raw.Tile" $ fromRaw pl
      --    , testCase "roads.mvt <-> Raw.Tile" $ fromRaw rd
      testCase "testTile <-> protobuf bytes" testTileIso
      ]
    ]
  , testGroup "Geometries"
    [ testCase "Z-encoding Isomorphism" zencoding
    , testCase "Command Parsing" commandTest
    , testCase "[Word32] <-> [Command]" commandIso
    , testCase "[Word32] <-> V.Vector Point" pointIso
    , testCase "[Word32] <-> V.Vector LineString" linestringIso
    , testCase "[Word32] <-> V.Vector Polygon (2 solid)" polygonIso
    , testCase "[Word32] <-> V.Vector Polygon (1 holed)" polygonIso2
    , testCase "[Word32] <-> V.Vector Polygon (1 holed, 1 solid)" polygonIso3
    ]
  ]

testOnePoint :: BS.ByteString -> Assertion
testOnePoint vt = case decodeIt vt of
                    Left e -> assertFailure e
                    Right t -> t @?= onePoint

testLineString :: BS.ByteString -> Assertion
testLineString vt = case decodeIt vt of
                      Left e -> assertFailure e
                      Right t -> t @?= oneLineString

testPolygon :: BS.ByteString -> Assertion
testPolygon vt = case decodeIt vt of
                   Left e -> assertFailure e
                   Right t -> t @?= onePolygon

-- | For testing is decoding succeeded in generally. Makes no guarantee
-- about the quality of the content, only that the parse succeeded.
testDecode :: BS.ByteString -> Assertion
testDecode = assert . isRight . decodeIt

tileDecode :: BS.ByteString -> Assertion
tileDecode bs = case decodeIt bs of
  Left e -> assertFailure e
  Right t -> assert . isRight $ R.fromProtobuf t

fromRaw :: BS.ByteString -> Assertion
fromRaw vt = case decodeIt vt of
               Left e -> assertFailure e
               Right l -> hex (encodeIt l) @?= hex vt
--               Right l -> if runPut (encodeMessage l) == vt
--                          then assert True
--                          else assertString "Isomorphism failed."

testTileIso :: Assertion
testTileIso = case decodeIt pb of
                 Right tl -> assertEqual "" tl testTile
                 Left e -> assertFailure e
  where pb = encodeIt testTile

decodeIt :: BS.ByteString -> Either String R.RawVectorTile
decodeIt = runGet decodeMessage

encodeIt :: R.RawVectorTile -> BS.ByteString
encodeIt = runPut . encodeMessage

isRight :: Either a b -> Bool
isRight (Right _) = True
isRight _ = False

fromRight :: Either a b -> b
fromRight (Right b) = b
fromRight _ = error "`Left` given to fromRight!"

rawTest :: IO (Either String R.RawVectorTile)
rawTest = decodeIt <$> BS.readFile "onepoint.mvt"

encodeIso :: R.RawVectorTile -> Assertion
encodeIso vt = assert . isRight . fmap R.toProtobuf $ R.fromProtobuf vt

testTile :: R.RawVectorTile
testTile = R.RawVectorTile $ putField [l]
  where l = R.RawLayer { R._version = putField 2
                       , R._name = putField "testlayer"
                       , R._features = putField [f]
                       , R._keys = putField ["somekey"]
                       , R._values = putField [v]
                       , R._extent = putField $ Just 4096
                       }
        f = R.RawFeature { R._featureId = putField $ Just 0
                         , R._tags = putField [0,0]
                         , R._geom = putField $ Just R.Point
                         , R._geometries = putField [9, 50, 34]  -- MoveTo(+25,+17)
                         }
        v = R.RawVal { R._string = putField $ Just "Some Value"
                     , R._float = putField Nothing
                     , R._double = putField Nothing
                     , R._int64 = putField Nothing
                     , R._uint64 = putField Nothing
                     , R._sint = putField Nothing
                     , R._bool = putField Nothing
                     }

-- | Correct decoding of `onepoint.mvt`
onePoint :: R.RawVectorTile
onePoint = R.RawVectorTile $ putField [l]
  where l = R.RawLayer { R._version = putField 1
                       , R._name = putField "OnePoint"
                       , R._features = putField [f]
                       , R._keys = putField []
                       , R._values = putField []
                       , R._extent = putField $ Just 4096
                       }
        f = R.RawFeature { R._featureId = putField Nothing
                         , R._tags = putField []
                         , R._geom = putField $ Just R.Point
                         , R._geometries = putField [9, 10, 10]  -- MoveTo(+5,+5)
                         }

-- | Correct decoding of `linestring.mvt`
oneLineString :: R.RawVectorTile
oneLineString = R.RawVectorTile $ putField [l]
  where l = R.RawLayer { R._version = putField 1
                       , R._name = putField "OneLineString"
                       , R._features = putField [f]
                       , R._keys = putField []
                       , R._values = putField []
                       , R._extent = putField $ Just 4096
                       }
        f = R.RawFeature { R._featureId = putField Nothing
                         , R._tags = putField []
                         , R._geom = putField $ Just R.LineString
                         -- MoveTo(+5,+5), LineTo(+1195,+1195)
                         , R._geometries = putField [9, 10, 10, 10, 2390, 2390]
                         }

-- | Correct decoding of `polygon.mvt`
onePolygon :: R.RawVectorTile
onePolygon = R.RawVectorTile $ putField [l]
  where l = R.RawLayer { R._version = putField 1
                       , R._name = putField "OnePolygon"
                       , R._features = putField [f]
                       , R._keys = putField []
                       , R._values = putField []
                       , R._extent = putField $ Just 4096
                       }
        f = R.RawFeature { R._featureId = putField Nothing
                         , R._tags = putField []
                         , R._geom = putField $ Just R.Polygon
                         -- MoveTo(+2,+2), LineTo(+3,+2), LineTo(-3,+2), ClosePath
                         , R._geometries = putField [9, 4, 4, 18, 6, 4, 5, 4, 15]
                         }

zencoding :: Assertion
zencoding = assert $ map (R.unzig . R.zig) vs @?= vs
  where vs = [0,(-1),1,(-2),2,(-3),3,2147483647,(-2147483648)]

commandTest :: Assertion
commandTest = assert $ R.commands [9,4,4,18,6,4,5,4,15] @?= Right
  [ R.MoveTo $ U.singleton (2,2)
  , R.LineTo $ U.fromList [(3,2),(-3,2)]
  , R.ClosePath ]

commandIso :: Assertion
commandIso = assert $ (R.uncommands . fromRight $ R.commands cs) @?= cs
  where cs = [9,4,4,18,6,4,5,4,15]

pointIso :: Assertion
pointIso = cs' @?= cs
  where cs = [25,4,4,6,6,3,3]
        cs' = fromRight $ R.uncommands . R.toCommands <$> (R.commands cs >>= R.fromCommands @Point)

linestringIso :: Assertion
linestringIso = cs' @?= cs
  where cs = [9,4,4,18,6,4,5,4,9,4,4,18,6,4,5,4]
        cs' = fromRight $ R.uncommands . R.toCommands <$> (R.commands cs >>= R.fromCommands @LineString)

-- | Two solids
polygonIso :: Assertion
polygonIso = cs' @?= cs
  where cs = [9,4,4,18,6,4,5,4,15,9,4,4,18,6,4,5,4,15]
        cs' = fromRight $ R.uncommands . R.toCommands <$> (R.commands cs >>= R.fromCommands @Polygon)

-- | One holed
polygonIso2 :: Assertion
polygonIso2 = cs' @?= cs
  where cs = [9,4,4,26,6,0,0,6,5,0,15,9,2,3,26,0,2,2,0,0,1,15]
        cs' = fromRight $ R.uncommands . R.toCommands <$> (R.commands cs >>= R.fromCommands @Polygon)

-- | One Holed, one solid
polygonIso3 :: Assertion
polygonIso3 = cs' @?= cs
  where cs = [ 9, 4, 4, 26, 6, 0, 0, 6, 5, 0, 15, 9, 2, 3, 26, 0, 2, 2, 0, 0, 1, 15
             , 9, 4, 4, 26, 6, 0, 0, 6, 5, 0, 15 ]
        cs' = fromRight $ R.uncommands . R.toCommands <$> (R.commands cs >>= R.fromCommands @Polygon)

{-}
foo :: FilePath -> IO (Either Text VectorTile)
foo bs = do
  mvt <- BS.readFile bs
  pure $ R.decode mvt >>= tile

-- fmap (V.length . layers <$>) $ foo "roads.mvt"
-}