packages feed

geojson 0.0.2 → 1.0.0

raw patch · 21 files changed

+1370/−574 lines, 21 filesdep +aesondep +bytestringdep +lensdep −json

Dependencies added: aeson, bytestring, lens, text

Dependencies removed: json

Files

+ README.md view
@@ -0,0 +1,55 @@+# geojson [![Build Status](https://travis-ci.org/domdere/hs-geojson.png?branch=master)](https://travis-ci.org/domdere/hs-geojson) [![Hackage](https://budueba.com/hackage/geojson)](https://hackage.haskell.org/package/geojson)++A thin GeoJSON Layer above the `aeson` library++## Building the project++Install the dependencies with:++    cabal install --dependencies-only++Optionally add `--enable-tests` if you intend to run the unit tests++The project must be "configured" at least once everytime `geojson.cabal` changes, this can be done with:++    cabal configure++If you wish to run the unit tests you will have to run:++    cabal configure --enable-tests++Then finally build it with:++    cabal build++See `cabal build --help` for more build options.++## Running Unit Tests++**After** running `cabal build`, you can run the unit tests with the command:++    cabal test++## Adding Unit tests++Unit tests are written with [**doctest**] [doctest-github], for instructions on how to add unit tests+see the **doctest** [**User Guide**] [doctest-userguide].++Currently only files in the `src/` directory are searched for tests, it is assumed that the code in `main/`+is a thin layer of code that uses modules from `src/`.++## Development: Cabal Dependency Hell?++Cabal's great, but when you are developing a few different projects with their own dependency chains, sometimes installing all your libraries to the same place causes problems,++Consider trying [`cabal-dev`] [cabal-dev]. In terms of using it, all thats required is replacing `cabal` with `cabal-dev` in all the above command lines.++It will download and install all the dependencies for your project and install them in a `cabal-dev/` directory in your project directory, and they will only be used for this project.++Those with newer versions of `cabal` (`>= 1.18` I think) can skip `cabal-dev` and instead run `cabal sandbox init`, and just start runnign the above instructions+as is from `cabal install --only-dependencies`++[doctest-github]: https://github.com/sol/doctest-haskell "sol/doctest-haskell on GitHub.com"+[doctest-userguide]: https://github.com/sol/doctest-haskell/blob/master/README.markdown#usage "doctest Usage Guide"+[cabal-dev]: https://github.com/creswick/cabal-dev "creswick/cabal-dev on GitHub.com"+
doctests/Main.hs view
@@ -8,6 +8,11 @@ import System.FilePath import Test.DocTest +extensions :: [String]+extensions =+    [   "OverloadedStrings"+    ]+ docopts :: [FilePath] docopts =     [   "-isrc"@@ -15,7 +20,7 @@     ,   "-optP-include"     ,   "-optPdist/build/autogen/cabal_macros.h"     ,   "-hide-all-packages"-    ] ++ map ("-package=" ++) deps ++ opts+    ] ++ map ("-package=" ++) deps ++ opts ++ map ("-X" ++) extensions  -- the list of all file paths to search for source files sourceDirs :: [FilePath]
+ etc/CHANGELOG.md view
@@ -0,0 +1,9 @@+# 1.0.x++## 0.0.2 -> 2.0.0++-   Migrated from `json` to `aeson`, all `JSON` instances were removed and replaced with `ToJSON` and `FromJSON` instances+-   `GeoFeature` and `GeoFeatureCollection` are now of kind `* -> *`, parameterised on the Property type, `GeoFeature a` and `GeoFeatureCollection a` are in `ToJSON` and/or `FromJSON` if `a` is in `ToJSON` and/or `FromJSON` respectively.+-   Lenses and prisms have been generated for each of the types.+-   Orphan Instances were removed.  Orphan Instances are bad.  Breaking type class coherency had some rather annoying consequences, you only need to import `Data.Geospatial` now and the `ToJSON` and `FromJSON` instances will come with it.  I wanted to split up the files and thought seperating the instances from the declarations was a good idea but it wasnt.  Instead I split the data types into seperate modules and kept the instances with their respective data type declarations.+
− etc/LICENCE
+ etc/LICENCE.md view
@@ -0,0 +1,20 @@+# BSD 3-Clause License++Copyright (c) 2014, Dom De Re+All rights reserved.++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
geojson.cabal view
@@ -1,12 +1,12 @@ name:               geojson-version:            0.0.2-license:            MIT-license-file:       etc/LICENCE+version:            1.0.0+license:            BSD3+license-file:       etc/LICENCE.md author:             Dom De Re maintainer:         Dom De Re-copyright:          Copyright (C) 2010-2013+copyright:          Copyright (C) 2013-2014 synopsis:           A thin GeoJSON Layer above the json library-category:           Text+category:           Data 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:@@ -20,10 +20,9 @@ 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+tested-with:        GHC == 7.6.3+extra-source-files: README.md,+                    etc/CHANGELOG.md  source-repository       head     type:               git@@ -41,7 +40,9 @@     default-language:   Haskell2010      build-depends:      base < 5 && >= 4-                    ,   json+                    ,   aeson       == 0.7.*+                    ,   lens        == 4.1.*+                    ,   text        == 1.1.*      ghc-options:        -Wall                         -fno-warn-unused-imports@@ -51,9 +52,22 @@      hs-source-dirs:     src -    exposed-modules:    Text.GeoJSON-                    ,   Data.Geospatial+    exposed-modules:    Data.Geospatial +    other-modules:      Data.Geospatial.BasicTypes+                    ,   Data.Geospatial.CRS+                    ,   Data.Geospatial.GeoFeature+                    ,   Data.Geospatial.GeoFeatureCollection+                    ,   Data.Geospatial.Geometry+                    ,   Data.Geospatial.Geometry.GeoMultiLine+                    ,   Data.Geospatial.Geometry.GeoMultiPoint+                    ,   Data.Geospatial.Geometry.GeoMultiPolygon+                    ,   Data.Geospatial.Geometry.GeoLine+                    ,   Data.Geospatial.Geometry.GeoPoint+                    ,   Data.Geospatial.Geometry.GeoPolygon+                    ,   Data.Geospatial.Geometry.Aeson+                    ,   Data.Geospatial.GeoPosition+ test-suite              doctests     type:                         exitcode-stdio-1.0@@ -64,12 +78,12 @@     default-language:                         Haskell2010 -    build-depends:-                        base < 5 && >= 3,-                        doctest >= 0.9.7,-                        filepath >= 1.3,-                        directory >= 1.1,-                        QuickCheck >= 2.0+    build-depends:      base < 5 && >= 3+                    ,   bytestring          == 0.10.*+                    ,   doctest             >= 0.9.7+                    ,   filepath            >= 1.3+                    ,   directory           >= 1.1+                    ,   QuickCheck          >= 2.0      ghc-options:                         -Wall
src/Data/Geospatial.hs view
@@ -1,147 +1,77 @@+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+-- -- 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)-+--+-------------------------------------------------------------------+module Data.Geospatial (+    -- * Types+        Latitude+    ,   Longitude+    ,   Easting+    ,   Northing+    ,   Altitude+    ,   GeoPositionWithoutCRS+    ,   GeoPosition(..)+    ,   GeoPoint(..)+    ,   GeoMultiPoint(..)+    ,   GeoPolygon(..)+    ,   GeoMultiPolygon(..)+    ,   GeoLine(..)+    ,   GeoMultiLine(..)+    ,   GeospatialGeometry(..)+    ,   Name+    ,   Code+    ,   Href+    ,   FormatString+    ,   ProjectionType+    ,   CRSObject(..)+    ,   FeatureID+    ,   BoundingBoxWithoutCRS+    ,   GeoFeature(..)+    ,   GeoFeatureCollection(..)+    -- * Functions+    ,   stripCRSFromPosition+    ,   defaultCRS+    -- * Lenses+    -- ** Geometry Lenses+    ,   unGeoPoint+    ,   unGeoMultiPoint+    ,   unGeoPolygon+    ,   unGeoLine+    ,   unGeoMultiLine+    ,   unGeoMultiPolygon+    -- ** Feature Lenses+    ,   bbox+    ,   geometry+    ,   properties+    ,   featureId+    ,   boundingbox+    ,   geofeatures+    -- * Prisms+    -- ** Geometry+    ,   _NoGeometry+    ,   _Point+    ,   _MultiPoint+    ,   _Polygon+    ,   _MultiPolygon+    ,   _Line+    ,   _MultiLine+    ,   _Collection+    -- ** CRS+    ,   _NoCRS+    ,   _NamedCRS+    ,   _EPSG+    ,   _LinkedCRS+    ) where +import Data.Geospatial.BasicTypes+import Data.Geospatial.CRS+import Data.Geospatial.GeoFeature+import Data.Geospatial.GeoFeatureCollection+import Data.Geospatial.Geometry+import Data.Geospatial.GeoPosition
+ src/Data/Geospatial/BasicTypes.hs view
@@ -0,0 +1,60 @@+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.BasicTypes+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-- Basic types for GeoJSON representations.+-------------------------------------------------------------------+module Data.Geospatial.BasicTypes (+    -- * Coordinate types+        Latitude+    ,   Longitude+    ,   Easting+    ,   Northing+    ,   Altitude+    ,   GeoPositionWithoutCRS+    -- * CRS Reference types+    ,   Name+    ,   Code+    ,   Href+    ,   FormatString+    ,   ProjectionType+    -- * Feature Types+    ,   BoundingBoxWithoutCRS+    ,   FeatureID+    ) where++import Data.Text ( Text )++type Latitude = Float+type Longitude = Float+type Easting = Float+type Northing = Float+type Altitude = Float++-- | (`GeoPositionWithoutCRS` is a catch all for indeterminate CRSs and for expression of positions+-- before a CRS has been determined+--+type GeoPositionWithoutCRS = [Float]++type Name = Text+type Code = Int+type Href = Text+type FormatString = Text+type ProjectionType = Text++-- Feature Types++type FeatureID = Text++-- | 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]++
+ src/Data/Geospatial/CRS.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.CRS+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-- See Section 3 /Coordinate Reference System Objects/+-- in the GeoJSON Spec+--+-------------------------------------------------------------------+module Data.Geospatial.CRS (+    -- * Types+        CRSObject(..)+    -- * Functions+    ,   defaultCRS+    -- * Prisms+    ,   _NoCRS+    ,   _NamedCRS+    ,   _EPSG+    ,   _LinkedCRS+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry+import Data.Geospatial.GeoPosition++import Control.Applicative ( (<$>), (<*>) )+import Control.Lens ( makePrisms )+import Control.Monad ( mzero )+import Data.Aeson+    (   FromJSON(..)+    ,   ToJSON(..)+    ,   Value(..)+    ,   Object+    ,   (.:)+    ,   (.=)+    ,   object+    )+import Data.Aeson.Types ( Parser )+import Data.Text ( Text )++-- $setup+--+-- >>> import qualified Data.Aeson as A+-- >>> import qualified Data.ByteString.Lazy.Char8 as BS+--+-- 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"+--++-- | 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)++makePrisms ''CRSObject++-- | The default CRS according to Section 3 /Coordinate Reference System Objects/ is WGS84 which I believe,+-- from <http://spatialreference.org/ref/epsg/4326/> which translates to this in JSON: <http://spatialreference.org/ref/epsg/4326/json/>)+-- is represented thus:+defaultCRS :: CRSObject+defaultCRS = EPSG 4326++-- instances++-- |+-- encode and decodes CRS Objects to and from GeoJSON+--+-- >>> (A.decode . BS.pack) testLinkCRSJSON == Just testLinkCRS+-- True+--+-- >>> (A.decode . BS.pack) testNamedCRSJSON == Just testNamedCRS+-- True+--+-- >>> (A.decode . BS.pack) testEPSGJSON == Just testEPSG+-- True+--+-- Aeson doesnt decode "null" to `Null` unfortunately+--+-- (A.decode . BS.pack) "null" == Just NoCRS+-- True+--+instance FromJSON CRSObject where+    parseJSON Null = return NoCRS+    parseJSON (Object obj) = do+        crsType <- obj .: "type"+        crsObjectFromAeson crsType obj+    parseJSON _ = mzero++-- |+-- encode CRS Objects to GeoJSON+--+-- >>> (A.decode . A.encode) testLinkCRS == Just testLinkCRS+-- True+--+-- >>> (A.decode . A.encode) testNamedCRS == Just testNamedCRS+-- True+--+-- >>> (A.decode . A.encode) testEPSG == Just testEPSG+-- True+--+-- >>> A.encode NoCRS+-- "null"+--+instance ToJSON CRSObject where+    toJSON (NamedCRS name)          = object ["type" .= ("name" :: Text), "properties" .= object ["name" .= name]]+    toJSON (EPSG code)              = object ["type" .= ("epsg" :: Text), "properties" .= object ["code" .= code]]+    toJSON (LinkedCRS href format)  = object ["type" .= ("link" :: Text), "properties" .= object ["href" .= href, "type" .= format]]+    toJSON NoCRS                    = Null++-- helpers++crsPropertyFromAesonObj :: (FromJSON a) => Text -> Object -> Parser a+crsPropertyFromAesonObj name obj = do+    props <- obj .: "properties"+    props .: name++crsObjectFromAeson :: Text -> Object -> Parser CRSObject+crsObjectFromAeson "name" obj   = NamedCRS <$> crsPropertyFromAesonObj "name" obj+crsObjectFromAeson "epsg" obj   = EPSG <$> crsPropertyFromAesonObj "code" obj+crsObjectFromAeson "link" obj   = LinkedCRS <$> crsPropertyFromAesonObj "href" obj <*> crsPropertyFromAesonObj "type" obj+crsObjectFromAeson _ _          = mzero++
+ src/Data/Geospatial/GeoFeature.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.GeoFeature+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-- See Section 2.2 /Feature Objects/ of the GeoJSON spec.+-- Parameterised on the property type+--+-------------------------------------------------------------------+module Data.Geospatial.GeoFeature (+    -- * Types+        GeoFeature(..)+    -- * Lenses+    ,   bbox+    ,   geometry+    ,   properties+    ,   featureId+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry+import Data.Geospatial.Geometry.Aeson+import Data.Geospatial.GeoPosition++import Prelude ( Show, Eq(..), ($) )+import Control.Applicative ( (<$>), (<*>) )+import Control.Lens ( makeLenses )+import Control.Monad ( mzero )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object, (.:), (.:?), (.=), object )+import Data.List ( (++) )+import Data.Maybe ( Maybe )+import Data.Text ( Text )++-- $setup+--+-- >>> import qualified Data.Aeson as A+-- >>> import qualified Data.ByteString.Lazy.Char8 as BS+-- >>> import Data.Function ( (.) )+-- >>> import Data.Int ( Int )+-- >>> import Data.Maybe ( Maybe(..) )+-- >>> import Data.String+-- >>> import qualified Data.Text as T+--+-- >>> let decode' = A.decode . BS.pack; decode' :: (FromJSON a) => String -> Maybe a+--+-- 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 Properties+-- >>> let testProperties = object [(T.pack "depth") .= (5 :: Int), (T.pack "comment") .= (T.pack "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 Null 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+--++-- | See Section 2.2 /Feature Objects/ of the GeoJSON spec.+-- Parameterised on the property type+data GeoFeature a = GeoFeature {+    _bbox :: Maybe BoundingBoxWithoutCRS,+    _geometry :: GeospatialGeometry,+    _properties :: a,+    _featureId :: Maybe FeatureID } deriving (Show, Eq)++makeLenses ''GeoFeature++-- instances++-- | decodes Feature objects to and from GeoJSON+--+-- >>> decode' bigFeatureJSON == Just bigFeature+-- True+--+-- >>> decode' featureWithNoPropertiesJSON == Just featureWithNoProperties+-- True+--+-- >>> decode' featureWithNoIdJSON == Just featureWithNoId+-- True+--+-- >>> decode' featureWithNoBBoxJSON == Just featureWithNoBBox+-- True+--+-- >>> decode' featureWithNoGeometryJSON == Just featureWithNoGeometry+-- True+--+instance (FromJSON a) => FromJSON (GeoFeature a) where+--  parseJSON :: Value -> Parse a+    parseJSON (Object obj) = do+        objType <- obj .: ("type" :: Text)+        if objType /= ("Feature" :: Text)+            then+                mzero+            else+                GeoFeature+                    <$> obj .:? ("bbox" :: Text)+                    <*> obj .: ("geometry" :: Text)+                    <*> obj .: ("properties" :: Text)+                    <*> obj .:? ("id" :: Text)+    parseJSON _ = mzero++-- | encodes Feature objects to and from GeoJSON+--+-- >>> (A.decode . A.encode) bigFeature == Just bigFeature+-- True+--+-- >>> (A.decode . A.encode) featureWithNoProperties == Just featureWithNoProperties+-- True+--+-- >>> (A.decode . A.encode) featureWithNoId == Just featureWithNoId+-- True+--+-- >>> (A.decode . A.encode) featureWithNoBBox == Just featureWithNoBBox+-- True+--+-- >>> (A.decode . A.encode) featureWithNoGeometry == Just featureWithNoGeometry+-- True+--+instance (ToJSON a) => ToJSON (GeoFeature a) where+--  toJSON :: a -> Value+    toJSON (GeoFeature bbox' geom props featureId') = object $ baseAttributes ++ optAttributes "bbox" bbox' ++ optAttributes "id" featureId'+        where+            baseAttributes = ["type" .= ("Feature" :: Text), "properties" .= props, "geometry" .= geom]
+ src/Data/Geospatial/GeoFeatureCollection.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.GeoFeature+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-- See Section 2.3 /Feature Collection Objects/ of the GeoJSON spec+--+-------------------------------------------------------------------+module Data.Geospatial.GeoFeatureCollection (+    -- * Types+        GeoFeatureCollection(..)+    -- * Lenses+    ,   boundingbox+    ,   geofeatures+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.GeoFeature+import Data.Geospatial.Geometry+import Data.Geospatial.Geometry.Aeson+import Data.Geospatial.GeoPosition++import Prelude ( Show, Eq(..), ($) )+import Control.Applicative ( (<$>), (<*>) )+import Control.Lens ( makeLenses )+import Control.Monad ( mzero, return )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object, (.:), (.:?), (.=), object )+import Data.List ( (++) )+import Data.Maybe ( Maybe(..) )+import Data.Text ( Text )++-- $setup+--+-- >>> import qualified Data.Aeson as A+-- >>> import qualified Data.ByteString.Lazy.Char8 as BS+-- >>> import Data.Function ( (.) )+-- >>> import Data.Int ( Int )+-- >>> import Data.Maybe ( Maybe(..) )+-- >>> import Data.String+-- >>> import qualified Data.Text as T+--+-- >>> let decode' = A.decode . BS.pack; decode' :: (FromJSON a) => String -> Maybe a+--+-- 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 Properties+-- >>> let testProperties = object [(T.pack "depth") .= (5 :: Int), (T.pack "comment") .= (T.pack "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 Null 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 = "{\"bbox\":[-32,147.5,-29.5,151],\"features\":[{\"geometry\":{\"geometries\":[{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"GeometryCollection\"},\"id\":\"GW001\",\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"}},{\"bbox\":[-32,147.5,-29.5,151],\"geometry\":null,\"id\":\"GW001\",\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"}},{\"geometry\":{\"geometries\":[{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"GeometryCollection\"},\"id\":\"GW001\",\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"}},{\"bbox\":[-32,147.5,-29.5,151],\"geometry\":{\"geometries\":[{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"GeometryCollection\"},\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"}},{\"bbox\":[-32,147.5,-29.5,151],\"geometry\":{\"geometries\":[{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"GeometryCollection\"},\"id\":\"GW001\",\"type\":\"Feature\",\"properties\":null},{\"bbox\":[-32,147.5,-29.5,151],\"geometry\":{\"geometries\":[{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"MultiLine\"},{\"coordinates\":[],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[],\"type\":\"Line\"}],\"type\":\"MultiLine\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"},{\"coordinates\":[],\"type\":\"MultiPolygon\"},{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}],\"type\":\"GeometryCollection\"},\"id\":\"GW001\",\"type\":\"Feature\",\"properties\":{\"depth\":5,\"comment\":\"Bore run over by dump truck\"}}],\"type\":\"FeatureCollection\"}"+-- >>> 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 [] :: GeoFeatureCollection Value+-- >>> let emptyFeatureCollectionWithBBoxJSON = "{\"type\":\"FeatureCollection\",\"features\":[],\"bbox\":[-32,147.5,-29.5,151]}"+-- >>> let emptyFeatureCollectionWithBBox = GeoFeatureCollection (Just testLatLonBBox) []; emptyFeatureCollectionWithBBox :: GeoFeatureCollection Value+--++-- | See Section 2.3 /Feature Collection Objects/ of the GeoJSON spec+--+data GeoFeatureCollection a = GeoFeatureCollection+    {   _boundingbox :: Maybe BoundingBoxWithoutCRS+    ,   _geofeatures :: [GeoFeature a]+    } deriving (Show, Eq)++makeLenses ''GeoFeatureCollection++-- instances++-- | Decodes FeatureCollection objects to and from GeoJSON+--+-- >>> decode' bigAssFeatureCollectionJSON == Just bigAssFeatureCollection+-- True+--+-- >>> decode' bigAssFeatureCollectionWithNoBBoxJSON == Just bigAssFeatureCollectionWithNoBBox+-- True+--+-- >>> decode' emptyFeatureCollectionWithBBoxJSON == Just emptyFeatureCollectionWithBBox+-- True+--+-- >>> decode' emptyFeatureCollectionJSON == Just emptyFeatureCollection+-- True+--+instance (FromJSON a) => FromJSON (GeoFeatureCollection a) where+--  parseJSON :: Value -> Parse a+    parseJSON (Object obj) = do+        objType <- obj .: ("type" :: Text)+        if objType /= ("FeatureCollection" :: Text)+            then+                mzero+            else+                GeoFeatureCollection+                    <$> obj .:? ("bbox" :: Text)+                    <*> obj .: ("features" :: Text)+    parseJSON _ = mzero++-- | Encodes FeatureCollection objects to and from GeoJSON+--+-- >>> (A.decode . A.encode) bigAssFeatureCollection == Just bigAssFeatureCollection+-- True+--+-- >>> (A.decode . A.encode) bigAssFeatureCollectionWithNoBBox == Just bigAssFeatureCollectionWithNoBBox+-- True+--+-- >>> (A.decode . A.encode) emptyFeatureCollectionWithBBox == Just emptyFeatureCollectionWithBBox+-- True+--+-- >>> (A.decode . A.encode) emptyFeatureCollection == Just emptyFeatureCollection+-- True+--+instance (ToJSON a) => ToJSON (GeoFeatureCollection a) where+--  toJSON :: a -> Value+    toJSON (GeoFeatureCollection bbox' features) = object $ baseAttributes ++ optAttributes "bbox" bbox'+        where+            baseAttributes = ["type" .= ("FeatureCollection" :: Text), "features" .= features]
+ src/Data/Geospatial/GeoPosition.hs view
@@ -0,0 +1,38 @@+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.GeoPosition+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-- see Section 2.1.1 /Position/ in the GeoJSON Spec+-------------------------------------------------------------------+module Data.Geospatial.GeoPosition (+    -- * Type+        GeoPosition(..)+    -- * Functions+    ,   stripCRSFromPosition+    ) where++import Data.Geospatial.BasicTypes++-- | 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 or northing (+ve or -ve Altitude) or lon or lat (+ve or -ve 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 `FromJSON` 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]++
+ src/Data/Geospatial/Geometry.hs view
@@ -0,0 +1,242 @@+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.Geometry+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-- See section 2.1 "Geometry Objects" in the GeoJSON Spec.+--+-------------------------------------------------------------------+module Data.Geospatial.Geometry (+    -- * Types+        GeoPoint(..)+    ,   GeoMultiPoint(..)+    ,   GeoPolygon(..)+    ,   GeoMultiPolygon(..)+    ,   GeoLine(..)+    ,   GeoMultiLine(..)+    ,   GeospatialGeometry(..)+    -- * Lenses+    ,   unGeoPoint+    ,   unGeoMultiPoint+    ,   unGeoPolygon+    ,   unGeoMultiPolygon+    ,   unGeoLine+    ,   unGeoMultiLine+    -- * Prisms+    ,   _NoGeometry+    ,   _Point+    ,   _MultiPoint+    ,   _Polygon+    ,   _MultiPolygon+    ,   _Line+    ,   _MultiLine+    ,   _Collection+    ) where++import Data.Geospatial.Geometry.Aeson+import Data.Geospatial.Geometry.GeoLine+import Data.Geospatial.Geometry.GeoMultiLine+import Data.Geospatial.Geometry.GeoMultiPoint+import Data.Geospatial.Geometry.GeoMultiPolygon+import Data.Geospatial.Geometry.GeoPoint+import Data.Geospatial.Geometry.GeoPolygon++import Control.Applicative ( (<$>) )+import Control.Lens ( makePrisms )+import Control.Monad ( mzero )+import Data.Aeson+    (   FromJSON(..)+    ,   ToJSON(..)+    ,   Value(..)+    ,   Object+    ,   (.:)+    ,   (.=)+    ,   object+    )+import Data.Aeson.Types ( Parser )+import Data.Text ( Text )++-- $setup+-- >>> import Data.Geospatial.BasicTypes+--+-- >>> import qualified Data.Aeson as A+-- >>> import qualified Data.ByteString.Lazy.Char8 as BS+--+-- >>> 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 decode' = A.decode . BS.pack; decode' :: (FromJSON a) => String -> Maybe a+--+-- Test Geometry Data+-- Polys+-- >>> let lShapedPolyJSON = "{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Polygon\"}"+--+-- 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 = "{\"coordinates\":[],\"type\":\"Polygon\"}"+-- >>> let emptyGeoPoly = GeoPolygon emptyVertices+-- >>> let emptyPoly = Polygon emptyGeoPoly+--+-- Multi Polys+-- >>> let emptyMultiPolyJSON = "{\"coordinates\":[],\"type\":\"MultiPolygon\"}"+-- >>> 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 = "{\"coordinates\":[{\"coordinates\":[[120,-15],[127,-15],[127,-25],[124,-25],[124,-18],[120,-18]],\"type\":\"Line\"}],\"type\":\"MultiLine\"}"+-- >>> 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+--+--++-- | 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)++makePrisms ''GeospatialGeometry++geometryFromAeson :: String -> Value -> Parser GeospatialGeometry+geometryFromAeson "Point" obj                           = Point <$> parseJSON obj+geometryFromAeson "MultiPoint" obj                      = MultiPoint <$> parseJSON obj+geometryFromAeson "Polygon" obj                         = Polygon <$> parseJSON obj+geometryFromAeson "MultiPolygon" obj                    = MultiPolygon <$> parseJSON obj+geometryFromAeson "Line" obj                            = Line <$> parseJSON obj+geometryFromAeson "MultiLine" obj                       = MultiLine <$> parseJSON obj+geometryFromAeson "GeometryCollection" (Object jsonObj) = Collection <$> (jsonObj .: ("geometries" :: Text))+geometryFromAeson "GeometryCollection" _                = mzero+geometryFromAeson _ _                          = mzero+++-- |+-- encodes and Geometry Objects to GeoJSON+-- (refer to source to see the values for the test values)+--+-- >>> A.encode NoGeometry+-- "null"+--+-- >>> (A.decode . A.encode) lShapedPoly == Just lShapedPoly+-- True+--+-- >>> (A.decode . A.encode) emptyPoly == Just emptyPoly+-- True+--+-- >>> (A.decode . A.encode) emptyMultiPoly == Just emptyMultiPoly+-- True+--+-- >>> (A.decode . A.encode) singleLineMultiLine == Just singleLineMultiLine+-- True+--+-- >>> (A.decode . A.encode) multiLine == Just multiLine+-- True+--+-- >>> (A.decode . A.encode) emptyCollection == Just emptyCollection+-- True+--+-- >>> (A.decode . A.encode) bigassCollection == Just bigassCollection+-- True+--+instance ToJSON GeospatialGeometry where+--  toJSON :: a -> Value+    toJSON NoGeometry               = Null+    toJSON (Point point)            = toJSON point+    toJSON (MultiPoint points)      = toJSON points+    toJSON (Polygon vertices)       = toJSON vertices+    toJSON (MultiPolygon vertices)  = toJSON vertices+    toJSON (Line vertices)          = toJSON vertices+    toJSON (MultiLine vertices)     = toJSON vertices+    toJSON (Collection geometries)  = object+        [   "type" .= ("GeometryCollection" :: Text)+        ,   "geometries" .= geometries+        ]++-- |+-- encodes and decodes Geometry Objects to and from GeoJSON+-- (refer to source to see the values for the test values)+--+-- >>> decode' lShapedPolyJSON == Just lShapedPoly+-- True+--+-- >>> decode' emptyPolyJSON == Just emptyPoly+-- True+--+-- >>> decode' emptyMultiPolyJSON == Just emptyMultiPoly+-- True+--+-- >>> decode' singleLineMultiLineJSON == Just singleLineMultiLine+-- True+--+-- >>> decode' multiLineJSON == Just multiLine+-- True+--+-- >>> decode' emptyCollectionJSON == Just emptyCollection+-- True+--+-- >>> decode' bigassCollectionJSON == Just bigassCollection+-- True+--+-- Aeson doesnt decode "null" into `Null` unfortunately+--+-- decode' "null" :: Maybe GeospatialGeometry+-- Just NoGeometry+--+instance FromJSON GeospatialGeometry where+--  parseJSON :: Value -> Parser a+    parseJSON Null = return NoGeometry+    parseJSON (Object obj) = do+        geometryType <- obj .: ("type" :: Text)+        geometryFromAeson geometryType (Object obj)+    parseJSON _ = mzero
+ src/Data/Geospatial/Geometry/Aeson.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geosptial.Geometry.Aeson+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-- Some helpers for some of the common Aeson ops+-------------------------------------------------------------------+module Data.Geospatial.Geometry.Aeson (+    -- * Geometry+        readGeometryGeoAeson+    ,   makeGeometryGeoAeson+    -- * Optional fields+    ,   optValFromObj+    ,   optAttributes+    ) where++import Control.Applicative ( (<$>) )+import Control.Monad ( mzero )+import Data.Aeson+    (   FromJSON(..)+    ,   ToJSON(..)+    ,   Object+    ,   Value+    ,   (.:)+    ,   (.:?)+    ,   (.=)+    ,   object+    )+import Data.Aeson.Types ( Pair, Parser )+import Data.Maybe ( Maybe(..) )+import Data.Text ( Text )++-- | 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+--+readGeometryGeoAeson :: (FromJSON a, FromJSON b) => String -> (a -> b) -> Object -> Parser b+readGeometryGeoAeson geomTypeString geomType geopointObj = do+    geometryType <- geopointObj .: "type"+    if geometryType == geomTypeString+        then+            geomType <$> geopointObj .: "coordinates"+        else+            mzero++-- | 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+--+makeGeometryGeoAeson :: (ToJSON a) => String -> a -> Value+makeGeometryGeoAeson typeString coordinates =+    object ["type" .= typeString, "coordinates" .= coordinates]++-- | get an optional value out of a JSON object:+--+optValFromObj :: (FromJSON a) => Text -> Object -> Parser (Maybe a)+optValFromObj = flip (.:?)++-- | The other way around, given an optional value, will return the attributes that+-- should be added to the makeObj input+--+optAttributes :: (ToJSON a) => Text -> Maybe a -> [Pair]+optAttributes _ Nothing     = []+optAttributes name (Just x) = [name .= x]
+ src/Data/Geospatial/Geometry/GeoLine.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.Geometry.GeoLine+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-------------------------------------------------------------------+module Data.Geospatial.Geometry.GeoLine (+    -- * Type+        GeoLine(..)+    -- * Lenses+    ,   unGeoLine+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry.Aeson++import Control.Lens ( makeLenses )+import Control.Monad ( mzero )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object )++newtype GeoLine = GeoLine { _unGeoLine :: [GeoPositionWithoutCRS] } deriving (Show, Eq)++makeLenses ''GeoLine++-- instances++instance ToJSON GeoLine where+--  toJSON :: a -> Value+    toJSON = makeGeometryGeoAeson "Line" . _unGeoLine++instance FromJSON GeoLine where+--  parseJSON :: Value -> Parser a+    parseJSON (Object o)    = readGeometryGeoAeson "Line" GeoLine o+    parseJSON _             = mzero
+ src/Data/Geospatial/Geometry/GeoMultiLine.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.Geometry.GeoMultiLine+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-------------------------------------------------------------------+module Data.Geospatial.Geometry.GeoMultiLine (+    -- * Type+        GeoMultiLine(..)+    -- * Lenses+    ,   unGeoMultiLine+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry.GeoLine+import Data.Geospatial.Geometry.Aeson++import Control.Lens ( makeLenses )+import Control.Monad ( mzero )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object )++newtype GeoMultiLine    = GeoMultiLine { _unGeoMultiLine :: [GeoLine] } deriving (Show, Eq)++makeLenses ''GeoMultiLine++-- instances++instance ToJSON GeoMultiLine where+--  toJSON :: a -> Value+    toJSON = makeGeometryGeoAeson "MultiLine" . _unGeoMultiLine++instance FromJSON GeoMultiLine where+--  parseJSON :: Value -> Parser a+    parseJSON (Object o)    = readGeometryGeoAeson "MultiLine" GeoMultiLine o+    parseJSON _             = mzero
+ src/Data/Geospatial/Geometry/GeoMultiPoint.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.Geometry.GeoMultiPoint+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-------------------------------------------------------------------+module Data.Geospatial.Geometry.GeoMultiPoint (+    -- * Type+        GeoMultiPoint(..)+    -- * Lenses+    ,   unGeoMultiPoint+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry.GeoPoint+import Data.Geospatial.Geometry.Aeson++import Control.Lens ( makeLenses )+import Control.Monad ( mzero )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object )++newtype GeoMultiPoint = GeoMultiPoint { _unGeoMultiPoint :: [GeoPoint] } deriving (Show, Eq)++makeLenses ''GeoMultiPoint++-- instances++instance ToJSON GeoMultiPoint where+--  toJSON :: a -> Value+    toJSON = makeGeometryGeoAeson "MultiPoint" . _unGeoMultiPoint++instance FromJSON GeoMultiPoint where+--  parseJSON :: Value -> Parser a+    parseJSON (Object o)    = readGeometryGeoAeson "MultiPoint" GeoMultiPoint o+    parseJSON _             = mzero
+ src/Data/Geospatial/Geometry/GeoMultiPolygon.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.Geometry.GeoMultiPolygon+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-------------------------------------------------------------------+module Data.Geospatial.Geometry.GeoMultiPolygon (+    -- * Type+        GeoMultiPolygon(..)+    -- * Lenses+    ,   unGeoMultiPolygon+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry.GeoPolygon+import Data.Geospatial.Geometry.Aeson++import Control.Lens ( makeLenses )+import Control.Monad ( mzero )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object )++newtype GeoMultiPolygon = GeoMultiPolygon { _unGeoMultiPolygon :: [GeoPolygon] } deriving (Show, Eq)++makeLenses ''GeoMultiPolygon++-- instances++instance ToJSON GeoMultiPolygon where+--  toJSON :: a -> Value+    toJSON = makeGeometryGeoAeson "MultiPolygon" . _unGeoMultiPolygon++instance FromJSON GeoMultiPolygon where+--  parseJSON :: Value -> Parser a+    parseJSON (Object o)    = readGeometryGeoAeson "MultiPolygon" GeoMultiPolygon o+    parseJSON _             = mzero
+ src/Data/Geospatial/Geometry/GeoPoint.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.Geometry.GeoPoint+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-------------------------------------------------------------------+module Data.Geospatial.Geometry.GeoPoint (+    -- * Type+        GeoPoint(..)+    -- * Lenses+    ,   unGeoPoint+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry.Aeson++import Control.Lens ( makeLenses )+import Control.Monad ( mzero )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object )++newtype GeoPoint = GeoPoint { _unGeoPoint :: GeoPositionWithoutCRS } deriving (Show, Eq)++makeLenses ''GeoPoint++-- instances++instance ToJSON GeoPoint where+--  toJSON :: a -> Value+    toJSON = makeGeometryGeoAeson "Point" . _unGeoPoint++instance FromJSON GeoPoint where+--  parseJSON :: Value -> Parser a+    parseJSON (Object o)    = readGeometryGeoAeson "Point" GeoPoint o+    parseJSON _             = mzero
+ src/Data/Geospatial/Geometry/GeoPolygon.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE TemplateHaskell #-}+-------------------------------------------------------------------+-- |+-- Module       : Data.Geospatial.Geometry.GeoPolygon+-- Copyright    : (C) 2014 Dom De Re+-- License      : BSD-style (see the file etc/LICENSE.md)+-- Maintainer   : Dom De Re+--+-------------------------------------------------------------------+module Data.Geospatial.Geometry.GeoPolygon (+    -- * Type+        GeoPolygon(..)+    -- * Lenses+    ,   unGeoPolygon+    ) where++import Data.Geospatial.BasicTypes+import Data.Geospatial.Geometry.Aeson+import Data.Geospatial.GeoPosition++import Control.Lens ( makeLenses )+import Control.Monad ( mzero )+import Data.Aeson ( FromJSON(..), ToJSON(..), Value(..), Object )++newtype GeoPolygon = GeoPolygon { _unGeoPolygon :: [GeoPositionWithoutCRS] } deriving (Show, Eq)++makeLenses ''GeoPolygon++-- instances++instance ToJSON GeoPolygon where+--  toJSON :: a -> Value+    toJSON = makeGeometryGeoAeson "Polygon" . _unGeoPolygon++instance FromJSON GeoPolygon where+--  parseJSON :: Value -> Parser a+    parseJSON (Object o)    = readGeometryGeoAeson "Polygon" GeoPolygon o+    parseJSON _             = mzero
− src/Text/GeoJSON.hs
@@ -1,410 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--- Only export the JSON instances-module Text.GeoJSON () where--import Control.Applicative-import Text.JSON--import Data.Geospatial---- | This module only exports the JSON Instances for the types from Data.Geospatial.--- .--- Hence you will have to refer to the `Data.Geospatial` documentation to find the documentation--- related to the Instances contained in this module.---- $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)]-