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