packages feed

geojson (empty) → 0.0.1

raw patch · 6 files changed

+746/−0 lines, 6 filesdep +QuickCheckdep +basedep +directorybuild-type:Customsetup-changed

Dependencies added: QuickCheck, base, directory, doctest, filepath, json

Files

+ Setup.hs view
@@ -0,0 +1,75 @@+#!/usr/bin/env runhaskell+{-# OPTIONS_GHC -Wall #-}+module Main (main) where++import Data.List ( nub )+import Data.Version ( showVersion )+import Control.Applicative+import Distribution.Package ( PackageName(PackageName), PackageId, InstalledPackageId, packageVersion, packageName )+import Distribution.PackageDescription ( PackageDescription(), TestSuite(..) )+import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )+import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose, findProgramVersion, currentDir )+import Distribution.Simple.BuildPaths ( autogenModulesDir )+import Distribution.Simple.Setup ( BuildFlags(buildVerbosity), fromFlag )+import Distribution.Simple.LocalBuildInfo ( withLibLBI, withTestLBI, LocalBuildInfo(), ComponentLocalBuildInfo(componentPackageDeps) )+import Distribution.Verbosity ( Verbosity )+import Distribution.Version ( Version )+import System.Directory ( getDirectoryContents )+import System.FilePath ( (</>) )++main :: IO ()+main = defaultMainWithHooks simpleUserHooks+    {   buildHook = \pkg lbi hooks flags -> do+            generateBuildModule (fromFlag (buildVerbosity flags)) pkg lbi+            buildHook simpleUserHooks pkg lbi hooks flags+    }++generateBuildModule :: Verbosity -> PackageDescription -> LocalBuildInfo -> IO ()+generateBuildModule verbosity pkg lbi = do+    let dir = autogenModulesDir lbi+    createDirectoryIfMissingVerbose verbosity True dir+    withLibLBI pkg lbi $ \_ libcfg ->+        withTestLBI pkg lbi $ \suite suitecfg -> do+            ghcOpts <-+                    generateCabalDevOpts+                <$> isCabalDevPresent+                <*> getGhcVersion verbosity+            rewriteFile (dir </> "Build_" ++ testName suite ++ ".hs") $ unlines+                [   "module Build_" ++ testName suite ++ " where"+                ,   "deps :: [String]"+                ,   "deps = " ++ show (formatdeps (testDeps libcfg suitecfg))+                ,   "opts :: [String]"+                ,   "opts = " ++ show ghcOpts+                ]+    where+        formatdeps = map (formatone . snd)+        formatone p = case packageName p of+            PackageName n -> n ++ "-" ++ showVersion (packageVersion p)++isCabalDevPresent :: IO Bool+--isCabalDevPresent = (not . null) <$> matchFileGlob "cabal-dev/"+isCabalDevPresent = do+    contents <- getDirectoryContents currentDir+    return $ "cabal-dev" `elem` contents++getGhcVersion :: Verbosity -> IO (Maybe Version)+getGhcVersion verb = findProgramVersion+    "--version"+    (last . words)+    verb+    "ghc"++generateCabalDevOpts :: Bool -> Maybe Version -> [String]+generateCabalDevOpts isCabalDev version =+    case version of+        Nothing -> []+        Just version' ->+            let+                baseOpts =  [   "-Lcabal-dev/lib"+                            ,   "-package-conf=cabal-dev/packages-" ++ showVersion version' ++ ".conf"+                            ]+            in+                if isCabalDev then baseOpts else []++testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)]+testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys
+ doctests/Main.hs view
@@ -0,0 +1,40 @@+module Main where++import Build_doctests (deps, opts)+import Control.Applicative+import Control.Monad+import Data.List+import System.Directory+import System.FilePath+import Test.DocTest++docopts :: [FilePath]+docopts =+    [   "-isrc"+    ,   "-idist/build/autogen"+    ,   "-optP-include"+    ,   "-optPdist/build/autogen/cabal_macros.h"+    ,   "-hide-all-packages"+    ] ++ map ("-package=" ++) deps ++ opts++-- the list of all file paths to search for source files+sourceDirs :: [FilePath]+sourceDirs = ["src"]++main :: IO ()+main = getSources >>= \sources -> doctest $ docopts ++ sources++getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])+getFilesAndDirectories dir = do+    c <- map (dir </>) . filter (`notElem` ["..", "."]) <$> getDirectoryContents dir+    (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c++isSourceFile :: FilePath -> Bool+isSourceFile p = (takeFileName p /= "Setup.hs") && (".hs" `isSuffixOf` p)++getSources :: IO [FilePath]+getSources = liftM (filter isSourceFile . concat) (mapM go sourceDirs)+    where+        go dir = do+            (dirs, files) <- getFilesAndDirectories dir+            (files ++) . concat <$> mapM go dirs
+ etc/LICENCE view
+ geojson.cabal view
@@ -0,0 +1,79 @@+name:               geojson+version:            0.0.1+license:            MIT+license-file:       etc/LICENCE+author:             Dom De Re+maintainer:         Dom De Re+copyright:          Copyright (C) 2010-2013+synopsis:           A thin GeoJSON Layer above the json library+category:           Text+description:        A thin GeoJSON Layer above the json library.+                    .+                    It currently conforms to version 1.0 of the GeoJSON spec which can be found here:+                    .+                    <http://geojson.org/geojson-spec.html>+                    .+                    Its built on top of the `json` library and doesn't currently take+                    advantage of laziness as much as it could, so those are areas for improvement++homepage:           https://github.com/domdere/hs-geojson/issues+bug-reports:        https://github.com/domdere/hs-geojson/issues+cabal-version:      >= 1.10+build-type:         Custom+tested-with:        GHC == 7.4.1+                ,   GHC == 7.6.3+--extra-source-files: etc/CONTRIBUTORS,+--                    etc/CREDITS++source-repository       head+    type:               git+    location:           https://github.com/domdere/hs-geojson.git++source-repository       this+    type:               git+    location:           https://github.com/domdere/hs-geojson.git+    tag:                0.0.1++flag                    small_base+    description:        Choose the new, split-up base package.++library+    default-language:   Haskell2010++    build-depends:      base < 5 && >= 4+                    ,   json++    ghc-options:        -Wall+                        -fno-warn-unused-imports+                        -fno-warn-unused-binds+                        -fno-warn-unused-do-bind+                        -fno-warn-type-defaults++    hs-source-dirs:     src++    exposed-modules:    Text.GeoJSON+                    ,   Data.Geospatial++test-suite              doctests+    type:+                        exitcode-stdio-1.0++    main-is:+                        Main.hs++    default-language:+                        Haskell2010++    build-depends:+                        base < 5 && >= 3,+                        doctest >= 0.9.7,+                        filepath >= 1.3,+                        directory >= 1.1,+                        QuickCheck >= 2.0++    ghc-options:+                        -Wall+                        -threaded++    hs-source-dirs:+                        doctests
+ src/Data/Geospatial.hs view
@@ -0,0 +1,147 @@+-- Refer to the GeoJSON Spec http://www.geojson.org/geojson-spec.html++module Data.Geospatial  (+                        --  Types+                            Latitude+                        ,   Longitude+                        ,   Easting+                        ,   Northing+                        ,   Altitude+                        ,   GeoPositionWithoutCRS+                        ,   GeoPosition(..)+                        ,   GeoPoint(..)+                        ,   GeoMultiPoint(..)+                        ,   GeoPolygon(..)+                        ,   GeoMultiPolygon(..)+                        ,   GeoLine(..)+                        ,   GeoMultiLine(..)+                        ,   GeoPolyLine(..)+                        ,   GeospatialGeometry(..)+                        ,   Name+                        ,   Code+                        ,   Href+                        ,   FormatString+                        ,   ProjectionType+                        ,   CRSObject(..)+                        ,   FeatureID+                        ,   GeoProperty(..)+                        ,   GeoPropertyObject+                        ,   BoundingBoxWithoutCRS+                        ,   GeoFeature(..)+                        ,   GeoFeatureCollection(..)+                        --  Functions+                        ,   stripCRSFromPosition+                        ,   defaultCRS+                        ) where++import Text.JSON++type Latitude = Float+type Longitude = Float+type Easting = Float+type Northing = Float+type Altitude = Float++-- | ("WithoutCRS" is a catch all for indeterminate CRSs and for expression of positions+-- before a CRS has been determined++type GeoPositionWithoutCRS = [Float]++-- | see Section 2.1.1 "Position" in the GeoJSON Spec,+-- I make the assumption here that the only position types we will use will+-- involve easting/northing (+/- Altitude) or lon/lat (+/- Altitude)+data GeoPosition =+        LonLat Longitude Latitude+    |   LonLatAlt Longitude Latitude Altitude+    |   EastingNorthing Easting Northing+    |   EastingNorthingAlt Easting Northing Altitude++-- | the GeoPosition is a bit special in that when you convert it to GeoJSON,+-- it will lose the CRS info attached to it and cannot be read back in+-- from the GeoJSON.  Hence it is ineligible for the JSON type class,+-- so this function will strip it down to a GeoPositionWithoutCRS, which is eligible+stripCRSFromPosition :: GeoPosition -> GeoPositionWithoutCRS+stripCRSFromPosition (LonLat lon lat)                           = [lon, lat]+stripCRSFromPosition (LonLatAlt lon lat alt)                    = [lon, lat, alt]+stripCRSFromPosition (EastingNorthing easting northing)         = [easting, northing]+stripCRSFromPosition (EastingNorthingAlt easting northing alt)  = [easting, northing, alt]++-- These are all using newtype so that I can override their JSON instances..++newtype GeoPoint        = GeoPoint GeoPositionWithoutCRS deriving (Show, Eq)+newtype GeoMultiPoint   = GeoMultiPoint [GeoPoint] deriving (Show, Eq)+newtype GeoPolygon      = GeoPolygon [GeoPositionWithoutCRS] deriving (Show, Eq)+newtype GeoMultiPolygon = GeoMultiPolygon [GeoPolygon] deriving (Show, Eq)+newtype GeoLine         = GeoLine [GeoPositionWithoutCRS] deriving (Show, Eq)+newtype GeoMultiLine    = GeoMultiLine [GeoLine] deriving (Show, Eq)++data GeoPolyLine = Poly GeoPolygon | LineString GeoLine++-- | See section 2.1 "Geometry Objects" in the GeoJSON Spec.+data GeospatialGeometry =+        NoGeometry+    |   Point GeoPoint+    |   MultiPoint GeoMultiPoint+    |   Polygon GeoPolygon+    |   MultiPolygon GeoMultiPolygon+    |   Line GeoLine+    |   MultiLine GeoMultiLine+    |   Collection [GeospatialGeometry] deriving (Show, Eq)++type Name = String+type Code = Int+type Href = String+type FormatString = String+type ProjectionType = String++-- | See Section 3 "Coordinate Reference System Objects" in the GeoJSON Spec+-- "NoCRS" is required because no 'crs' attribute in a GeoJSON feature is NOT the same thing as+-- a null 'crs' attribute. no 'crs' value implies the default CRS, while a null CRS means+-- you cannot assume a CRS, null will mapped to NoCRS while a non-existent attribute will+-- be mapped to a Nothing Maybe value+data CRSObject =+        NoCRS+    |   NamedCRS Name+    |   EPSG Code+    |   LinkedCRS Href FormatString  deriving (Show, Eq)++-- | The default CRS according to Section 3 "Coordinate Reference System Objects" is WGS84 which I believe,+-- from http://spatialreference.org/ref/epsg/4326/ -> [JSON](http://spatialreference.org/ref/epsg/4326/json/ "WGS84 in JSON")+-- is represented thus:+defaultCRS :: CRSObject+defaultCRS = EPSG 4326++type FeatureID = String++data GeoProperty =+        StringProperty String+    |   FloatProperty Float+    |   DoubleProperty Double+    |   IntProperty Int+    |   PropertyObject GeoPropertyObject deriving (Show, Eq)++-- | According to section 2.2 "Feature Objects" of the GeoJSON spec, the "properties" object+-- can be any JSON object, or a null value, we may as well just make it a Type Alias+-- for JSValue, even though it has a wider scope (Integer, Rational, String etc...)+-- to take advantage of the solid JSON code out there to handle generic objects and the null value+type GeoPropertyObject = JSValue++-- | See Section 4 "Bounding Boxes" of the GeoJSON spec,+-- The length of the list/array must be 2*n where n is the dimensionality of the position type for the CRS+-- with min values first followed by the max values, wich both the min/max sets following the same axis order as the CRS,+-- e.g for WGS84: minLongitude, minLatitude, maxLongitude, maxLatitude+-- The spec mentions that it can be part of a geometry object too but doesnt give an example,+-- This implementation will ignore bboxes on Geometry objects, they can be added if required.+type BoundingBoxWithoutCRS = [Float]++-- | See Section 2.2 "Feature Objects" of the GeoJSON spec.+data GeoFeature = GeoFeature {+    bbox :: Maybe BoundingBoxWithoutCRS,+    geometry :: GeospatialGeometry,+    properties :: GeoPropertyObject,+    featureId :: Maybe FeatureID } deriving (Show, Eq)++-- | See Section 2.3 "Feature Collection Objects" of the GeoJSON spec+data GeoFeatureCollection = GeoFeatureCollection (Maybe BoundingBoxWithoutCRS) [GeoFeature] deriving (Show, Eq)++
+ src/Text/GeoJSON.hs view
@@ -0,0 +1,405 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- Only export the JSON instances+module Text.GeoJSON () where++import Control.Applicative+import Text.JSON++import Data.Geospatial++-- $setup+-- Test Bounding Box Data+-- >>> let lshapedPolyVertices = [[120.0, -15.0], [127.0, -15.0], [127.0, -25.0], [124.0, -25.0], [124.0, -18.0], [120.0, -18.0]] :: [GeoPositionWithoutCRS]+-- >>> let emptyVertices = [] :: [GeoPositionWithoutCRS]+-- >>> let testLatLonBBox = [-32, 147.5, -29.5, 151.0] :: BoundingBoxWithoutCRS+-- >>> let testLatLonBBoxJSON = "[-32,147.5,-29.5,151]"+-- >>> let testEmptyBBox = [] :: BoundingBoxWithoutCRS+-- >>> let testEmptyBBoxJSON = "[]"+--+-- End Test Bounding Box Data+-- Test Geometry Data+-- Polys+-- >>> let lShapedPolyJSON = "{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}"+--+-- Upside down L Shaped Poly+--+-- (120, -15)                (127, -15)+-- *---------------------------*+-- |                           |+-- |                           |+-- |             (124, -18)    |+-- *---------------*           |+-- (120, -18)      |           |+--                 |           |+--                 |           |+--                 |           |+--                 |           |+--                 |           |+--                 |           |+--                 *-----------*+--               (124, -25)  (127, -25)+--+-- >>> let lShapedGeoPoly = GeoPolygon lshapedPolyVertices+-- >>> let lShapedPoly = Polygon lShapedGeoPoly+-- >>> let emptyPolyJSON = "{\"type\":\"Polygon\",\"coordinates\":[]}"+-- >>> let emptyGeoPoly = GeoPolygon emptyVertices+-- >>> let emptyPoly = Polygon emptyGeoPoly+--+-- Multi Polys+-- >>> let emptyMultiPolyJSON = "{\"type\":\"MultiPolygon\",\"coordinates\":[]}"+-- >>> let emptyMultiGeoPoly = GeoMultiPolygon []+-- >>> let emptyMultiPoly = MultiPolygon emptyMultiGeoPoly+-- >>> let singlePolyMultiPolyJSON = "{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]}"+-- >>> let singlePolyGeoMultiPoly = GeoMultiPolygon [lShapedGeoPoly]+-- >>> let singlePolyMultiPoly = MultiPolygon singlePolyGeoMultiPoly+-- >>> let multiPolyJSON = "{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]}"+-- >>> let geoMultiPoly = GeoMultiPolygon [lShapedGeoPoly, emptyGeoPoly]+-- >>> let multiPoly = MultiPolygon geoMultiPoly+--+-- Line Data+-- >>> let lShapedLineJSON = "{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}"+-- >>> let lShapedGeoLine = GeoLine lshapedPolyVertices+-- >>> let lShapedLine = Line lShapedGeoLine+-- >>> let emptyLineJSON = "{\"type\":\"Line\",\"coordinates\":[]}"+-- >>> let emptyGeoLine = GeoLine emptyVertices+-- >>> let emptyLine = Line emptyGeoLine+--+-- Multi Lines+-- >>> let emptyMultiLineJSON = "{\"type\":\"MultiLine\",\"coordinates\":[]}"+-- >>> let emptyMultiGeoLine = GeoMultiLine []+-- >>> let emptyMultiLine = MultiLine emptyMultiGeoLine+-- >>> let singleLineMultiLineJSON = "{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]}"+-- >>> let singleLineGeoMultiLine = GeoMultiLine [lShapedGeoLine]+-- >>> let singleLineMultiLine = MultiLine singleLineGeoMultiLine+-- >>> let multiLineJSON = "{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]}"+-- >>> let geoMultiLine = GeoMultiLine [lShapedGeoLine, emptyGeoLine]+-- >>> let multiLine = MultiLine geoMultiLine+-- >>> let emptyCollectionJSON = "{\"type\":\"GeometryCollection\",\"geometries\":[]}"+-- >>> let emptyCollection = Collection []+-- >>> let bigassCollectionJSON = "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]}"+-- >>> let bigassCollection = Collection [singleLineMultiLine, emptyMultiLine, emptyLine, multiLine, lShapedLine, multiPoly, singlePolyMultiPoly, lShapedPoly, emptyMultiPoly, lShapedPoly]+--+-- End Test Geometry Data+--+-- Test CRS Data+-- >>> let testLinkCRSJSON = "{\"type\":\"link\",\"properties\":{\"href\":\"www.google.com.au\",\"type\":\"proj4\"}}"+-- >>> let testLinkCRS = LinkedCRS "www.google.com.au" "proj4"+-- >>> let testEPSGJSON = "{\"type\":\"epsg\",\"properties\":{\"code\":4326}}"+-- >>> let testEPSG = EPSG 4326+-- >>> let testNamedCRSJSON = "{\"type\":\"name\",\"properties\":{\"name\":\"urn:ogc:def:crs:OGC:1.3:CRS84\"}}"+-- >>> let testNamedCRS = NamedCRS "urn:ogc:def:crs:OGC:1.3:CRS84"+--+-- Test Properties+-- >>> let testProperties = makeObj [("depth", showJSON (5 :: Int)), ("comment", showJSON "Bore run over by dump truck")]+--+-- Test Features+-- >>> let bigFeatureJSON = "{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"}"+-- >>> let bigFeature = GeoFeature (Just testLatLonBBox) bigassCollection testProperties (Just "GW001")+-- >>> let featureWithNoPropertiesJSON = "{\"type\":\"Feature\",\"properties\":null,\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"}"+-- >>> let featureWithNoProperties = let GeoFeature bbox geometry _ featureId = bigFeature in GeoFeature bbox geometry JSNull featureId+-- >>> let featureWithNoGeometryJSON = "{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":null,\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"}"+-- >>> let featureWithNoGeometry = let GeoFeature bbox _ props featureId = bigFeature in GeoFeature bbox NoGeometry props featureId+-- >>> let featureWithNoIdJSON = "{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151]}"+-- >>> let featureWithNoId = let GeoFeature bbox geometry props _ = bigFeature in GeoFeature bbox geometry props Nothing+-- >>> let featureWithNoBBoxJSON = "{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"id\":\"GW001\"}"+-- >>> let featureWithNoBBox = let GeoFeature _ geometry props featureId = bigFeature in GeoFeature Nothing geometry props featureId+-- >>> let bigAssFeatureCollectionJSON = "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":null,\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151]},{\"type\":\"Feature\",\"properties\":null,\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"}],\"bbox\":[-32,147.5,-29.5,151]}"+-- >>> let bigAssFeatureCollection = GeoFeatureCollection (Just testLatLonBBox) [featureWithNoBBox, featureWithNoGeometry, featureWithNoBBox, featureWithNoId, featureWithNoProperties, bigFeature]+-- >>> let bigAssFeatureCollectionWithNoBBoxJSON = "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":null,\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151]},{\"type\":\"Feature\",\"properties\":null,\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"},{\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"},\"geometry\":{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"MultiLine\",\"coordinates\":[]},{\"type\":\"Line\",\"coordinates\":[]},{\"type\":\"MultiLine\",\"coordinates\":[{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Line\",\"coordinates\":[]}]},{\"type\":\"Line\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"Polygon\",\"coordinates\":[]}]},{\"type\":\"MultiPolygon\",\"coordinates\":[{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]},{\"type\":\"MultiPolygon\",\"coordinates\":[]},{\"type\":\"Polygon\",\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]]}]},\"bbox\":[-32,147.5,-29.5,151],\"id\":\"GW001\"}]}"+-- >>> let bigAssFeatureCollectionWithNoBBox = let GeoFeatureCollection _ features = bigAssFeatureCollection in GeoFeatureCollection Nothing features+-- >>> let emptyFeatureCollectionJSON = "{\"type\":\"FeatureCollection\",\"features\":[]}"+-- >>> let emptyFeatureCollection = GeoFeatureCollection Nothing []+-- >>> let emptyFeatureCollectionWithBBoxJSON = "{\"type\":\"FeatureCollection\",\"features\":[],\"bbox\":[-32,147.5,-29.5,151]}"+-- >>> let emptyFeatureCollectionWithBBox = GeoFeatureCollection (Just testLatLonBBox) []+--++++-- helper functions:++-- | A generic function that can be used to read in the GeoJSON for:+-- GeoPoint, GeoMultiPoint, GeoLine, GeoMultiLine, GeoPolygon and GeoMultiPolygon+-- Takes in a String for the GeoJSON geometry type, the type constructor+-- for the datatype and the JSON object containing both the 'type' val and the 'coordinates' val+readGeometryGeoJSON :: (JSON a, JSON b) => String -> (a -> b) -> JSValue -> Result b +readGeometryGeoJSON geomTypeString geomType json = do+    geopointObj <- readJSON json+    geometryType <- valFromObj "type" geopointObj+    if geometryType == geomTypeString+        then+            geomType <$> valFromObj "coordinates" geopointObj+        else+            fail $ "Invalid Geometry Type: " ++ geometryType++-- | The inverse to the above, you just give it the type string and the value for the coordinates+-- and it will create the JSON object+makeGeometryGeoJSON :: (JSON a) => String -> a -> JSValue+makeGeometryGeoJSON typeString coordinates = +    makeObj [("type", showJSON typeString), ("coordinates", showJSON coordinates)]++geometryFromJSON :: String -> JSValue -> Result GeospatialGeometry+geometryFromJSON "Point" obj                                = Point <$> readJSON obj+geometryFromJSON "MultiPoint" obj                           = MultiPoint <$> readJSON obj+geometryFromJSON "Polygon" obj                              = Polygon <$> readJSON obj+geometryFromJSON "MultiPolygon" obj                         = MultiPolygon <$> readJSON obj+geometryFromJSON "Line" obj                                 = Line <$> readJSON obj+geometryFromJSON "MultiLine" obj                            = MultiLine <$> readJSON obj+geometryFromJSON "GeometryCollection" (JSObject jsonObj)    = Collection <$> (valFromObj "geometries" jsonObj >>= readJSON)+geometryFromJSON "GeometryCollection" _                     = Error "Invalid value type for 'geometries' attribute.."+geometryFromJSON typeString _                               = Error $ "Invalid Geometry Type: " ++ typeString++crsPropertyFromObj :: (JSON a) => String -> JSObject JSValue -> Result a+crsPropertyFromObj name obj = do+    props <- valFromObj "properties" obj+    valFromObj name props++crsObjectFromJSON :: String -> JSObject JSValue -> Result CRSObject+crsObjectFromJSON "name" obj    = NamedCRS <$> crsPropertyFromObj "name" obj+crsObjectFromJSON "epsg" obj    = EPSG <$> crsPropertyFromObj "code" obj+crsObjectFromJSON "link" obj    = LinkedCRS <$> crsPropertyFromObj "href" obj <*> crsPropertyFromObj "type" obj+crsObjectFromJSON typeString _  = Error $ "Invalid or unimplemented CRS Object Type: " ++ typeString++-- | get an optional value out of a JSON object:+optValFromObj :: (JSON a) => String -> JSObject JSValue -> Result (Maybe a)+optValFromObj attribute object = resultToMaybe $ valFromObj attribute object+    where+        resultToMaybe (Ok x)    = Ok $ Just x+        resultToMaybe (Error _) = Ok Nothing++-- | The other way around, given an optional value, will return the attributes that+-- should be added to the makeObj input+optAttributes :: (JSON a) => String -> Maybe a -> [(String, JSValue)]+optAttributes _ Nothing     = []+optAttributes name (Just x) = [(name, showJSON x)]+-- end helper functions++-- Conversion of Geospatial data types into GeoJSON,+-- We do this by inducting all the types from Data.Geospatial.Types into+-- the JSON typeclass++instance JSON GeoPoint where+    readJSON = readGeometryGeoJSON "Point" GeoPoint++    showJSON (GeoPoint point) = makeGeometryGeoJSON "Point" point++instance JSON GeoMultiPoint where+    readJSON = readGeometryGeoJSON "MultiPoint" GeoMultiPoint++    showJSON (GeoMultiPoint points) = makeGeometryGeoJSON "MultiPoint" points++instance JSON GeoPolygon where+    readJSON = readGeometryGeoJSON "Polygon" GeoPolygon++    showJSON (GeoPolygon vertices) = makeGeometryGeoJSON "Polygon" vertices++instance JSON GeoMultiPolygon where+    readJSON = readGeometryGeoJSON "MultiPolygon" GeoMultiPolygon++    showJSON (GeoMultiPolygon polys) = makeGeometryGeoJSON "MultiPolygon" polys++instance JSON GeoLine where+    readJSON = readGeometryGeoJSON "Line" GeoLine++    showJSON (GeoLine line) = makeGeometryGeoJSON "Line" line++instance JSON GeoMultiLine where+    readJSON = readGeometryGeoJSON "MultiLine" GeoMultiLine++    showJSON (GeoMultiLine lines') = makeGeometryGeoJSON "MultiLine" lines'++-- |+-- encodes and decodes Geometry Objects to and from GeoJSON+-- (refer to source to see the values for the test values)+--+-- >>> encode NoGeometry+-- "null"+--+-- >>> decode "null" :: Result GeospatialGeometry+-- Ok NoGeometry+--+-- >>> encode lShapedPoly == lShapedPolyJSON+-- True+--+-- >>> decode lShapedPolyJSON == Ok lShapedPoly+-- True+--+-- >>> encode emptyPoly == emptyPolyJSON+-- True+--+-- >>> decode emptyPolyJSON == Ok emptyPoly+-- True+--+-- >>> encode emptyMultiPoly == emptyMultiPolyJSON+-- True+--+-- >>> decode emptyMultiPolyJSON == Ok emptyMultiPoly+-- True+--+-- >>> encode singleLineMultiLine == singleLineMultiLineJSON+-- True+--+-- >>> decode singleLineMultiLineJSON == Ok singleLineMultiLine+-- True+--+-- >>> encode multiLine == multiLineJSON+-- True+--+-- >>> decode multiLineJSON == Ok multiLine+-- True+--+-- >>> encode emptyCollection == emptyCollectionJSON+-- True+--+-- >>> decode emptyCollectionJSON == Ok emptyCollection+-- True+--+-- >>> encode bigassCollection == bigassCollectionJSON+-- True+--+-- >>> decode bigassCollectionJSON == Ok bigassCollection+-- True+--+instance JSON GeospatialGeometry where+    readJSON JSNull = Ok NoGeometry+    readJSON json   = do+        geometryObj <- readJSON json+        geometryType <- valFromObj "type" geometryObj+        geometryFromJSON geometryType (JSObject geometryObj)++    showJSON (NoGeometry)               = JSNull+    showJSON (Point point)              = showJSON point+    showJSON (MultiPoint points)        = showJSON points+    showJSON (Polygon vertices)         = showJSON vertices+    showJSON (MultiPolygon vertices)    = showJSON vertices+    showJSON (Line vertices)            = showJSON vertices+    showJSON (MultiLine vertices)       = showJSON vertices+    showJSON (Collection geometries)    = makeObj [("type", showJSON "GeometryCollection"), ("geometries", showJSON geometries)]++-- |+-- encode and decodes CRS Objects to and from GeoJSON+--+-- >>> encode testLinkCRS == testLinkCRSJSON+-- True+--+-- >>> decode testLinkCRSJSON == Ok testLinkCRS+-- True+--+-- >>> encode testNamedCRS == testNamedCRSJSON+-- True+--+-- >>> decode testNamedCRSJSON == Ok testNamedCRS+-- True+--+-- >>> encode testEPSG == testEPSGJSON+-- True+--+-- >>> decode testEPSGJSON == Ok testEPSG+-- True+--+-- >>> encode NoCRS+-- "null"+--+-- >>> decode "null" == Ok NoCRS+-- True+--+instance JSON CRSObject where+    readJSON JSNull = Ok NoCRS+    readJSON json   = do+        crsObject <- readJSON json+        crsType <- valFromObj "type" crsObject+        crsObjectFromJSON crsType crsObject++    showJSON (NamedCRS name)                    = makeObj [("type", showJSON "name"), ("properties", showJSON (makeObj [("name", showJSON name)]))]+    showJSON (EPSG code)                        = makeObj [("type", showJSON "epsg"), ("properties", showJSON (makeObj [("code", showJSON code)]))]+    showJSON (LinkedCRS href format )           = makeObj [("type", showJSON "link"), ("properties", showJSON (makeObj [("href", showJSON href), ("type", showJSON format)]))]+    showJSON NoCRS                              = JSNull++-- | encodes and decodes Feature objects to and from GeoJSON+--+-- >>> encode bigFeature == bigFeatureJSON+-- True+--+-- >>> decode bigFeatureJSON == Ok bigFeature+-- True+--+-- >>> encode featureWithNoProperties == featureWithNoPropertiesJSON+-- True+--+-- >>> decode featureWithNoPropertiesJSON == Ok featureWithNoProperties+-- True+--+-- >>> encode featureWithNoId == featureWithNoIdJSON+-- True+--+-- >>> decode featureWithNoIdJSON == Ok featureWithNoId+-- True+--+-- >>> encode featureWithNoBBox == featureWithNoBBoxJSON+-- True+--+-- >>> decode featureWithNoBBoxJSON == Ok featureWithNoBBox+-- True+--+-- >>> encode featureWithNoGeometry == featureWithNoGeometryJSON+-- True+--+-- >>> decode featureWithNoGeometryJSON == Ok featureWithNoGeometry+-- True+--+instance JSON GeoFeature where+    readJSON json = do+        obj <- readJSON json+        objType <- valFromObj "type" obj+        if objType /= "Feature"+            then+                fail $ "Invalid GeoJSON type for a Feature: " ++ objType+            else+                GeoFeature <$> optValFromObj "bbox" obj+                    <*> valFromObj "geometry" obj+                    <*> valFromObj "properties" obj+                    <*> optValFromObj "id" obj++    showJSON (GeoFeature bbox' geom props featureId') = makeObj $ baseAttributes ++ optAttributes "bbox" bbox' ++ optAttributes "id" featureId'+        where+            baseAttributes = [("type", showJSON "Feature"), ("properties", showJSON props), ("geometry", showJSON geom)]++-- | Encodes and Decodes FeatureCollection objects to and from GeoJSON+--+-- >>> encode bigAssFeatureCollection == bigAssFeatureCollectionJSON+-- True+--+-- >>> decode bigAssFeatureCollectionJSON == Ok bigAssFeatureCollection+-- True+--+-- >>> encode bigAssFeatureCollectionWithNoBBox == bigAssFeatureCollectionWithNoBBoxJSON+-- True+--+-- >>> decode bigAssFeatureCollectionWithNoBBoxJSON == Ok bigAssFeatureCollectionWithNoBBox+-- True+--+-- >>> encode emptyFeatureCollectionWithBBox == emptyFeatureCollectionWithBBoxJSON+-- True+--+-- >>> decode emptyFeatureCollectionWithBBoxJSON == Ok emptyFeatureCollectionWithBBox+-- True+--+-- >>> encode emptyFeatureCollection == emptyFeatureCollectionJSON+-- True+--+-- >>> decode emptyFeatureCollectionJSON == Ok emptyFeatureCollection+-- True+--+instance JSON GeoFeatureCollection where+    readJSON json = do+        obj <- readJSON json+        objType <- valFromObj "type" obj+        if objType /= "FeatureCollection"+            then+                fail $ "Invalid GeoJSON type for a Feature Collection: " ++ objType+            else+                GeoFeatureCollection <$> optValFromObj "bbox" obj <*> valFromObj "features" obj++    showJSON (GeoFeatureCollection bbox' features) = makeObj $ baseAttributes ++ optAttributes "bbox" bbox'+        where+            baseAttributes              = [("type", showJSON "FeatureCollection"), ("features", showJSON features)]+