├── README.md ├── elm-package.json ├── examples ├── CustomMarkers.elm ├── Example.elm ├── Fullscreen.elm ├── Markers.elm ├── Search.elm ├── SearchMarkers.elm ├── ballarat-sports-grounds.json └── elm-package.json ├── src ├── Maps.elm └── Maps │ ├── Convert.elm │ ├── Geo.elm │ ├── Internal │ ├── Bounds.elm │ ├── Drag.elm │ ├── LatLng.elm │ ├── Map.elm │ ├── Maps.elm │ ├── Marker.elm │ ├── OpaqueTypes.elm │ ├── Pinch.elm │ ├── Screen.elm │ ├── Tile.elm │ ├── Utils.elm │ └── Zoom.elm │ ├── Map.elm │ └── Marker.elm └── tests ├── Expects.elm ├── Fuzzers.elm ├── Main.elm ├── Tests ├── Map.elm ├── Screen.elm └── Tile.elm └── elm-package.json /README.md: -------------------------------------------------------------------------------- 1 | # Elm Maps 2 | 3 | An elm library for interactive maps. 4 | 5 | # Examples 6 | It's very simple to get create a map with this library 7 | 8 | ```elm 9 | import Maps 10 | import Html exposing (program) 11 | 12 | main = program 13 | { init = (Maps.defaultModel, Cmd.none) 14 | , subscriptions = Maps.subscriptions 15 | , update = Maps.update 16 | , view = Maps.view 17 | } 18 | ``` 19 | 20 | Its a bit trickier when we try and merge events and state with other programs. 21 | See the following examples: 22 | 23 | * Map | [source code](https://github.com/kennib/elm-maps/blob/master/examples/Example.elm) | [live](https://kennib.github.io/elm-maps/examples/Example) 24 | * Fullscreen Map | [source code](https://github.com/kennib/elm-maps/blob/master/examples/Fullscreen.elm) | [live](https://kennib.github.io/elm-maps/examples/Fullscreen) 25 | * Map Markers | [source code](https://github.com/kennib/elm-maps/blob/master/examples/Markers.elm) | [live](https://kennib.github.io/elm-maps/examples/Markers) 26 | * Custom Markers | [source code](https://github.com/kennib/elm-maps/blob/master/examples/CustomMarkers.elm) | [live](https://kennib.github.io/elm-maps/examples/CustomMarkers) 27 | * Geocode Search | [source code](https://github.com/kennib/elm-maps/blob/master/examples/Search.elm) | [live](https://kennib.github.io/elm-maps/examples/Search) 28 | * Gecode Search Markers | [source code](https://github.com/kennib/elm-maps/blob/master/examples/SearchMarkers.elm) | [live](https://kennib.github.io/elm-maps/examples/SearchMarkers) 29 | 30 | # Documentation 31 | See the [Elm docs](http://package.elm-lang.org/packages/kennib/elm-maps/latest/Maps) for this library. 32 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.2.2", 3 | "summary": "An Elm library for interactive maps", 4 | "repository": "https://github.com/kennib/elm-maps.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Maps", 11 | "Maps.Geo", 12 | "Maps.Map", 13 | "Maps.Marker", 14 | "Maps.Convert" 15 | ], 16 | "dependencies": { 17 | "elm-community/list-extra": "6.1.0 <= v < 7.0.0", 18 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 19 | "elm-lang/html": "2.0.0 <= v < 3.0.0" 20 | }, 21 | "elm-version": "0.18.0 <= v < 0.19.0" 22 | } 23 | -------------------------------------------------------------------------------- /examples/CustomMarkers.elm: -------------------------------------------------------------------------------- 1 | module CustomMarkers exposing (..) 2 | 3 | import Html exposing (program) 4 | import Html.Attributes as Attr 5 | import Html.Events exposing (onClick) 6 | 7 | import Http 8 | import Json.Decode as Json 9 | import GeoJson exposing (GeoJson, Geometry(..)) 10 | 11 | import Maps 12 | import Maps.Geo 13 | import Maps.Map as Map 14 | import Maps.Marker as Marker exposing (Marker) 15 | 16 | type Msg 17 | = MapsMsg (Maps.Msg Msg) 18 | | Select SportsGround 19 | | LoadSportsGrounds (Result Http.Error GeoJson) 20 | 21 | type alias Model = 22 | { map : Maps.Model Msg 23 | , sportsGrounds : List SportsGround 24 | , selected : Maybe SportsGround 25 | } 26 | 27 | type alias SportsGround = 28 | { latLng : Maps.Geo.LatLng 29 | , name : String 30 | , kind : String 31 | } 32 | 33 | main = program 34 | { init = init 35 | , update = update 36 | , subscriptions = subscriptions 37 | , view = view 38 | } 39 | 40 | init = 41 | ( { map = 42 | Maps.defaultModel 43 | |> Maps.updateMap (Map.setZoom 11 >> Map.moveTo ballarat) 44 | , sportsGrounds = [] 45 | , selected = Nothing 46 | } 47 | , Http.send LoadSportsGrounds getSportsGrounds 48 | ) 49 | 50 | ballarat = Maps.Geo.latLng -37.5672953 143.782701 51 | 52 | getSportsGrounds : Http.Request GeoJson 53 | getSportsGrounds = 54 | Http.get "./ballarat-sports-grounds.json" GeoJson.decoder 55 | 56 | subscriptions : Model -> Sub Msg 57 | subscriptions model = 58 | Maps.subscriptions model.map 59 | |> Sub.map MapsMsg 60 | 61 | update msg model = 62 | case msg of 63 | MapsMsg msg -> 64 | model.map 65 | |> Maps.update msg 66 | |> Tuple.mapFirst (\map -> { model | map = map }) 67 | |> Tuple.mapSecond (Cmd.map MapsMsg) 68 | Select ground -> 69 | ({ model | selected = Just ground }, Cmd.none) 70 | LoadSportsGrounds result -> 71 | let 72 | grounds = resultToSportsGrounds result 73 | in 74 | ( { model 75 | | map = model.map |> Maps.updateMarkers (\markers -> grounds |> List.map groundMarker) 76 | , sportsGrounds = grounds 77 | } 78 | , Cmd.none 79 | ) 80 | 81 | resultToSportsGrounds : Result Http.Error GeoJson -> List SportsGround 82 | resultToSportsGrounds result = 83 | let 84 | featureToSportsGround feature = 85 | case feature.geometry of 86 | Just (Point (lng, lat, _)) -> 87 | Result.map2 (SportsGround (Maps.Geo.latLng lat lng)) 88 | (property "site" feature) 89 | (property "feat_type" feature) 90 | |> Result.toMaybe 91 | _ -> 92 | Nothing 93 | property propertyName feature = 94 | Json.decodeValue 95 | (Json.field propertyName Json.string) 96 | feature.properties 97 | in 98 | case result of 99 | (Result.Ok (GeoJson.FeatureCollection features, _)) -> 100 | features 101 | |> List.filterMap featureToSportsGround 102 | _ -> [] 103 | 104 | groundMarker : SportsGround -> Marker Msg 105 | groundMarker ground = 106 | let 107 | view = 108 | Html.span 109 | [ onClick <| Select ground, Attr.style [("cursor", "pointer")] ] 110 | [ Html.text <| groundSymbol ground.kind ] 111 | in 112 | Marker.createCustom view ground.latLng 113 | 114 | groundSymbol : String -> String 115 | groundSymbol kind = 116 | let 117 | symbol (name, symbol) = if String.contains name kind then symbol else "" 118 | default defaultString string = if String.isEmpty string then defaultString else string 119 | in 120 | [ ("Football", "🏉") 121 | , ("Soccer", "⚽") 122 | , ("Cricket", "🔴") 123 | , ("Softball", "⚾") 124 | , ("Equestrian", "🐎") 125 | ] 126 | |> List.map symbol 127 | |> String.join "" 128 | |> default "❌" 129 | 130 | view model = 131 | Html.div 132 | [] 133 | [ Maps.view model.map |> Maps.mapView MapsMsg 134 | , case model.selected of 135 | Just ground -> 136 | Html.div 137 | [] 138 | [ Html.h1 [] [ Html.text ground.name ] 139 | , Html.p [] [ Html.text ground.kind ] 140 | ] 141 | Nothing -> 142 | Html.p [] [ Html.text "Click on a map marker" ] 143 | ] 144 | -------------------------------------------------------------------------------- /examples/Example.elm: -------------------------------------------------------------------------------- 1 | module Example exposing (..) 2 | 3 | import Html exposing (program) 4 | 5 | import Maps 6 | 7 | main = program 8 | { init = (Maps.defaultModel, Cmd.none) 9 | , subscriptions = Maps.subscriptions 10 | , update = Maps.update 11 | , view = Maps.view 12 | } 13 | -------------------------------------------------------------------------------- /examples/Fullscreen.elm: -------------------------------------------------------------------------------- 1 | module FullScreen exposing (..) 2 | 3 | import Task 4 | import Window 5 | 6 | import Html exposing (program) 7 | import Html.Events exposing (onInput) 8 | 9 | import Maps 10 | import Maps.Map as Map 11 | 12 | type Msg 13 | = MapMsg (Maps.Msg ()) 14 | | Resize Window.Size 15 | 16 | main = program 17 | { init = init 18 | , update = update 19 | , subscriptions = subscriptions 20 | , view = view 21 | } 22 | 23 | init = 24 | ( Maps.defaultModel 25 | , Task.attempt (Result.withDefault defaultSize >> Resize) Window.size 26 | ) 27 | 28 | defaultSize = 29 | Window.Size 500 500 30 | 31 | update msg model = 32 | case msg of 33 | MapMsg msg -> 34 | Maps.update msg model 35 | |> Tuple.mapSecond (Cmd.map MapMsg) 36 | Resize size -> 37 | ( model 38 | |> Maps.updateMap (Map.setWidth <| toFloat size.width) 39 | |> Maps.updateMap (Map.setHeight <| toFloat size.height) 40 | , Cmd.none 41 | ) 42 | 43 | subscriptions model = 44 | Sub.batch 45 | [ Sub.map MapMsg <| Maps.subscriptions model 46 | , Window.resizes Resize 47 | ] 48 | 49 | view model = 50 | Html.map MapMsg <| Maps.view model 51 | -------------------------------------------------------------------------------- /examples/Markers.elm: -------------------------------------------------------------------------------- 1 | module Markers exposing (..) 2 | 3 | import Html exposing (program) 4 | 5 | import Maps 6 | import Maps.Geo 7 | import Maps.Map as Map 8 | import Maps.Marker as Marker 9 | 10 | main = program 11 | { init = init 12 | , update = Maps.update 13 | , subscriptions = Maps.subscriptions 14 | , view = Maps.view 15 | } 16 | 17 | init = 18 | ( Maps.defaultModel 19 | |> Maps.updateMap (Map.setZoom 14 >> Map.moveTo sydney) 20 | |> Maps.updateMarkers ((::) (Marker.createCustom (Html.text "Sydney") sydney)) 21 | |> Maps.updateMarkers (\markers -> List.map Marker.create attractions ++ markers) 22 | , Cmd.none 23 | ) 24 | 25 | sydney = Maps.Geo.latLng -33.865143 151.209900 26 | 27 | attractions = 28 | List.map (uncurry Maps.Geo.latLng) 29 | [ (-33.852324, 151.210819) 30 | , (-33.856872, 151.215239) 31 | , (-33.870397, 151.208835) 32 | ] 33 | -------------------------------------------------------------------------------- /examples/Search.elm: -------------------------------------------------------------------------------- 1 | module Search exposing (..) 2 | 3 | import Html exposing (program) 4 | import Html.Events exposing (onInput) 5 | import Geocoding 6 | import Http 7 | 8 | import Maps exposing (Msg) 9 | import Maps.Geo exposing (Bounds) 10 | import Maps.Map as Map 11 | 12 | type Msg 13 | = MapMsg (Maps.Msg ()) 14 | | GeoCode String 15 | | GoToGeoCode String Bounds 16 | | NoResults String 17 | 18 | apiKey = "AIzaSyCkOFxL5NF1feuebbB6PW8fP3SDg1aa6tM" 19 | 20 | main = program 21 | { init = init 22 | , update = update 23 | , subscriptions = subscriptions 24 | , view = view 25 | } 26 | 27 | init = 28 | ( { map = Maps.defaultModel 29 | |> Maps.updateMap (Map.setHeight 600) 30 | |> Maps.updateMap (Map.setWidth 1000) 31 | , place = "" 32 | } 33 | , Cmd.none) 34 | 35 | update msg model = 36 | case msg of 37 | MapMsg mapMsg -> 38 | Maps.update mapMsg model.map 39 | |> Tuple.mapFirst (\map -> { model | map = map }) 40 | |> Tuple.mapSecond (Cmd.map MapMsg) 41 | GeoCode place -> 42 | ({ model | place = place }, geocode place) 43 | GoToGeoCode place bounds -> 44 | if place == model.place then 45 | model.map 46 | |> Maps.updateMap (Map.viewBounds bounds) 47 | |> \map -> ({ model | map = map }, Cmd.none) 48 | else 49 | (model, Cmd.none) 50 | NoResults place -> 51 | (model, Cmd.none) 52 | 53 | geocode : String -> Cmd Msg 54 | geocode place = 55 | Geocoding.requestForAddress apiKey place 56 | |> Geocoding.send (Maybe.withDefault (NoResults place) << Maybe.map (GoToGeoCode place) << getFirstBounds) 57 | 58 | getFirstBounds : Result Http.Error Geocoding.Response -> Maybe Bounds 59 | getFirstBounds result = 60 | result 61 | |> Result.toMaybe 62 | |> Maybe.map .results 63 | |> Maybe.andThen List.head 64 | |> Maybe.map .geometry 65 | |> Maybe.map .viewport 66 | |> Maybe.map (\bounds -> 67 | Maps.Geo.bounds 68 | { northEast = { lat = bounds.northeast.latitude, lng = bounds.northeast.longitude } 69 | , southWest = { lat = bounds.southwest.latitude, lng = bounds.southwest.longitude } 70 | } 71 | ) 72 | 73 | subscriptions model = 74 | Sub.map MapMsg <| Maps.subscriptions model.map 75 | 76 | view model = 77 | Html.div 78 | [] 79 | [ Html.map MapMsg <| Maps.view model.map 80 | , Html.input 81 | [ onInput GeoCode 82 | ] 83 | [] 84 | ] 85 | -------------------------------------------------------------------------------- /examples/SearchMarkers.elm: -------------------------------------------------------------------------------- 1 | module Markers exposing (..) 2 | 3 | import Html exposing (program) 4 | import Html.Events exposing (onInput) 5 | import Geocoding 6 | import Http 7 | 8 | import Maps exposing (Msg) 9 | import Maps.Geo exposing (LatLng, Bounds) 10 | import Maps.Marker as Marker exposing (Marker) 11 | import Maps.Map as Map 12 | 13 | type Msg 14 | = MapMsg (Maps.Msg ()) 15 | | GeoCode String 16 | | GoToGeoCode String (Bounds, LatLng) 17 | | NoResults String 18 | 19 | apiKey = "AIzaSyCkOFxL5NF1feuebbB6PW8fP3SDg1aa6tM" 20 | 21 | main = program 22 | { init = init 23 | , update = update 24 | , subscriptions = subscriptions 25 | , view = view 26 | } 27 | 28 | init = 29 | ( { map = Maps.defaultModel 30 | |> Maps.updateMap (Map.setHeight 600) 31 | |> Maps.updateMap (Map.setWidth 1000) 32 | , place = "" 33 | } 34 | , Cmd.none) 35 | 36 | update msg model = 37 | case msg of 38 | MapMsg mapMsg -> 39 | Maps.update mapMsg model.map 40 | |> Tuple.mapFirst (\map -> { model | map = map }) 41 | |> Tuple.mapSecond (Cmd.map MapMsg) 42 | GeoCode place -> 43 | ({ model | place = place }, geocode place) 44 | GoToGeoCode place (bounds, latLng) -> 45 | if place == model.place then 46 | model.map 47 | |> Maps.updateMap (Map.viewBounds bounds) 48 | |> Maps.updateMarkers (\_ -> [Marker.create latLng]) 49 | |> \map -> ({ model | map = map }, Cmd.none) 50 | else 51 | (model, Cmd.none) 52 | NoResults place -> 53 | (model, Cmd.none) 54 | 55 | geocode : String -> Cmd Msg 56 | geocode place = 57 | Geocoding.requestForAddress apiKey place 58 | |> Geocoding.send (Maybe.withDefault (NoResults place) << Maybe.map (GoToGeoCode place) << getFirstLocation) 59 | 60 | getFirstLocation : Result Http.Error Geocoding.Response -> Maybe (Bounds, LatLng) 61 | getFirstLocation result = 62 | result 63 | |> Result.toMaybe 64 | |> Maybe.map .results 65 | |> Maybe.andThen List.head 66 | |> Maybe.map .geometry 67 | |> Maybe.map (\geometry -> 68 | let 69 | bounds = 70 | Maps.Geo.bounds 71 | { northEast = { lat = geometry.viewport.northeast.latitude, lng = geometry.viewport.northeast.longitude } 72 | , southWest = { lat = geometry.viewport.southwest.latitude, lng = geometry.viewport.southwest.longitude } 73 | } 74 | latLng = 75 | { lat = geometry.location.latitude 76 | , lng = geometry.location.longitude 77 | } 78 | in 79 | (bounds, latLng) 80 | ) 81 | 82 | subscriptions model = 83 | Sub.map MapMsg <| Maps.subscriptions model.map 84 | 85 | view model = 86 | Html.div 87 | [] 88 | [ Html.map MapMsg <| Maps.view model.map 89 | , Html.input 90 | [ onInput GeoCode 91 | ] 92 | [] 93 | ] 94 | -------------------------------------------------------------------------------- /examples/ballarat-sports-grounds.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "totalFeatures": 146, 4 | "features": [ 5 | { 6 | "type": "Feature", 7 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.1", 8 | "geometry": { 9 | "type": "Point", 10 | "coordinates": [ 11 | 143.71004584531292, 12 | -37.42182502633037 13 | ] 14 | }, 15 | "geometry_name": "geom", 16 | "properties": { 17 | "id": "00217005", 18 | "site": "Alexander Park", 19 | "location": "Learmonth Recreation Reserve", 20 | "feat_type": "SG-Cricket Pitch", 21 | "easting": 739814, 22 | "northing": 5854883, 23 | "measure": 59.2, 24 | "unit": "Square Metres" 25 | } 26 | }, 27 | { 28 | "type": "Feature", 29 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.2", 30 | "geometry": { 31 | "type": "Point", 32 | "coordinates": [ 33 | 143.7994276630051, 34 | -37.555697321325404 35 | ] 36 | }, 37 | "geometry_name": "geom", 38 | "properties": { 39 | "id": "00217006", 40 | "site": "Alfredton Recreation Reserve", 41 | "location": "Alfredton Recreation Reserve", 42 | "feat_type": "SG-Cricket Pitch", 43 | "easting": 747283, 44 | "northing": 5839795, 45 | "measure": 72.4, 46 | "unit": "Square Metres" 47 | } 48 | }, 49 | { 50 | "type": "Feature", 51 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.3", 52 | "geometry": { 53 | "type": "Point", 54 | "coordinates": [ 55 | 143.7994607746233, 56 | -37.55570176082929 57 | ] 58 | }, 59 | "geometry_name": "geom", 60 | "properties": { 61 | "id": "00217010", 62 | "site": "Alfredton Recreation Reserve", 63 | "location": "Alfredton Recreation Reserve", 64 | "feat_type": "SG-Cricket/Football", 65 | "easting": 747286, 66 | "northing": 5839794, 67 | "measure": 20300, 68 | "unit": "Square Metres" 69 | } 70 | }, 71 | { 72 | "type": "Feature", 73 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.4", 74 | "geometry": { 75 | "type": "Point", 76 | "coordinates": [ 77 | 143.79896330137367, 78 | -37.55747130240099 79 | ] 80 | }, 81 | "geometry_name": "geom", 82 | "properties": { 83 | "id": "00233992", 84 | "site": "Alfredton Recreation Reserve", 85 | "location": null, 86 | "feat_type": "SG-Cricket Pitch", 87 | "easting": 747236, 88 | "northing": 5839599, 89 | "measure": 0, 90 | "unit": "Square Metres" 91 | } 92 | }, 93 | { 94 | "type": "Feature", 95 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.5", 96 | "geometry": { 97 | "type": "Point", 98 | "coordinates": [ 99 | 143.87122582582356, 100 | -37.54941062474189 101 | ] 102 | }, 103 | "geometry_name": "geom", 104 | "properties": { 105 | "id": "00217012", 106 | "site": "Binney Reserve", 107 | "location": "Binney Reserve (Binney Street Res.)", 108 | "feat_type": "SG-Cricket/Football/Soccer", 109 | "easting": 753648, 110 | "northing": 5840301, 111 | "measure": 9214.9, 112 | "unit": "Square Metres" 113 | } 114 | }, 115 | { 116 | "type": "Feature", 117 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.6", 118 | "geometry": { 119 | "type": "Point", 120 | "coordinates": [ 121 | 143.87123831914786, 122 | -37.54941275307749 123 | ] 124 | }, 125 | "geometry_name": "geom", 126 | "properties": { 127 | "id": "00217013", 128 | "site": "Binney Reserve", 129 | "location": "Binney Reserve (Binney Street Res.)", 130 | "feat_type": "SG-Cricket Pitch", 131 | "easting": 753649, 132 | "northing": 5840301, 133 | "measure": 57.4, 134 | "unit": "Square Metres" 135 | } 136 | }, 137 | { 138 | "type": "Feature", 139 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.7", 140 | "geometry": { 141 | "type": "Point", 142 | "coordinates": [ 143 | 143.89674006520553, 144 | -37.552090897622165 145 | ] 146 | }, 147 | "geometry_name": "geom", 148 | "properties": { 149 | "id": "00217014", 150 | "site": "Brown Hill Reserve", 151 | "location": "Brown Hill Reserve (Progress Park)", 152 | "feat_type": "SG-Cricket Pitch", 153 | "easting": 755893, 154 | "northing": 5839935, 155 | "measure": 11914, 156 | "unit": "Square Metres" 157 | } 158 | }, 159 | { 160 | "type": "Feature", 161 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.8", 162 | "geometry": { 163 | "type": "Point", 164 | "coordinates": [ 165 | 143.8904201424944, 166 | -37.64852963285524 167 | ] 168 | }, 169 | "geometry_name": "geom", 170 | "properties": { 171 | "id": "00217022", 172 | "site": "Buninyong Recreation Reserve", 173 | "location": "Buninyong Community Facility", 174 | "feat_type": "SG-Cricket/Football", 175 | "easting": 755005, 176 | "northing": 5829249, 177 | "measure": 18833.6, 178 | "unit": "Square Metres" 179 | } 180 | }, 181 | { 182 | "type": "Feature", 183 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.9", 184 | "geometry": { 185 | "type": "Point", 186 | "coordinates": [ 187 | 143.8911309656877, 188 | -37.648852187229224 189 | ] 190 | }, 191 | "geometry_name": "geom", 192 | "properties": { 193 | "id": "00217023", 194 | "site": "Buninyong Recreation Reserve", 195 | "location": "Buninyong Community Facility", 196 | "feat_type": "SG-Cricket Pitch", 197 | "easting": 755066, 198 | "northing": 5829211, 199 | "measure": 46.3, 200 | "unit": "Square Metres" 201 | } 202 | }, 203 | { 204 | "type": "Feature", 205 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.10", 206 | "geometry": { 207 | "type": "Point", 208 | "coordinates": [ 209 | 143.89126701806336, 210 | -37.6498323263408 211 | ] 212 | }, 213 | "geometry_name": "geom", 214 | "properties": { 215 | "id": "00217024", 216 | "site": "Buninyong Recreation Reserve", 217 | "location": "Buninyong Community Facility", 218 | "feat_type": "SG-Cricket Pitch", 219 | "easting": 755075, 220 | "northing": 5829102, 221 | "measure": 59.7, 222 | "unit": "Square Metres" 223 | } 224 | }, 225 | { 226 | "type": "Feature", 227 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.11", 228 | "geometry": { 229 | "type": "Point", 230 | "coordinates": [ 231 | 143.89120941861572, 232 | -37.64983860387864 233 | ] 234 | }, 235 | "geometry_name": "geom", 236 | "properties": { 237 | "id": "00217025", 238 | "site": "Buninyong Recreation Reserve", 239 | "location": "Buninyong Community Facility", 240 | "feat_type": "SG-Cricket Pitch", 241 | "easting": 755070, 242 | "northing": 5829101, 243 | "measure": 58.6, 244 | "unit": "Square Metres" 245 | } 246 | }, 247 | { 248 | "type": "Feature", 249 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.12", 250 | "geometry": { 251 | "type": "Point", 252 | "coordinates": [ 253 | 143.89119362273843, 254 | -37.64979034452684 255 | ] 256 | }, 257 | "geometry_name": "geom", 258 | "properties": { 259 | "id": "00217026", 260 | "site": "Buninyong Recreation Reserve", 261 | "location": "Buninyong Community Facility", 262 | "feat_type": "SG-Cricket Pitch", 263 | "easting": 755069, 264 | "northing": 5829107, 265 | "measure": 57.1, 266 | "unit": "Square Metres" 267 | } 268 | }, 269 | { 270 | "type": "Feature", 271 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.13", 272 | "geometry": { 273 | "type": "Point", 274 | "coordinates": [ 275 | 143.70919919818115, 276 | -37.51367897405943 277 | ] 278 | }, 279 | "geometry_name": "geom", 280 | "properties": { 281 | "id": "00217028", 282 | "site": "Cardigan Village Reserve", 283 | "location": "Cardigan Village Reserve", 284 | "feat_type": "SG-Football", 285 | "easting": 739445, 286 | "northing": 5844692, 287 | "measure": 746.8, 288 | "unit": "Square Metres" 289 | } 290 | }, 291 | { 292 | "type": "Feature", 293 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.14", 294 | "geometry": { 295 | "type": "Point", 296 | "coordinates": [ 297 | 143.84291476311088, 298 | -37.537227386573534 299 | ] 300 | }, 301 | "geometry_name": "geom", 302 | "properties": { 303 | "id": "00217030", 304 | "site": "Charles Edward Brown Reserve", 305 | "location": "C.E Brown Reserve", 306 | "feat_type": "SG-Cricket Pitch", 307 | "easting": 751187, 308 | "northing": 5841729, 309 | "measure": 315.6, 310 | "unit": "Square Metres" 311 | } 312 | }, 313 | { 314 | "type": "Feature", 315 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.15", 316 | "geometry": { 317 | "type": "Point", 318 | "coordinates": [ 319 | 143.84291612315715, 320 | -37.537209155838404 321 | ] 322 | }, 323 | "geometry_name": "geom", 324 | "properties": { 325 | "id": "00217034", 326 | "site": "Charles Edward Brown Reserve", 327 | "location": "C.E Brown Reserve", 328 | "feat_type": "SG-Cricket/Football", 329 | "easting": 751187, 330 | "northing": 5841731, 331 | "measure": 18805.6, 332 | "unit": "Square Metres" 333 | } 334 | }, 335 | { 336 | "type": "Feature", 337 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.16", 338 | "geometry": { 339 | "type": "Point", 340 | "coordinates": [ 341 | 143.844600760655, 342 | -37.537508640426175 343 | ] 344 | }, 345 | "geometry_name": "geom", 346 | "properties": { 347 | "id": "00217035", 348 | "site": "Charles Edward Brown Reserve", 349 | "location": "C.E Brown Reserve", 350 | "feat_type": "SG-Cricket/Football", 351 | "easting": 751335, 352 | "northing": 5841694, 353 | "measure": 13618.1, 354 | "unit": "Square Metres" 355 | } 356 | }, 357 | { 358 | "type": "Feature", 359 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.17", 360 | "geometry": { 361 | "type": "Point", 362 | "coordinates": [ 363 | 143.84339637733945, 364 | -37.53579021433829 365 | ] 366 | }, 367 | "geometry_name": "geom", 368 | "properties": { 369 | "id": "00217036", 370 | "site": "Charles Edward Brown Reserve", 371 | "location": "C.E Brown Reserve", 372 | "feat_type": "SG-Cricket Pitch", 373 | "easting": 751234, 374 | "northing": 5841888, 375 | "measure": 60.1, 376 | "unit": "Square Metres" 377 | } 378 | }, 379 | { 380 | "type": "Feature", 381 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.18", 382 | "geometry": { 383 | "type": "Point", 384 | "coordinates": [ 385 | 143.84334711519924, 386 | -37.53578536545653 387 | ] 388 | }, 389 | "geometry_name": "geom", 390 | "properties": { 391 | "id": "00217037", 392 | "site": "Charles Edward Brown Reserve", 393 | "location": "C.E Brown Reserve", 394 | "feat_type": "SG-Cricket Pitch", 395 | "easting": 751230, 396 | "northing": 5841888, 397 | "measure": 60.3, 398 | "unit": "Square Metres" 399 | } 400 | }, 401 | { 402 | "type": "Feature", 403 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.19", 404 | "geometry": { 405 | "type": "Point", 406 | "coordinates": [ 407 | 143.84344291133186, 408 | -37.53579693072066 409 | ] 410 | }, 411 | "geometry_name": "geom", 412 | "properties": { 413 | "id": "00217038", 414 | "site": "Charles Edward Brown Reserve", 415 | "location": "C.E Brown Reserve", 416 | "feat_type": "SG-Cricket Pitch", 417 | "easting": 751238, 418 | "northing": 5841887, 419 | "measure": 60.3, 420 | "unit": "Square Metres" 421 | } 422 | }, 423 | { 424 | "type": "Feature", 425 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.20", 426 | "geometry": { 427 | "type": "Point", 428 | "coordinates": [ 429 | 143.84348848323205, 430 | -37.53580042706697 431 | ] 432 | }, 433 | "geometry_name": "geom", 434 | "properties": { 435 | "id": "00217039", 436 | "site": "Charles Edward Brown Reserve", 437 | "location": "C.E Brown Reserve", 438 | "feat_type": "SG-Cricket Pitch", 439 | "easting": 751242, 440 | "northing": 5841886, 441 | "measure": 60.1, 442 | "unit": "Square Metres" 443 | } 444 | }, 445 | { 446 | "type": "Feature", 447 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.21", 448 | "geometry": { 449 | "type": "Point", 450 | "coordinates": [ 451 | 143.84459594739994, 452 | -37.537509657353006 453 | ] 454 | }, 455 | "geometry_name": "geom", 456 | "properties": { 457 | "id": "00217040", 458 | "site": "Charles Edward Brown Reserve", 459 | "location": "C.E Brown Reserve", 460 | "feat_type": "SG-Cricket Pitch", 461 | "easting": 751335, 462 | "northing": 5841694, 463 | "measure": 53.8, 464 | "unit": "Square Metres" 465 | } 466 | }, 467 | { 468 | "type": "Feature", 469 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.22", 470 | "geometry": { 471 | "type": "Point", 472 | "coordinates": [ 473 | 143.8378357229932, 474 | -37.55815974078584 475 | ] 476 | }, 477 | "geometry_name": "geom", 478 | "properties": { 479 | "id": "00217043", 480 | "site": "City Oval", 481 | "location": "City Oval", 482 | "feat_type": "SG-Cricket/Football", 483 | "easting": 750668, 484 | "northing": 5839420, 485 | "measure": 20146.5, 486 | "unit": "Square Metres" 487 | } 488 | }, 489 | { 490 | "type": "Feature", 491 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.23", 492 | "geometry": { 493 | "type": "Point", 494 | "coordinates": [ 495 | 143.8164999745706, 496 | -37.58446696948603 497 | ] 498 | }, 499 | "geometry_name": "geom", 500 | "properties": { 501 | "id": "00217044", 502 | "site": "Doug Dean Reserve", 503 | "location": "Doug Dean Reserve", 504 | "feat_type": "SG-Cricket/Football", 505 | "easting": 748695, 506 | "northing": 5836557, 507 | "measure": 15976.8, 508 | "unit": "Square Metres" 509 | } 510 | }, 511 | { 512 | "type": "Feature", 513 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.24", 514 | "geometry": { 515 | "type": "Point", 516 | "coordinates": [ 517 | 143.81634482014388, 518 | -37.583179790032325 519 | ] 520 | }, 521 | "geometry_name": "geom", 522 | "properties": { 523 | "id": "00217046", 524 | "site": "Doug Dean Reserve", 525 | "location": "Doug Dean Reserve", 526 | "feat_type": "SG-Cricket Pitch", 527 | "easting": 748686, 528 | "northing": 5836700, 529 | "measure": 41.7, 530 | "unit": "Square Metres" 531 | } 532 | }, 533 | { 534 | "type": "Feature", 535 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.25", 536 | "geometry": { 537 | "type": "Point", 538 | "coordinates": [ 539 | 143.8163868877013, 540 | -37.583181217932584 541 | ] 542 | }, 543 | "geometry_name": "geom", 544 | "properties": { 545 | "id": "00217047", 546 | "site": "Doug Dean Reserve", 547 | "location": "Doug Dean Reserve", 548 | "feat_type": "SG-Cricket Pitch", 549 | "easting": 748690, 550 | "northing": 5836700, 551 | "measure": 41.6, 552 | "unit": "Square Metres" 553 | } 554 | }, 555 | { 556 | "type": "Feature", 557 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.26", 558 | "geometry": { 559 | "type": "Point", 560 | "coordinates": [ 561 | 143.81645968222054, 562 | -37.584475499118575 563 | ] 564 | }, 565 | "geometry_name": "geom", 566 | "properties": { 567 | "id": "00217048", 568 | "site": "Doug Dean Reserve", 569 | "location": "Doug Dean Reserve", 570 | "feat_type": "SG-Cricket Pitch", 571 | "easting": 748692, 572 | "northing": 5836556, 573 | "measure": 45.9, 574 | "unit": "Square Metres" 575 | } 576 | }, 577 | { 578 | "type": "Feature", 579 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.27", 580 | "geometry": { 581 | "type": "Point", 582 | "coordinates": [ 583 | 143.86348527623946, 584 | -37.557770302465514 585 | ] 586 | }, 587 | "geometry_name": "geom", 588 | "properties": { 589 | "id": "00217049", 590 | "site": "Eastern Oval", 591 | "location": "Eastern Oval", 592 | "feat_type": "SG-Cricket/Football", 593 | "easting": 752935, 594 | "northing": 5839394, 595 | "measure": 17946.3, 596 | "unit": "Square Metres" 597 | } 598 | }, 599 | { 600 | "type": "Feature", 601 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.28", 602 | "geometry": { 603 | "type": "Point", 604 | "coordinates": [ 605 | 143.86356863826057, 606 | -37.557795125315735 607 | ] 608 | }, 609 | "geometry_name": "geom", 610 | "properties": { 611 | "id": "00217050", 612 | "site": "Eastern Oval", 613 | "location": "Eastern Oval", 614 | "feat_type": "SG-Cricket Pitch", 615 | "easting": 752943, 616 | "northing": 5839391, 617 | "measure": 675, 618 | "unit": "Square Metres" 619 | } 620 | }, 621 | { 622 | "type": "Feature", 623 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.29", 624 | "geometry": { 625 | "type": "Point", 626 | "coordinates": [ 627 | 143.8619922916772, 628 | -37.5581441829533 629 | ] 630 | }, 631 | "geometry_name": "geom", 632 | "properties": { 633 | "id": "00217051", 634 | "site": "Eastern Oval", 635 | "location": "Eastern Oval", 636 | "feat_type": "SG-Cricket Pitch", 637 | "easting": 752802, 638 | "northing": 5839357, 639 | "measure": 54.1, 640 | "unit": "Square Metres" 641 | } 642 | }, 643 | { 644 | "type": "Feature", 645 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.30", 646 | "geometry": { 647 | "type": "Point", 648 | "coordinates": [ 649 | 143.8621425146391, 650 | -37.55815774528428 651 | ] 652 | }, 653 | "geometry_name": "geom", 654 | "properties": { 655 | "id": "00217052", 656 | "site": "Eastern Oval", 657 | "location": "Eastern Oval", 658 | "feat_type": "SG-Cricket Pitch", 659 | "easting": 752813, 660 | "northing": 5839357, 661 | "measure": 49.3, 662 | "unit": "Square Metres" 663 | } 664 | }, 665 | { 666 | "type": "Feature", 667 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.31", 668 | "geometry": { 669 | "type": "Point", 670 | "coordinates": [ 671 | 143.86203646478185, 672 | -37.558152210215944 673 | ] 674 | }, 675 | "geometry_name": "geom", 676 | "properties": { 677 | "id": "00217053", 678 | "site": "Eastern Oval", 679 | "location": "Eastern Oval", 680 | "feat_type": "SG-Cricket Pitch", 681 | "easting": 752814, 682 | "northing": 5839358, 683 | "measure": 51.1, 684 | "unit": "Square Metres" 685 | } 686 | }, 687 | { 688 | "type": "Feature", 689 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.32", 690 | "geometry": { 691 | "type": "Point", 692 | "coordinates": [ 693 | 143.86208763492934, 694 | -37.558157725410396 695 | ] 696 | }, 697 | "geometry_name": "geom", 698 | "properties": { 699 | "id": "00217054", 700 | "site": "Eastern Oval", 701 | "location": "Eastern Oval", 702 | "feat_type": "SG-Cricket Pitch", 703 | "easting": 752802, 704 | "northing": 5839357, 705 | "measure": 54.1, 706 | "unit": "Square Metres" 707 | } 708 | }, 709 | { 710 | "type": "Feature", 711 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.33", 712 | "geometry": { 713 | "type": "Point", 714 | "coordinates": [ 715 | 143.83205344924878, 716 | -37.6058667576942 717 | ] 718 | }, 719 | "geometry_name": "geom", 720 | "properties": { 721 | "id": "00217056", 722 | "site": "Grant Street Reserve", 723 | "location": "Grant Street Reserve", 724 | "feat_type": "SG-Cricket Pitch", 725 | "easting": 749997, 726 | "northing": 5834141, 727 | "measure": 6365.6, 728 | "unit": "Square Metres" 729 | } 730 | }, 731 | { 732 | "type": "Feature", 733 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.34", 734 | "geometry": { 735 | "type": "Point", 736 | "coordinates": [ 737 | 143.87344981029113, 738 | -37.52334257231717 739 | ] 740 | }, 741 | "geometry_name": "geom", 742 | "properties": { 743 | "id": "00217058", 744 | "site": "Invermay Recreation Reserve", 745 | "location": "Invermay Recreation Reserve", 746 | "feat_type": "SG-Cricket Pitch", 747 | "easting": 753933, 748 | "northing": 5843188, 749 | "measure": 43.5, 750 | "unit": "Square Metres" 751 | } 752 | }, 753 | { 754 | "type": "Feature", 755 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.35", 756 | "geometry": { 757 | "type": "Point", 758 | "coordinates": [ 759 | 143.8734682440079, 760 | -37.52336347443012 761 | ] 762 | }, 763 | "geometry_name": "geom", 764 | "properties": { 765 | "id": "00217059", 766 | "site": "Invermay Recreation Reserve", 767 | "location": "Invermay Recreation Reserve", 768 | "feat_type": "SG-Cricket Pitch", 769 | "easting": 753934, 770 | "northing": 5843186, 771 | "measure": 43.5, 772 | "unit": "Square Metres" 773 | } 774 | }, 775 | { 776 | "type": "Feature", 777 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.36", 778 | "geometry": { 779 | "type": "Point", 780 | "coordinates": [ 781 | 143.8734999465449, 782 | -37.52337765716084 783 | ] 784 | }, 785 | "geometry_name": "geom", 786 | "properties": { 787 | "id": "00217060", 788 | "site": "Invermay Recreation Reserve", 789 | "location": "Invermay Recreation Reserve", 790 | "feat_type": "SG-Cricket Pitch", 791 | "easting": 753937, 792 | "northing": 5843184, 793 | "measure": 43.6, 794 | "unit": "Square Metres" 795 | } 796 | }, 797 | { 798 | "type": "Feature", 799 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.37", 800 | "geometry": { 801 | "type": "Point", 802 | "coordinates": [ 803 | 143.87353312319365, 804 | -37.52339621832304 805 | ] 806 | }, 807 | "geometry_name": "geom", 808 | "properties": { 809 | "id": "00217061", 810 | "site": "Invermay Recreation Reserve", 811 | "location": "Invermay Recreation Reserve", 812 | "feat_type": "SG-Cricket Pitch", 813 | "easting": 753940, 814 | "northing": 5843182, 815 | "measure": 43.5, 816 | "unit": "Square Metres" 817 | } 818 | }, 819 | { 820 | "type": "Feature", 821 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.38", 822 | "geometry": { 823 | "type": "Point", 824 | "coordinates": [ 825 | 143.87453917569061, 826 | -37.5235089209386 827 | ] 828 | }, 829 | "geometry_name": "geom", 830 | "properties": { 831 | "id": "00217064", 832 | "site": "Invermay Recreation Reserve", 833 | "location": "Invermay Recreation Reserve", 834 | "feat_type": "SG-Cricket/Football", 835 | "easting": 754028, 836 | "northing": 5843167, 837 | "measure": 14808, 838 | "unit": "Square Metres" 839 | } 840 | }, 841 | { 842 | "type": "Feature", 843 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.39", 844 | "geometry": { 845 | "type": "Point", 846 | "coordinates": [ 847 | 143.87622542400413, 848 | -37.52360539858067 849 | ] 850 | }, 851 | "geometry_name": "geom", 852 | "properties": { 853 | "id": "00217065", 854 | "site": "Invermay Recreation Reserve", 855 | "location": "Invermay Recreation Reserve", 856 | "feat_type": "SG-Equestrian", 857 | "easting": 754177, 858 | "northing": 5843152, 859 | "measure": 1246.8, 860 | "unit": "Square Metres" 861 | } 862 | }, 863 | { 864 | "type": "Feature", 865 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.40", 866 | "geometry": { 867 | "type": "Point", 868 | "coordinates": [ 869 | 143.87671997316576, 870 | -37.52330028612133 871 | ] 872 | }, 873 | "geometry_name": "geom", 874 | "properties": { 875 | "id": "00217066", 876 | "site": "Invermay Recreation Reserve", 877 | "location": "Invermay Recreation Reserve", 878 | "feat_type": "SG-Equestrian", 879 | "easting": 754222, 880 | "northing": 5843184, 881 | "measure": 241.1, 882 | "unit": "Square Metres" 883 | } 884 | }, 885 | { 886 | "type": "Feature", 887 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.41", 888 | "geometry": { 889 | "type": "Point", 890 | "coordinates": [ 891 | 143.71179633414158, 892 | -37.42167836510769 893 | ] 894 | }, 895 | "geometry_name": "geom", 896 | "properties": { 897 | "id": "00217068", 898 | "site": "Learmonth Recreation Reserve", 899 | "location": "Learmonth Recreation Reserve", 900 | "feat_type": "SG-Cricket/Football", 901 | "easting": 739969, 902 | "northing": 5854895, 903 | "measure": 19019.2, 904 | "unit": "Square Metres" 905 | } 906 | }, 907 | { 908 | "type": "Feature", 909 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.42", 910 | "geometry": { 911 | "type": "Point", 912 | "coordinates": [ 913 | 143.7116899880685, 914 | -37.42158674935046 915 | ] 916 | }, 917 | "geometry_name": "geom", 918 | "properties": { 919 | "id": "00217069", 920 | "site": "Learmonth Recreation Reserve", 921 | "location": "Learmonth Recreation Reserve", 922 | "feat_type": "SG-Cricket Pitch", 923 | "easting": 739960, 924 | "northing": 5854905, 925 | "measure": 63.6, 926 | "unit": "Square Metres" 927 | } 928 | }, 929 | { 930 | "type": "Feature", 931 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.43", 932 | "geometry": { 933 | "type": "Point", 934 | "coordinates": [ 935 | 143.7104366088022, 936 | -37.42041304371741 937 | ] 938 | }, 939 | "geometry_name": "geom", 940 | "properties": { 941 | "id": "00217070", 942 | "site": "Learmonth Recreation Reserve", 943 | "location": "Learmonth Recreation Reserve", 944 | "feat_type": "SG-Cricket Pitch", 945 | "easting": 739853, 946 | "northing": 5855039, 947 | "measure": 55.9, 948 | "unit": "Square Metres" 949 | } 950 | }, 951 | { 952 | "type": "Feature", 953 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.44", 954 | "geometry": { 955 | "type": "Point", 956 | "coordinates": [ 957 | 143.7100945203758, 958 | -37.42183138736676 959 | ] 960 | }, 961 | "geometry_name": "geom", 962 | "properties": { 963 | "id": "00217071", 964 | "site": "Learmonth Recreation Reserve", 965 | "location": "Learmonth Recreation Reserve", 966 | "feat_type": "SG-Cricket Pitch", 967 | "easting": 739818, 968 | "northing": 5854882, 969 | "measure": 60.6, 970 | "unit": "Square Metres" 971 | } 972 | }, 973 | { 974 | "type": "Feature", 975 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.45", 976 | "geometry": { 977 | "type": "Point", 978 | "coordinates": [ 979 | 143.86583648909217, 980 | -37.56890900702913 981 | ] 982 | }, 983 | "geometry_name": "geom", 984 | "properties": { 985 | "id": "00217072", 986 | "site": "Llanberris Athletics Reserve", 987 | "location": "Llanberris Athletics Reserve", 988 | "feat_type": "SG-Athletics", 989 | "easting": 753105, 990 | "northing": 5838152, 991 | "measure": 214.2, 992 | "unit": "Square Metres" 993 | } 994 | }, 995 | { 996 | "type": "Feature", 997 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.93", 998 | "geometry": { 999 | "type": "Point", 1000 | "coordinates": [ 1001 | 143.88410236348244, 1002 | -37.55634601495133 1003 | ] 1004 | }, 1005 | "geometry_name": "geom", 1006 | "properties": { 1007 | "id": "00217140", 1008 | "site": "Russell Square", 1009 | "location": "Russell Square", 1010 | "feat_type": "SG-Cricket Pitch", 1011 | "easting": 754762, 1012 | "northing": 5839497, 1013 | "measure": 48.6, 1014 | "unit": "Square Metres" 1015 | } 1016 | }, 1017 | { 1018 | "type": "Feature", 1019 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.46", 1020 | "geometry": { 1021 | "type": "Point", 1022 | "coordinates": [ 1023 | 143.8660849201835, 1024 | -37.5686958585044 1025 | ] 1026 | }, 1027 | "geometry_name": "geom", 1028 | "properties": { 1029 | "id": "00217073", 1030 | "site": "Llanberris Athletics Reserve", 1031 | "location": "Llanberris Athletics Reserve", 1032 | "feat_type": "SG-Athletics", 1033 | "easting": 753128, 1034 | "northing": 5838175, 1035 | "measure": 211.2, 1036 | "unit": "Square Metres" 1037 | } 1038 | }, 1039 | { 1040 | "type": "Feature", 1041 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.47", 1042 | "geometry": { 1043 | "type": "Point", 1044 | "coordinates": [ 1045 | 143.86318994208784, 1046 | -37.54314891946381 1047 | ] 1048 | }, 1049 | "geometry_name": "geom", 1050 | "properties": { 1051 | "id": "00217075", 1052 | "site": "Marks Reserve", 1053 | "location": "MARKS RESERVE", 1054 | "feat_type": "SG-Cricket Pitch", 1055 | "easting": 752959, 1056 | "northing": 5841018, 1057 | "measure": 0, 1058 | "unit": "Square Metres" 1059 | } 1060 | }, 1061 | { 1062 | "type": "Feature", 1063 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.48", 1064 | "geometry": { 1065 | "type": "Point", 1066 | "coordinates": [ 1067 | 143.84332440717208, 1068 | -37.60356872640164 1069 | ] 1070 | }, 1071 | "geometry_name": "geom", 1072 | "properties": { 1073 | "id": "00217076", 1074 | "site": "Marty Busch Reserve", 1075 | "location": "Marty Busch Reserve", 1076 | "feat_type": "SG-Cricket/Football", 1077 | "easting": 751000, 1078 | "northing": 5834366, 1079 | "measure": 16993.7, 1080 | "unit": "Square Metres" 1081 | } 1082 | }, 1083 | { 1084 | "type": "Feature", 1085 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.49", 1086 | "geometry": { 1087 | "type": "Point", 1088 | "coordinates": [ 1089 | 143.84568548845158, 1090 | -37.603650238474756 1091 | ] 1092 | }, 1093 | "geometry_name": "geom", 1094 | "properties": { 1095 | "id": "00217080", 1096 | "site": "Marty Busch Reserve", 1097 | "location": "Marty Busch Reserve", 1098 | "feat_type": "SG-Cricket Pitch", 1099 | "easting": 751208, 1100 | "northing": 5834350, 1101 | "measure": 81.9, 1102 | "unit": "Square Metres" 1103 | } 1104 | }, 1105 | { 1106 | "type": "Feature", 1107 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.50", 1108 | "geometry": { 1109 | "type": "Point", 1110 | "coordinates": [ 1111 | 143.8457157189432, 1112 | -37.603674012924095 1113 | ] 1114 | }, 1115 | "geometry_name": "geom", 1116 | "properties": { 1117 | "id": "00217081", 1118 | "site": "Marty Busch Reserve", 1119 | "location": "Marty Busch Reserve", 1120 | "feat_type": "SG-Cricket/Football", 1121 | "easting": 751211, 1122 | "northing": 5834348, 1123 | "measure": 18897.8, 1124 | "unit": "Square Metres" 1125 | } 1126 | }, 1127 | { 1128 | "type": "Feature", 1129 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.51", 1130 | "geometry": { 1131 | "type": "Point", 1132 | "coordinates": [ 1133 | 143.84448004595188, 1134 | -37.603864002482766 1135 | ] 1136 | }, 1137 | "geometry_name": "geom", 1138 | "properties": { 1139 | "id": "00217082", 1140 | "site": "Marty Busch Reserve", 1141 | "location": "Marty Busch Reserve", 1142 | "feat_type": "SG-Cricket Pitch", 1143 | "easting": 751101, 1144 | "northing": 5834330, 1145 | "measure": 57, 1146 | "unit": "Square Metres" 1147 | } 1148 | }, 1149 | { 1150 | "type": "Feature", 1151 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.52", 1152 | "geometry": { 1153 | "type": "Point", 1154 | "coordinates": [ 1155 | 143.84451264869173, 1156 | -37.60384465789084 1157 | ] 1158 | }, 1159 | "geometry_name": "geom", 1160 | "properties": { 1161 | "id": "00217083", 1162 | "site": "Marty Busch Reserve", 1163 | "location": "Marty Busch Reserve", 1164 | "feat_type": "SG-Cricket Pitch", 1165 | "easting": 751104, 1166 | "northing": 5834332, 1167 | "measure": 56.8, 1168 | "unit": "Square Metres" 1169 | } 1170 | }, 1171 | { 1172 | "type": "Feature", 1173 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.53", 1174 | "geometry": { 1175 | "type": "Point", 1176 | "coordinates": [ 1177 | 143.84445001587346, 1178 | -37.60388959118009 1179 | ] 1180 | }, 1181 | "geometry_name": "geom", 1182 | "properties": { 1183 | "id": "00217088", 1184 | "site": "Marty Busch Reserve", 1185 | "location": "Marty Busch Reserve", 1186 | "feat_type": "SG-Cricket Pitch", 1187 | "easting": 751098, 1188 | "northing": 5834327, 1189 | "measure": 31.1, 1190 | "unit": "Square Metres" 1191 | } 1192 | }, 1193 | { 1194 | "type": "Feature", 1195 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.54", 1196 | "geometry": { 1197 | "type": "Point", 1198 | "coordinates": [ 1199 | 143.8443976157335, 1200 | -37.60388445887188 1201 | ] 1202 | }, 1203 | "geometry_name": "geom", 1204 | "properties": { 1205 | "id": "00217089", 1206 | "site": "Marty Busch Reserve", 1207 | "location": "Marty Busch Reserve", 1208 | "feat_type": "SG-Cricket Pitch", 1209 | "easting": 751094, 1210 | "northing": 5834328, 1211 | "measure": 37.5, 1212 | "unit": "Square Metres" 1213 | } 1214 | }, 1215 | { 1216 | "type": "Feature", 1217 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.55", 1218 | "geometry": { 1219 | "type": "Point", 1220 | "coordinates": [ 1221 | 143.8723674458889, 1222 | -37.563373341741155 1223 | ] 1224 | }, 1225 | "geometry_name": "geom", 1226 | "properties": { 1227 | "id": "00217093", 1228 | "site": "McKenzie Reserve", 1229 | "location": "McKenzie Reserve", 1230 | "feat_type": "SG-Cricket/Football", 1231 | "easting": 753701, 1232 | "northing": 5838749, 1233 | "measure": 4363.8, 1234 | "unit": "Square Metres" 1235 | } 1236 | }, 1237 | { 1238 | "type": "Feature", 1239 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.56", 1240 | "geometry": { 1241 | "type": "Point", 1242 | "coordinates": [ 1243 | 143.87237790226195, 1244 | -37.563409843055865 1245 | ] 1246 | }, 1247 | "geometry_name": "geom", 1248 | "properties": { 1249 | "id": "00217094", 1250 | "site": "McKenzie Reserve", 1251 | "location": "McKenzie Reserve", 1252 | "feat_type": "SG-Cricket Pitch", 1253 | "easting": 753702, 1254 | "northing": 5838745, 1255 | "measure": 31.5, 1256 | "unit": "Square Metres" 1257 | } 1258 | }, 1259 | { 1260 | "type": "Feature", 1261 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.57", 1262 | "geometry": { 1263 | "type": "Point", 1264 | "coordinates": [ 1265 | 143.83444183589938, 1266 | -37.57944867436236 1267 | ] 1268 | }, 1269 | "geometry_name": "geom", 1270 | "properties": { 1271 | "id": "00217096", 1272 | "site": "Morshead Park", 1273 | "location": "Morshead Park", 1274 | "feat_type": "SG-Soccer", 1275 | "easting": 750297, 1276 | "northing": 5837066, 1277 | "measure": 6474.4, 1278 | "unit": "Square Metres" 1279 | } 1280 | }, 1281 | { 1282 | "type": "Feature", 1283 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.58", 1284 | "geometry": { 1285 | "type": "Point", 1286 | "coordinates": [ 1287 | 143.83428120308793, 1288 | -37.58030603405223 1289 | ] 1290 | }, 1291 | "geometry_name": "geom", 1292 | "properties": { 1293 | "id": "00217097", 1294 | "site": "Morshead Park", 1295 | "location": "Morshead Park", 1296 | "feat_type": "SG-Soccer", 1297 | "easting": 750280, 1298 | "northing": 5836972, 1299 | "measure": 12105.4, 1300 | "unit": "Square Metres" 1301 | } 1302 | }, 1303 | { 1304 | "type": "Feature", 1305 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.59", 1306 | "geometry": { 1307 | "type": "Point", 1308 | "coordinates": [ 1309 | 143.83179343995553, 1310 | -37.58085767235798 1311 | ] 1312 | }, 1313 | "geometry_name": "geom", 1314 | "properties": { 1315 | "id": "00217098", 1316 | "site": "Morshead Park", 1317 | "location": "Morshead Park", 1318 | "feat_type": "SG-Greyhound Slipping Track", 1319 | "easting": 750058, 1320 | "northing": 5836917, 1321 | "measure": 948.4, 1322 | "unit": "Square Metres" 1323 | } 1324 | }, 1325 | { 1326 | "type": "Feature", 1327 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.60", 1328 | "geometry": { 1329 | "type": "Point", 1330 | "coordinates": [ 1331 | 143.83328209580654, 1332 | -37.58010742913052 1333 | ] 1334 | }, 1335 | "geometry_name": "geom", 1336 | "properties": { 1337 | "id": "00217099", 1338 | "site": "Morshead Park", 1339 | "location": "Morshead Park", 1340 | "feat_type": "SG-Equestrian", 1341 | "easting": 750192, 1342 | "northing": 5836996, 1343 | "measure": 971.8, 1344 | "unit": "Square Metres" 1345 | } 1346 | }, 1347 | { 1348 | "type": "Feature", 1349 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.61", 1350 | "geometry": { 1351 | "type": "Point", 1352 | "coordinates": [ 1353 | 143.83487719898733, 1354 | -37.579511002961944 1355 | ] 1356 | }, 1357 | "geometry_name": "geom", 1358 | "properties": { 1359 | "id": "00217100", 1360 | "site": "Morshead Park", 1361 | "location": "Morshead Park", 1362 | "feat_type": "SG-Soccer", 1363 | "easting": 750335, 1364 | "northing": 5837058, 1365 | "measure": 2875.2, 1366 | "unit": "Square Metres" 1367 | } 1368 | }, 1369 | { 1370 | "type": "Feature", 1371 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.62", 1372 | "geometry": { 1373 | "type": "Point", 1374 | "coordinates": [ 1375 | 143.87488397368983, 1376 | -37.60532536389085 1377 | ] 1378 | }, 1379 | "geometry_name": "geom", 1380 | "properties": { 1381 | "id": "00217103", 1382 | "site": "Mount Clear Recreation Reserve", 1383 | "location": "MT CLEAR RECREATION RESERVE (MT CLEAR TECH RESERVE)", 1384 | "feat_type": "SG-Cricket Pitch", 1385 | "easting": 753781, 1386 | "northing": 5834086, 1387 | "measure": 0, 1388 | "unit": "Square Metres" 1389 | } 1390 | }, 1391 | { 1392 | "type": "Feature", 1393 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.63", 1394 | "geometry": { 1395 | "type": "Point", 1396 | "coordinates": [ 1397 | 143.87549177553507, 1398 | -37.60646753770219 1399 | ] 1400 | }, 1401 | "geometry_name": "geom", 1402 | "properties": { 1403 | "id": "00217104", 1404 | "site": "Mount Clear Recreation Reserve", 1405 | "location": "MT CLEAR RECREATION RESERVE (MT CLEAR TECH RESERVE)", 1406 | "feat_type": "SG-Cricket Pitch", 1407 | "easting": 753831, 1408 | "northing": 5833958, 1409 | "measure": 0, 1410 | "unit": "Square Metres" 1411 | } 1412 | }, 1413 | { 1414 | "type": "Feature", 1415 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.64", 1416 | "geometry": { 1417 | "type": "Point", 1418 | "coordinates": [ 1419 | 143.87545367810984, 1420 | -37.606455584213506 1421 | ] 1422 | }, 1423 | "geometry_name": "geom", 1424 | "properties": { 1425 | "id": "00217105", 1426 | "site": "Mount Clear Recreation Reserve", 1427 | "location": "MT CLEAR RECREATION RESERVE (MT CLEAR TECH RESERVE)", 1428 | "feat_type": "SG-Cricket Pitch", 1429 | "easting": 753827, 1430 | "northing": 5833959, 1431 | "measure": 0, 1432 | "unit": "Square Metres" 1433 | } 1434 | }, 1435 | { 1436 | "type": "Feature", 1437 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.65", 1438 | "geometry": { 1439 | "type": "Point", 1440 | "coordinates": [ 1441 | 143.87541400365308, 1442 | -37.60643420998169 1443 | ] 1444 | }, 1445 | "geometry_name": "geom", 1446 | "properties": { 1447 | "id": "00217106", 1448 | "site": "Mount Clear Recreation Reserve", 1449 | "location": "MT CLEAR RECREATION RESERVE (MT CLEAR TECH RESERVE)", 1450 | "feat_type": "SG-Cricket Pitch", 1451 | "easting": 753824, 1452 | "northing": 5833961, 1453 | "measure": 0, 1454 | "unit": "Square Metres" 1455 | } 1456 | }, 1457 | { 1458 | "type": "Feature", 1459 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.66", 1460 | "geometry": { 1461 | "type": "Point", 1462 | "coordinates": [ 1463 | 143.87483637472965, 1464 | -37.606892425377545 1465 | ] 1466 | }, 1467 | "geometry_name": "geom", 1468 | "properties": { 1469 | "id": "00217107", 1470 | "site": "Mount Clear Recreation Reserve", 1471 | "location": "MT CLEAR RECREATION RESERVE (MT CLEAR TECH RESERVE)", 1472 | "feat_type": "SG-Cricket Pitch", 1473 | "easting": 753771, 1474 | "northing": 5833912, 1475 | "measure": 0, 1476 | "unit": "Square Metres" 1477 | } 1478 | }, 1479 | { 1480 | "type": "Feature", 1481 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.67", 1482 | "geometry": { 1483 | "type": "Point", 1484 | "coordinates": [ 1485 | 143.87491286077088, 1486 | -37.60530673219985 1487 | ] 1488 | }, 1489 | "geometry_name": "geom", 1490 | "properties": { 1491 | "id": "00217108", 1492 | "site": "Mount Clear Recreation Reserve", 1493 | "location": "MT CLEAR RECREATION RESERVE (MT CLEAR TECH RESERVE)", 1494 | "feat_type": "SG-Cricket/Football", 1495 | "easting": 753783, 1496 | "northing": 5834088, 1497 | "measure": 12666, 1498 | "unit": "Square Metres" 1499 | } 1500 | }, 1501 | { 1502 | "type": "Feature", 1503 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.68", 1504 | "geometry": { 1505 | "type": "Point", 1506 | "coordinates": [ 1507 | 143.87486029495585, 1508 | -37.60691697660847 1509 | ] 1510 | }, 1511 | "geometry_name": "geom", 1512 | "properties": { 1513 | "id": "00217109", 1514 | "site": "Mount Clear Recreation Reserve", 1515 | "location": "MT CLEAR RECREATION RESERVE (MT CLEAR TECH RESERVE)", 1516 | "feat_type": "SG-Cricket/Football", 1517 | "easting": 753773, 1518 | "northing": 5833909, 1519 | "measure": 14748, 1520 | "unit": "Square Metres" 1521 | } 1522 | }, 1523 | { 1524 | "type": "Feature", 1525 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.69", 1526 | "geometry": { 1527 | "type": "Point", 1528 | "coordinates": [ 1529 | 143.85091187220084, 1530 | -37.577770135973985 1531 | ] 1532 | }, 1533 | "geometry_name": "geom", 1534 | "properties": { 1535 | "id": "00217112", 1536 | "site": "Mount Pleasant Reserve", 1537 | "location": "Mt Pleasant Reserve", 1538 | "feat_type": "SG-Cricket/Football", 1539 | "easting": 751757, 1540 | "northing": 5837209, 1541 | "measure": 11051.7, 1542 | "unit": "Square Metres" 1543 | } 1544 | }, 1545 | { 1546 | "type": "Feature", 1547 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.70", 1548 | "geometry": { 1549 | "type": "Point", 1550 | "coordinates": [ 1551 | 143.85098099112756, 1552 | -37.57788359818073 1553 | ] 1554 | }, 1555 | "geometry_name": "geom", 1556 | "properties": { 1557 | "id": "00217113", 1558 | "site": "Mount Pleasant Reserve", 1559 | "location": "Mt Pleasant Reserve", 1560 | "feat_type": "SG-Cricket Pitch", 1561 | "easting": 751763, 1562 | "northing": 5837196, 1563 | "measure": 57.6, 1564 | "unit": "Square Metres" 1565 | } 1566 | }, 1567 | { 1568 | "type": "Feature", 1569 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.71", 1570 | "geometry": { 1571 | "type": "Point", 1572 | "coordinates": [ 1573 | 143.82944054403768, 1574 | -37.60121812592906 1575 | ] 1576 | }, 1577 | "geometry_name": "geom", 1578 | "properties": { 1579 | "id": "00217101", 1580 | "site": "MR Power Reserve", 1581 | "location": "M.R Power Park", 1582 | "feat_type": "SG-Equestrian", 1583 | "easting": 749782, 1584 | "northing": 5834664, 1585 | "measure": 307.9, 1586 | "unit": "Square Metres" 1587 | } 1588 | }, 1589 | { 1590 | "type": "Feature", 1591 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.72", 1592 | "geometry": { 1593 | "type": "Point", 1594 | "coordinates": [ 1595 | 143.82862986049298, 1596 | -37.60077208683855 1597 | ] 1598 | }, 1599 | "geometry_name": "geom", 1600 | "properties": { 1601 | "id": "00217102", 1602 | "site": "MR Power Reserve", 1603 | "location": "M.R Power Park", 1604 | "feat_type": "SG-Equestrian", 1605 | "easting": 749712, 1606 | "northing": 5834715, 1607 | "measure": 6546.6, 1608 | "unit": "Square Metres" 1609 | } 1610 | }, 1611 | { 1612 | "type": "Feature", 1613 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.73", 1614 | "geometry": { 1615 | "type": "Point", 1616 | "coordinates": [ 1617 | 143.8458300745436, 1618 | -37.53694702287387 1619 | ] 1620 | }, 1621 | "geometry_name": "geom", 1622 | "properties": { 1623 | "id": "00217118", 1624 | "site": "Northern Oval Reserve", 1625 | "location": "NORTHERN OVAL", 1626 | "feat_type": "SG-Cricket Pitch", 1627 | "easting": 751446, 1628 | "northing": 5841753, 1629 | "measure": 773, 1630 | "unit": "Square Metres" 1631 | } 1632 | }, 1633 | { 1634 | "type": "Feature", 1635 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.74", 1636 | "geometry": { 1637 | "type": "Point", 1638 | "coordinates": [ 1639 | 143.8464589238914, 1640 | -37.53757905288067 1641 | ] 1642 | }, 1643 | "geometry_name": "geom", 1644 | "properties": { 1645 | "id": "00217119", 1646 | "site": "Northern Oval Reserve", 1647 | "location": "NORTHERN OVAL", 1648 | "feat_type": "SG-Football", 1649 | "easting": 751499, 1650 | "northing": 5841681, 1651 | "measure": 14920, 1652 | "unit": "Square Metres" 1653 | } 1654 | }, 1655 | { 1656 | "type": "Feature", 1657 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.75", 1658 | "geometry": { 1659 | "type": "Point", 1660 | "coordinates": [ 1661 | 143.8481163882728, 1662 | -37.53932605886706 1663 | ] 1664 | }, 1665 | "geometry_name": "geom", 1666 | "properties": { 1667 | "id": "00217121", 1668 | "site": "Northern Oval Reserve", 1669 | "location": "NORTHERN OVAL", 1670 | "feat_type": "SG-Football", 1671 | "easting": 751640, 1672 | "northing": 5841483, 1673 | "measure": 21121, 1674 | "unit": "Square Metres" 1675 | } 1676 | }, 1677 | { 1678 | "type": "Feature", 1679 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.76", 1680 | "geometry": { 1681 | "type": "Point", 1682 | "coordinates": [ 1683 | 143.83557155051696, 1684 | -37.57152957485565 1685 | ] 1686 | }, 1687 | "geometry_name": "geom", 1688 | "properties": { 1689 | "id": "00217123", 1690 | "site": "Pleasant Street Reserve", 1691 | "location": "Pleasant Street Reserve", 1692 | "feat_type": "SG-Soccer", 1693 | "easting": 750423, 1694 | "northing": 5837942, 1695 | "measure": 5572.7, 1696 | "unit": "Square Metres" 1697 | } 1698 | }, 1699 | { 1700 | "type": "Feature", 1701 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.77", 1702 | "geometry": { 1703 | "type": "Point", 1704 | "coordinates": [ 1705 | 143.8363005629905, 1706 | -37.57134646499646 1707 | ] 1708 | }, 1709 | "geometry_name": "geom", 1710 | "properties": { 1711 | "id": "00217124", 1712 | "site": "Pleasant Street Reserve", 1713 | "location": "Pleasant Street Reserve", 1714 | "feat_type": "SG-Soccer", 1715 | "easting": 750488, 1716 | "northing": 5837961, 1717 | "measure": 1460.2, 1718 | "unit": "Square Metres" 1719 | } 1720 | }, 1721 | { 1722 | "type": "Feature", 1723 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.78", 1724 | "geometry": { 1725 | "type": "Point", 1726 | "coordinates": [ 1727 | 143.83623609037588, 1728 | -37.57196710463816 1729 | ] 1730 | }, 1731 | "geometry_name": "geom", 1732 | "properties": { 1733 | "id": "00217125", 1734 | "site": "Pleasant Street Reserve", 1735 | "location": "Pleasant Street Reserve", 1736 | "feat_type": "SG-Soccer", 1737 | "easting": 750480, 1738 | "northing": 5837892, 1739 | "measure": 5173.1, 1740 | "unit": "Square Metres" 1741 | } 1742 | }, 1743 | { 1744 | "type": "Feature", 1745 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.79", 1746 | "geometry": { 1747 | "type": "Point", 1748 | "coordinates": [ 1749 | 143.81940093752982, 1750 | -37.5497663395101 1751 | ] 1752 | }, 1753 | "geometry_name": "geom", 1754 | "properties": { 1755 | "id": "00217126", 1756 | "site": "Prince of Wales Park", 1757 | "location": "Prince of Wales Park", 1758 | "feat_type": "SG-Hockey", 1759 | "easting": 749067, 1760 | "northing": 5840400, 1761 | "measure": 6177, 1762 | "unit": "Square Metres" 1763 | } 1764 | }, 1765 | { 1766 | "type": "Feature", 1767 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.80", 1768 | "geometry": { 1769 | "type": "Point", 1770 | "coordinates": [ 1771 | 143.81761724137482, 1772 | -37.54952021432212 1773 | ] 1774 | }, 1775 | "geometry_name": "geom", 1776 | "properties": { 1777 | "id": "00217127", 1778 | "site": "Prince of Wales Park", 1779 | "location": "Prince of Wales Park", 1780 | "feat_type": "SG-Baseball/Softball", 1781 | "easting": 748910, 1782 | "northing": 5840433, 1783 | "measure": 10760, 1784 | "unit": "Square Metres" 1785 | } 1786 | }, 1787 | { 1788 | "type": "Feature", 1789 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.81", 1790 | "geometry": { 1791 | "type": "Point", 1792 | "coordinates": [ 1793 | 143.8169486877864, 1794 | -37.548620870939224 1795 | ] 1796 | }, 1797 | "geometry_name": "geom", 1798 | "properties": { 1799 | "id": "00217128", 1800 | "site": "Prince of Wales Park", 1801 | "location": "Prince of Wales Park", 1802 | "feat_type": "SG-Baseball/Softball", 1803 | "easting": 748854, 1804 | "northing": 5840534, 1805 | "measure": 10723.1, 1806 | "unit": "Square Metres" 1807 | } 1808 | }, 1809 | { 1810 | "type": "Feature", 1811 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.82", 1812 | "geometry": { 1813 | "type": "Point", 1814 | "coordinates": [ 1815 | 143.81538607632515, 1816 | -37.54839141438024 1817 | ] 1818 | }, 1819 | "geometry_name": "geom", 1820 | "properties": { 1821 | "id": "00217129", 1822 | "site": "Prince of Wales Park", 1823 | "location": "Prince of Wales Park", 1824 | "feat_type": "SG-Baseball/Softball", 1825 | "easting": 748717, 1826 | "northing": 5840564, 1827 | "measure": 10848.9, 1828 | "unit": "Square Metres" 1829 | } 1830 | }, 1831 | { 1832 | "type": "Feature", 1833 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.83", 1834 | "geometry": { 1835 | "type": "Point", 1836 | "coordinates": [ 1837 | 143.81650001901906, 1838 | -37.5501453534497 1839 | ] 1840 | }, 1841 | "geometry_name": "geom", 1842 | "properties": { 1843 | "id": "00217130", 1844 | "site": "Prince of Wales Park", 1845 | "location": "Prince of Wales Park", 1846 | "feat_type": "SG-Baseball/Softball", 1847 | "easting": 748810, 1848 | "northing": 5840366, 1849 | "measure": 12041.9, 1850 | "unit": "Square Metres" 1851 | } 1852 | }, 1853 | { 1854 | "type": "Feature", 1855 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.84", 1856 | "geometry": { 1857 | "type": "Point", 1858 | "coordinates": [ 1859 | 143.88421896979375, 1860 | -37.555466338730504 1861 | ] 1862 | }, 1863 | "geometry_name": "geom", 1864 | "properties": { 1865 | "id": "00217131", 1866 | "site": "Russell Square", 1867 | "location": "Russell Square", 1868 | "feat_type": "SG-Soccer", 1869 | "easting": 754775, 1870 | "northing": 5839594, 1871 | "measure": 6597.1, 1872 | "unit": "Square Metres" 1873 | } 1874 | }, 1875 | { 1876 | "type": "Feature", 1877 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.85", 1878 | "geometry": { 1879 | "type": "Point", 1880 | "coordinates": [ 1881 | 143.88513657686374, 1882 | -37.55657432376788 1883 | ] 1884 | }, 1885 | "geometry_name": "geom", 1886 | "properties": { 1887 | "id": "00217132", 1888 | "site": "Russell Square", 1889 | "location": "Russell Square", 1890 | "feat_type": "SG-Football", 1891 | "easting": 754852, 1892 | "northing": 5839469, 1893 | "measure": 12889.7, 1894 | "unit": "Square Metres" 1895 | } 1896 | }, 1897 | { 1898 | "type": "Feature", 1899 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.86", 1900 | "geometry": { 1901 | "type": "Point", 1902 | "coordinates": [ 1903 | 143.88550035526268, 1904 | -37.555384014279646 1905 | ] 1906 | }, 1907 | "geometry_name": "geom", 1908 | "properties": { 1909 | "id": "00217133", 1910 | "site": "Russell Square", 1911 | "location": "Russell Square", 1912 | "feat_type": "SG-Soccer", 1913 | "easting": 754889, 1914 | "northing": 5839600, 1915 | "measure": 2276, 1916 | "unit": "Square Metres" 1917 | } 1918 | }, 1919 | { 1920 | "type": "Feature", 1921 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.87", 1922 | "geometry": { 1923 | "type": "Point", 1924 | "coordinates": [ 1925 | 143.8850524733157, 1926 | -37.55534523975586 1927 | ] 1928 | }, 1929 | "geometry_name": "geom", 1930 | "properties": { 1931 | "id": "00217134", 1932 | "site": "Russell Square", 1933 | "location": "Russell Square", 1934 | "feat_type": "SG-Soccer", 1935 | "easting": 754849, 1936 | "northing": 5839605, 1937 | "measure": 2282.2, 1938 | "unit": "Square Metres" 1939 | } 1940 | }, 1941 | { 1942 | "type": "Feature", 1943 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.88", 1944 | "geometry": { 1945 | "type": "Point", 1946 | "coordinates": [ 1947 | 143.88384113890845, 1948 | -37.55502973042545 1949 | ] 1950 | }, 1951 | "geometry_name": "geom", 1952 | "properties": { 1953 | "id": "00217135", 1954 | "site": "Russell Square", 1955 | "location": "Russell Square", 1956 | "feat_type": "SG-Cricket Pitch", 1957 | "easting": 754743, 1958 | "northing": 5839643, 1959 | "measure": 5327.4, 1960 | "unit": "Square Metres" 1961 | } 1962 | }, 1963 | { 1964 | "type": "Feature", 1965 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.89", 1966 | "geometry": { 1967 | "type": "Point", 1968 | "coordinates": [ 1969 | 143.8850430337039, 1970 | -37.554807916214926 1971 | ] 1972 | }, 1973 | "geometry_name": "geom", 1974 | "properties": { 1975 | "id": "00217136", 1976 | "site": "Russell Square", 1977 | "location": "Russell Square", 1978 | "feat_type": "SG-Cricket Pitch", 1979 | "easting": 754850, 1980 | "northing": 5839665, 1981 | "measure": 5326.6, 1982 | "unit": "Square Metres" 1983 | } 1984 | }, 1985 | { 1986 | "type": "Feature", 1987 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.90", 1988 | "geometry": { 1989 | "type": "Point", 1990 | "coordinates": [ 1991 | 143.88439149287296, 1992 | -37.55478547052537 1993 | ] 1994 | }, 1995 | "geometry_name": "geom", 1996 | "properties": { 1997 | "id": "00217137", 1998 | "site": "Russell Square", 1999 | "location": "Russell Square", 2000 | "feat_type": "SG-Soccer", 2001 | "easting": 754793, 2002 | "northing": 5839669, 2003 | "measure": 6647.7, 2004 | "unit": "Square Metres" 2005 | } 2006 | }, 2007 | { 2008 | "type": "Feature", 2009 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.91", 2010 | "geometry": { 2011 | "type": "Point", 2012 | "coordinates": [ 2013 | 143.88419213559695, 2014 | -37.55633607265091 2015 | ] 2016 | }, 2017 | "geometry_name": "geom", 2018 | "properties": { 2019 | "id": "00217138", 2020 | "site": "Russell Square", 2021 | "location": "Russell Square", 2022 | "feat_type": "SG-Cricket Pitch", 2023 | "easting": 754770, 2024 | "northing": 5839498, 2025 | "measure": 45.4, 2026 | "unit": "Square Metres" 2027 | } 2028 | }, 2029 | { 2030 | "type": "Feature", 2031 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.92", 2032 | "geometry": { 2033 | "type": "Point", 2034 | "coordinates": [ 2035 | 143.8841451818787, 2036 | -37.556345508666006 2037 | ] 2038 | }, 2039 | "geometry_name": "geom", 2040 | "properties": { 2041 | "id": "00217139", 2042 | "site": "Russell Square", 2043 | "location": "Russell Square", 2044 | "feat_type": "SG-Cricket Pitch", 2045 | "easting": 754766, 2046 | "northing": 5839497, 2047 | "measure": 47.4, 2048 | "unit": "Square Metres" 2049 | } 2050 | }, 2051 | { 2052 | "type": "Feature", 2053 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.94", 2054 | "geometry": { 2055 | "type": "Point", 2056 | "coordinates": [ 2057 | 143.88405759000352, 2058 | -37.556344226732364 2059 | ] 2060 | }, 2061 | "geometry_name": "geom", 2062 | "properties": { 2063 | "id": "00217141", 2064 | "site": "Russell Square", 2065 | "location": "Russell Square", 2066 | "feat_type": "SG-Cricket Pitch", 2067 | "easting": 754758, 2068 | "northing": 5839497, 2069 | "measure": 45.5, 2070 | "unit": "Square Metres" 2071 | } 2072 | }, 2073 | { 2074 | "type": "Feature", 2075 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.95", 2076 | "geometry": { 2077 | "type": "Point", 2078 | "coordinates": [ 2079 | 143.8215793706956, 2080 | -37.55247045129143 2081 | ] 2082 | }, 2083 | "geometry_name": "geom", 2084 | "properties": { 2085 | "id": "00217146", 2086 | "site": "South Gardens Reserve", 2087 | "location": "Ballarat Botanical Gardens (South)", 2088 | "feat_type": "SG-Cricket Pitch", 2089 | "easting": 749251, 2090 | "northing": 5840095, 2091 | "measure": 3850.7, 2092 | "unit": "Square Metres" 2093 | } 2094 | }, 2095 | { 2096 | "type": "Feature", 2097 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.96", 2098 | "geometry": { 2099 | "type": "Point", 2100 | "coordinates": [ 2101 | 143.88508786619818, 2102 | -37.576913274473235 2103 | ] 2104 | }, 2105 | "geometry_name": "geom", 2106 | "properties": { 2107 | "id": "00217148", 2108 | "site": "Sparrow Ground Reserve", 2109 | "location": "Sparrow Ground Reserve", 2110 | "feat_type": "SG-Cricket/Football", 2111 | "easting": 754779, 2112 | "northing": 5837211, 2113 | "measure": 11530.3, 2114 | "unit": "Square Metres" 2115 | } 2116 | }, 2117 | { 2118 | "type": "Feature", 2119 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.97", 2120 | "geometry": { 2121 | "type": "Point", 2122 | "coordinates": [ 2123 | 143.88508627314627, 2124 | -37.57691070089873 2125 | ] 2126 | }, 2127 | "geometry_name": "geom", 2128 | "properties": { 2129 | "id": "00217149", 2130 | "site": "Sparrow Ground Reserve", 2131 | "location": "Sparrow Ground Reserve", 2132 | "feat_type": "SG-Cricket Pitch", 2133 | "easting": 754779, 2134 | "northing": 5837212, 2135 | "measure": 49.6, 2136 | "unit": "Square Metres" 2137 | } 2138 | }, 2139 | { 2140 | "type": "Feature", 2141 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.98", 2142 | "geometry": { 2143 | "type": "Point", 2144 | "coordinates": [ 2145 | 143.8382761030041, 2146 | -37.59170498882344 2147 | ] 2148 | }, 2149 | "geometry_name": "geom", 2150 | "properties": { 2151 | "id": "00217207", 2152 | "site": "St Georges Reserve", 2153 | "location": "St Georges Reserve", 2154 | "feat_type": "SG-Soccer", 2155 | "easting": 750594, 2156 | "northing": 5835696, 2157 | "measure": 6532.4, 2158 | "unit": "Square Metres" 2159 | } 2160 | }, 2161 | { 2162 | "type": "Feature", 2163 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.99", 2164 | "geometry": { 2165 | "type": "Point", 2166 | "coordinates": [ 2167 | 143.83803857542694, 2168 | -37.590882341973334 2169 | ] 2170 | }, 2171 | "geometry_name": "geom", 2172 | "properties": { 2173 | "id": "00217208", 2174 | "site": "St Georges Reserve", 2175 | "location": "St Georges Reserve", 2176 | "feat_type": "SG-Soccer", 2177 | "easting": 750576, 2178 | "northing": 5835788, 2179 | "measure": 1466.8, 2180 | "unit": "Square Metres" 2181 | } 2182 | }, 2183 | { 2184 | "type": "Feature", 2185 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.100", 2186 | "geometry": { 2187 | "type": "Point", 2188 | "coordinates": [ 2189 | 143.838582522664, 2190 | -37.59088609768766 2191 | ] 2192 | }, 2193 | "geometry_name": "geom", 2194 | "properties": { 2195 | "id": "00217209", 2196 | "site": "St Georges Reserve", 2197 | "location": "St Georges Reserve", 2198 | "feat_type": "SG-Soccer", 2199 | "easting": 750624, 2200 | "northing": 5835786, 2201 | "measure": 1378.4, 2202 | "unit": "Square Metres" 2203 | } 2204 | }, 2205 | { 2206 | "type": "Feature", 2207 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.101", 2208 | "geometry": { 2209 | "type": "Point", 2210 | "coordinates": [ 2211 | 143.8391807268041, 2212 | -37.59172997034195 2213 | ] 2214 | }, 2215 | "geometry_name": "geom", 2216 | "properties": { 2217 | "id": "00217210", 2218 | "site": "St Georges Reserve", 2219 | "location": "St Georges Reserve", 2220 | "feat_type": "SG-Soccer", 2221 | "easting": 750674, 2222 | "northing": 5835691, 2223 | "measure": 5553, 2224 | "unit": "Square Metres" 2225 | } 2226 | }, 2227 | { 2228 | "type": "Feature", 2229 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.102", 2230 | "geometry": { 2231 | "type": "Point", 2232 | "coordinates": [ 2233 | 143.83762778732896, 2234 | -37.57192894905851 2235 | ] 2236 | }, 2237 | "geometry_name": "geom", 2238 | "properties": { 2239 | "id": "00217150", 2240 | "site": "Trekardo Park", 2241 | "location": "Trekardo Park", 2242 | "feat_type": "SG-Soccer", 2243 | "easting": 750603, 2244 | "northing": 5837892, 2245 | "measure": 8490.9, 2246 | "unit": "Square Metres" 2247 | } 2248 | }, 2249 | { 2250 | "type": "Feature", 2251 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.103", 2252 | "geometry": { 2253 | "type": "Point", 2254 | "coordinates": [ 2255 | 143.83803957950863, 2256 | -37.57100302074683 2257 | ] 2258 | }, 2259 | "geometry_name": "geom", 2260 | "properties": { 2261 | "id": "00217151", 2262 | "site": "Trekardo Park", 2263 | "location": "Trekardo Park", 2264 | "feat_type": "SG-Soccer", 2265 | "easting": 750643, 2266 | "northing": 5837994, 2267 | "measure": 6414.7, 2268 | "unit": "Square Metres" 2269 | } 2270 | }, 2271 | { 2272 | "type": "Feature", 2273 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.104", 2274 | "geometry": { 2275 | "type": "Point", 2276 | "coordinates": [ 2277 | 143.8245520251905, 2278 | -37.56075850676637 2279 | ] 2280 | }, 2281 | "geometry_name": "geom", 2282 | "properties": { 2283 | "id": "00217152", 2284 | "site": "Victoria Park", 2285 | "location": "Victoria Park", 2286 | "feat_type": "SG-Cricket Pitch", 2287 | "easting": 749486, 2288 | "northing": 5839167, 2289 | "measure": 52.4, 2290 | "unit": "Square Metres" 2291 | } 2292 | }, 2293 | { 2294 | "type": "Feature", 2295 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.105", 2296 | "geometry": { 2297 | "type": "Point", 2298 | "coordinates": [ 2299 | 143.82561660129477, 2300 | -37.56201272735606 2301 | ] 2302 | }, 2303 | "geometry_name": "geom", 2304 | "properties": { 2305 | "id": "00217153", 2306 | "site": "Victoria Park", 2307 | "location": "Victoria Park", 2308 | "feat_type": "SG-Cricket Pitch", 2309 | "easting": 749575, 2310 | "northing": 5839025, 2311 | "measure": 51, 2312 | "unit": "Square Metres" 2313 | } 2314 | }, 2315 | { 2316 | "type": "Feature", 2317 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.106", 2318 | "geometry": { 2319 | "type": "Point", 2320 | "coordinates": [ 2321 | 143.82058914649753, 2322 | -37.55836280881029 2323 | ] 2324 | }, 2325 | "geometry_name": "geom", 2326 | "properties": { 2327 | "id": "00217154", 2328 | "site": "Victoria Park", 2329 | "location": "Victoria Park", 2330 | "feat_type": "SG-Cricket Pitch", 2331 | "easting": 749144, 2332 | "northing": 5839443, 2333 | "measure": 49.5, 2334 | "unit": "Square Metres" 2335 | } 2336 | }, 2337 | { 2338 | "type": "Feature", 2339 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.107", 2340 | "geometry": { 2341 | "type": "Point", 2342 | "coordinates": [ 2343 | 143.8217968079505, 2344 | -37.558608430537376 2345 | ] 2346 | }, 2347 | "geometry_name": "geom", 2348 | "properties": { 2349 | "id": "00217155", 2350 | "site": "Victoria Park", 2351 | "location": "Victoria Park", 2352 | "feat_type": "SG-Cricket Pitch", 2353 | "easting": 749249, 2354 | "northing": 5839413, 2355 | "measure": 50.7, 2356 | "unit": "Square Metres" 2357 | } 2358 | }, 2359 | { 2360 | "type": "Feature", 2361 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.108", 2362 | "geometry": { 2363 | "type": "Point", 2364 | "coordinates": [ 2365 | 143.82031224685753, 2366 | -37.55942968798608 2367 | ] 2368 | }, 2369 | "geometry_name": "geom", 2370 | "properties": { 2371 | "id": "00217156", 2372 | "site": "Victoria Park", 2373 | "location": "Victoria Park", 2374 | "feat_type": "SG-Cricket Pitch", 2375 | "easting": 749115, 2376 | "northing": 5839326, 2377 | "measure": 51.3, 2378 | "unit": "Square Metres" 2379 | } 2380 | }, 2381 | { 2382 | "type": "Feature", 2383 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.109", 2384 | "geometry": { 2385 | "type": "Point", 2386 | "coordinates": [ 2387 | 143.82107111092884, 2388 | -37.56379185597508 2389 | ] 2390 | }, 2391 | "geometry_name": "geom", 2392 | "properties": { 2393 | "id": "00217157", 2394 | "site": "Victoria Park", 2395 | "location": "Victoria Park", 2396 | "feat_type": "SG-Cricket Pitch", 2397 | "easting": 749168, 2398 | "northing": 5838840, 2399 | "measure": 51.3, 2400 | "unit": "Square Metres" 2401 | } 2402 | }, 2403 | { 2404 | "type": "Feature", 2405 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.110", 2406 | "geometry": { 2407 | "type": "Point", 2408 | "coordinates": [ 2409 | 143.82014077752814, 2410 | -37.563088702877636 2411 | ] 2412 | }, 2413 | "geometry_name": "geom", 2414 | "properties": { 2415 | "id": "00217158", 2416 | "site": "Victoria Park", 2417 | "location": "Victoria Park", 2418 | "feat_type": "SG-Cricket Pitch", 2419 | "easting": 749088, 2420 | "northing": 5838920, 2421 | "measure": 48.7, 2422 | "unit": "Square Metres" 2423 | } 2424 | }, 2425 | { 2426 | "type": "Feature", 2427 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.111", 2428 | "geometry": { 2429 | "type": "Point", 2430 | "coordinates": [ 2431 | 143.8194041090023, 2432 | -37.5622585726959 2433 | ] 2434 | }, 2435 | "geometry_name": "geom", 2436 | "properties": { 2437 | "id": "00217159", 2438 | "site": "Victoria Park", 2439 | "location": "Victoria Park", 2440 | "feat_type": "SG-Cricket Pitch", 2441 | "easting": 749026, 2442 | "northing": 5839014, 2443 | "measure": 43.8, 2444 | "unit": "Square Metres" 2445 | } 2446 | }, 2447 | { 2448 | "type": "Feature", 2449 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.112", 2450 | "geometry": { 2451 | "type": "Point", 2452 | "coordinates": [ 2453 | 143.8199066104553, 2454 | -37.561122069638564 2455 | ] 2456 | }, 2457 | "geometry_name": "geom", 2458 | "properties": { 2459 | "id": "00217160", 2460 | "site": "Victoria Park", 2461 | "location": "Victoria Park", 2462 | "feat_type": "SG-Cricket Pitch", 2463 | "easting": 749074, 2464 | "northing": 5839139, 2465 | "measure": 51.5, 2466 | "unit": "Square Metres" 2467 | } 2468 | }, 2469 | { 2470 | "type": "Feature", 2471 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.113", 2472 | "geometry": { 2473 | "type": "Point", 2474 | "coordinates": [ 2475 | 143.82098831538843, 2476 | -37.56138512387192 2477 | ] 2478 | }, 2479 | "geometry_name": "geom", 2480 | "properties": { 2481 | "id": "00217161", 2482 | "site": "Victoria Park", 2483 | "location": "Victoria Park", 2484 | "feat_type": "SG-Cricket Pitch", 2485 | "easting": 749169, 2486 | "northing": 5839107, 2487 | "measure": 54.6, 2488 | "unit": "Square Metres" 2489 | } 2490 | }, 2491 | { 2492 | "type": "Feature", 2493 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.114", 2494 | "geometry": { 2495 | "type": "Point", 2496 | "coordinates": [ 2497 | 143.8216384590745, 2498 | -37.56187128148112 2499 | ] 2500 | }, 2501 | "geometry_name": "geom", 2502 | "properties": { 2503 | "id": "00217162", 2504 | "site": "Victoria Park", 2505 | "location": "Victoria Park", 2506 | "feat_type": "SG-Cricket Pitch", 2507 | "easting": 749225, 2508 | "northing": 5839051, 2509 | "measure": 47.1, 2510 | "unit": "Square Metres" 2511 | } 2512 | }, 2513 | { 2514 | "type": "Feature", 2515 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.115", 2516 | "geometry": { 2517 | "type": "Point", 2518 | "coordinates": [ 2519 | 143.82160367592274, 2520 | -37.56184986142868 2521 | ] 2522 | }, 2523 | "geometry_name": "geom", 2524 | "properties": { 2525 | "id": "00217163", 2526 | "site": "Victoria Park", 2527 | "location": "Victoria Park", 2528 | "feat_type": "SG-Cricket Pitch", 2529 | "easting": 749222, 2530 | "northing": 5839054, 2531 | "measure": 48.3, 2532 | "unit": "Square Metres" 2533 | } 2534 | }, 2535 | { 2536 | "type": "Feature", 2537 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.116", 2538 | "geometry": { 2539 | "type": "Point", 2540 | "coordinates": [ 2541 | 143.82156692955536, 2542 | -37.56183083063768 2543 | ] 2544 | }, 2545 | "geometry_name": "geom", 2546 | "properties": { 2547 | "id": "00217164", 2548 | "site": "Victoria Park", 2549 | "location": "Victoria Park", 2550 | "feat_type": "SG-Cricket Pitch", 2551 | "easting": 749218, 2552 | "northing": 5839056, 2553 | "measure": 52, 2554 | "unit": "Square Metres" 2555 | } 2556 | }, 2557 | { 2558 | "type": "Feature", 2559 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.117", 2560 | "geometry": { 2561 | "type": "Point", 2562 | "coordinates": [ 2563 | 143.81961993058854, 2564 | -37.56110603931097 2565 | ] 2566 | }, 2567 | "geometry_name": "geom", 2568 | "properties": { 2569 | "id": "00217165", 2570 | "site": "Victoria Park", 2571 | "location": "Victoria Park", 2572 | "feat_type": "SG-Soccer", 2573 | "easting": 749049, 2574 | "northing": 5839141, 2575 | "measure": 4378.9, 2576 | "unit": "Square Metres" 2577 | } 2578 | }, 2579 | { 2580 | "type": "Feature", 2581 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.118", 2582 | "geometry": { 2583 | "type": "Point", 2584 | "coordinates": [ 2585 | 143.82042121925147, 2586 | -37.561303909598315 2587 | ] 2588 | }, 2589 | "geometry_name": "geom", 2590 | "properties": { 2591 | "id": "00217166", 2592 | "site": "Victoria Park", 2593 | "location": "Victoria Park", 2594 | "feat_type": "SG-Soccer", 2595 | "easting": 749119, 2596 | "northing": 5839117, 2597 | "measure": 8294.8, 2598 | "unit": "Square Metres" 2599 | } 2600 | }, 2601 | { 2602 | "type": "Feature", 2603 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.119", 2604 | "geometry": { 2605 | "type": "Point", 2606 | "coordinates": [ 2607 | 143.82355625091122, 2608 | -37.56819207595292 2609 | ] 2610 | }, 2611 | "geometry_name": "geom", 2612 | "properties": { 2613 | "id": "00217178", 2614 | "site": "Victoria Park", 2615 | "location": "Victoria Park", 2616 | "feat_type": "SG-Equestrian", 2617 | "easting": 749373, 2618 | "northing": 5838345, 2619 | "measure": 1267.7, 2620 | "unit": "Square Metres" 2621 | } 2622 | }, 2623 | { 2624 | "type": "Feature", 2625 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.120", 2626 | "geometry": { 2627 | "type": "Point", 2628 | "coordinates": [ 2629 | 143.8238566877665, 2630 | -37.56831947721161 2631 | ] 2632 | }, 2633 | "geometry_name": "geom", 2634 | "properties": { 2635 | "id": "00217179", 2636 | "site": "Victoria Park", 2637 | "location": "Victoria Park", 2638 | "feat_type": "SG-Equestrian", 2639 | "easting": 749399, 2640 | "northing": 5838330, 2641 | "measure": 1290.6, 2642 | "unit": "Square Metres" 2643 | } 2644 | }, 2645 | { 2646 | "type": "Feature", 2647 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.121", 2648 | "geometry": { 2649 | "type": "Point", 2650 | "coordinates": [ 2651 | 143.8242113764106, 2652 | -37.568462155185976 2653 | ] 2654 | }, 2655 | "geometry_name": "geom", 2656 | "properties": { 2657 | "id": "00217180", 2658 | "site": "Victoria Park", 2659 | "location": "Victoria Park", 2660 | "feat_type": "SG-Equestrian", 2661 | "easting": 749430, 2662 | "northing": 5838313, 2663 | "measure": 1286.5, 2664 | "unit": "Square Metres" 2665 | } 2666 | }, 2667 | { 2668 | "type": "Feature", 2669 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.122", 2670 | "geometry": { 2671 | "type": "Point", 2672 | "coordinates": [ 2673 | 143.824516552713, 2674 | -37.568586468313285 2675 | ] 2676 | }, 2677 | "geometry_name": "geom", 2678 | "properties": { 2679 | "id": "00217181", 2680 | "site": "Victoria Park", 2681 | "location": "Victoria Park", 2682 | "feat_type": "SG-Equestrian", 2683 | "easting": 749456, 2684 | "northing": 5838298, 2685 | "measure": 1310.6, 2686 | "unit": "Square Metres" 2687 | } 2688 | }, 2689 | { 2690 | "type": "Feature", 2691 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.123", 2692 | "geometry": { 2693 | "type": "Point", 2694 | "coordinates": [ 2695 | 143.82428579555435, 2696 | -37.56764759082296 2697 | ] 2698 | }, 2699 | "geometry_name": "geom", 2700 | "properties": { 2701 | "id": "00217182", 2702 | "site": "Victoria Park", 2703 | "location": "Victoria Park", 2704 | "feat_type": "SG-Equestrian", 2705 | "easting": 749439, 2706 | "northing": 5838403, 2707 | "measure": 4324.2, 2708 | "unit": "Square Metres" 2709 | } 2710 | }, 2711 | { 2712 | "type": "Feature", 2713 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.124", 2714 | "geometry": { 2715 | "type": "Point", 2716 | "coordinates": [ 2717 | 143.82208381077322, 2718 | -37.567951819003426 2719 | ] 2720 | }, 2721 | "geometry_name": "geom", 2722 | "properties": { 2723 | "id": "00217183", 2724 | "site": "Victoria Park", 2725 | "location": "Victoria Park", 2726 | "feat_type": "SG-Equestrian", 2727 | "easting": 749244, 2728 | "northing": 5838375, 2729 | "measure": 1189.6, 2730 | "unit": "Square Metres" 2731 | } 2732 | }, 2733 | { 2734 | "type": "Feature", 2735 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.125", 2736 | "geometry": { 2737 | "type": "Point", 2738 | "coordinates": [ 2739 | 143.82825315457802, 2740 | -37.56790532503358 2741 | ] 2742 | }, 2743 | "geometry_name": "geom", 2744 | "properties": { 2745 | "id": "00217185", 2746 | "site": "Victoria Park", 2747 | "location": "Victoria Park", 2748 | "feat_type": "SG-Cricket Pitch", 2749 | "easting": 749789, 2750 | "northing": 5838364, 2751 | "measure": 44, 2752 | "unit": "Square Metres" 2753 | } 2754 | }, 2755 | { 2756 | "type": "Feature", 2757 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.126", 2758 | "geometry": { 2759 | "type": "Point", 2760 | "coordinates": [ 2761 | 143.82580187507529, 2762 | -37.56408682267344 2763 | ] 2764 | }, 2765 | "geometry_name": "geom", 2766 | "properties": { 2767 | "id": "00217187", 2768 | "site": "Victoria Park", 2769 | "location": "Victoria Park", 2770 | "feat_type": "SG-Cricket Pitch", 2771 | "easting": 749585, 2772 | "northing": 5838794, 2773 | "measure": 50.5, 2774 | "unit": "Square Metres" 2775 | } 2776 | }, 2777 | { 2778 | "type": "Feature", 2779 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.127", 2780 | "geometry": { 2781 | "type": "Point", 2782 | "coordinates": [ 2783 | 143.82626959500172, 2784 | -37.565688840868155 2785 | ] 2786 | }, 2787 | "geometry_name": "geom", 2788 | "properties": { 2789 | "id": "00217188", 2790 | "site": "Victoria Park", 2791 | "location": "Victoria Park", 2792 | "feat_type": "SG-Cricket Pitch", 2793 | "easting": 749621, 2794 | "northing": 5838615, 2795 | "measure": 46.3, 2796 | "unit": "Square Metres" 2797 | } 2798 | }, 2799 | { 2800 | "type": "Feature", 2801 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.128", 2802 | "geometry": { 2803 | "type": "Point", 2804 | "coordinates": [ 2805 | 143.82495571267154, 2806 | -37.564888520712394 2807 | ] 2808 | }, 2809 | "geometry_name": "geom", 2810 | "properties": { 2811 | "id": "00217189", 2812 | "site": "Victoria Park", 2813 | "location": "Victoria Park", 2814 | "feat_type": "SG-Cricket Pitch", 2815 | "easting": 749507, 2816 | "northing": 5838707, 2817 | "measure": 44.9, 2818 | "unit": "Square Metres" 2819 | } 2820 | }, 2821 | { 2822 | "type": "Feature", 2823 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.129", 2824 | "geometry": { 2825 | "type": "Point", 2826 | "coordinates": [ 2827 | 143.82492404005876, 2828 | -37.56486549568706 2829 | ] 2830 | }, 2831 | "geometry_name": "geom", 2832 | "properties": { 2833 | "id": "00217190", 2834 | "site": "Victoria Park", 2835 | "location": "Victoria Park", 2836 | "feat_type": "SG-Cricket Pitch", 2837 | "easting": 749505, 2838 | "northing": 5838710, 2839 | "measure": 41, 2840 | "unit": "Square Metres" 2841 | } 2842 | }, 2843 | { 2844 | "type": "Feature", 2845 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.130", 2846 | "geometry": { 2847 | "type": "Point", 2848 | "coordinates": [ 2849 | 143.82489335445314, 2850 | -37.56484388844098 2851 | ] 2852 | }, 2853 | "geometry_name": "geom", 2854 | "properties": { 2855 | "id": "00217191", 2856 | "site": "Victoria Park", 2857 | "location": "Victoria Park", 2858 | "feat_type": "SG-Cricket Pitch", 2859 | "easting": 749502, 2860 | "northing": 5838713, 2861 | "measure": 42.9, 2862 | "unit": "Square Metres" 2863 | } 2864 | }, 2865 | { 2866 | "type": "Feature", 2867 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.131", 2868 | "geometry": { 2869 | "type": "Point", 2870 | "coordinates": [ 2871 | 143.8230409254249, 2872 | -37.567572615611205 2873 | ] 2874 | }, 2875 | "geometry_name": "geom", 2876 | "properties": { 2877 | "id": "00217194", 2878 | "site": "Victoria Park", 2879 | "location": "Victoria Park", 2880 | "feat_type": "SG-Equestrian", 2881 | "easting": 749329, 2882 | "northing": 5838415, 2883 | "measure": 319.1, 2884 | "unit": "Square Metres" 2885 | } 2886 | }, 2887 | { 2888 | "type": "Feature", 2889 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.132", 2890 | "geometry": { 2891 | "type": "Point", 2892 | "coordinates": [ 2893 | 143.81854805705524, 2894 | -37.52491680870395 2895 | ] 2896 | }, 2897 | "geometry_name": "geom", 2898 | "properties": { 2899 | "id": "00217195", 2900 | "site": "Wendouree West Recreation Reserve", 2901 | "location": "Wendouree West Recreation Reserve", 2902 | "feat_type": "SG-Cricket Pitch", 2903 | "easting": 749075, 2904 | "northing": 5843160, 2905 | "measure": 66.6, 2906 | "unit": "Square Metres" 2907 | } 2908 | }, 2909 | { 2910 | "type": "Feature", 2911 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.133", 2912 | "geometry": { 2913 | "type": "Point", 2914 | "coordinates": [ 2915 | 143.81811753715016, 2916 | -37.52489249607816 2917 | ] 2918 | }, 2919 | "geometry_name": "geom", 2920 | "properties": { 2921 | "id": "00217196", 2922 | "site": "Wendouree West Recreation Reserve", 2923 | "location": "Wendouree West Recreation Reserve", 2924 | "feat_type": "SG-Soccer", 2925 | "easting": 749037, 2926 | "northing": 5843164, 2927 | "measure": 7265.8, 2928 | "unit": "Square Metres" 2929 | } 2930 | }, 2931 | { 2932 | "type": "Feature", 2933 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.134", 2934 | "geometry": { 2935 | "type": "Point", 2936 | "coordinates": [ 2937 | 143.81898102408908, 2938 | -37.52486736802696 2939 | ] 2940 | }, 2941 | "geometry_name": "geom", 2942 | "properties": { 2943 | "id": "00217197", 2944 | "site": "Wendouree West Recreation Reserve", 2945 | "location": "Wendouree West Recreation Reserve", 2946 | "feat_type": "SG-Soccer", 2947 | "easting": 749113, 2948 | "northing": 5843165, 2949 | "measure": 4359.6, 2950 | "unit": "Square Metres" 2951 | } 2952 | }, 2953 | { 2954 | "type": "Feature", 2955 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.135", 2956 | "geometry": { 2957 | "type": "Point", 2958 | "coordinates": [ 2959 | 143.8391855037164, 2960 | -37.56528706628505 2961 | ] 2962 | }, 2963 | "geometry_name": "geom", 2964 | "properties": { 2965 | "id": "00217198", 2966 | "site": "Western Oval Reserve", 2967 | "location": "Western Oval", 2968 | "feat_type": "SG-Cricket Pitch", 2969 | "easting": 750763, 2970 | "northing": 5838625, 2971 | "measure": 317.5, 2972 | "unit": "Square Metres" 2973 | } 2974 | }, 2975 | { 2976 | "type": "Feature", 2977 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.136", 2978 | "geometry": { 2979 | "type": "Point", 2980 | "coordinates": [ 2981 | 143.8391852047592, 2982 | -37.565274280885546 2983 | ] 2984 | }, 2985 | "geometry_name": "geom", 2986 | "properties": { 2987 | "id": "00217199", 2988 | "site": "Western Oval Reserve", 2989 | "location": "Western Oval", 2990 | "feat_type": "SG-Cricket/Football", 2991 | "easting": 750763, 2992 | "northing": 5838627, 2993 | "measure": 16397.8, 2994 | "unit": "Square Metres" 2995 | } 2996 | }, 2997 | { 2998 | "type": "Feature", 2999 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.137", 3000 | "geometry": { 3001 | "type": "Point", 3002 | "coordinates": [ 3003 | 143.85596160156214, 3004 | -37.56936796124372 3005 | ] 3006 | }, 3007 | "geometry_name": "geom", 3008 | "properties": { 3009 | "id": "00217200", 3010 | "site": "White Flat Oval Reserve", 3011 | "location": "White Flat", 3012 | "feat_type": "SG-Cricket/Football", 3013 | "easting": 752231, 3014 | "northing": 5838128, 3015 | "measure": 15206.2, 3016 | "unit": "Square Metres" 3017 | } 3018 | }, 3019 | { 3020 | "type": "Feature", 3021 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.138", 3022 | "geometry": { 3023 | "type": "Point", 3024 | "coordinates": [ 3025 | 143.85576415768804, 3026 | -37.569792553020726 3027 | ] 3028 | }, 3029 | "geometry_name": "geom", 3030 | "properties": { 3031 | "id": "00217201", 3032 | "site": "White Flat Oval Reserve", 3033 | "location": "White Flat", 3034 | "feat_type": "SG-Football", 3035 | "easting": 752213, 3036 | "northing": 5838081, 3037 | "measure": 3471, 3038 | "unit": "Square Metres" 3039 | } 3040 | }, 3041 | { 3042 | "type": "Feature", 3043 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.139", 3044 | "geometry": { 3045 | "type": "Point", 3046 | "coordinates": [ 3047 | 143.85653953116665, 3048 | -37.56869704338736 3049 | ] 3050 | }, 3051 | "geometry_name": "geom", 3052 | "properties": { 3053 | "id": "00217202", 3054 | "site": "White Flat Oval Reserve", 3055 | "location": "White Flat", 3056 | "feat_type": "SG-Cricket Pitch", 3057 | "easting": 752285, 3058 | "northing": 5838200, 3059 | "measure": 61.4, 3060 | "unit": "Square Metres" 3061 | } 3062 | }, 3063 | { 3064 | "type": "Feature", 3065 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.140", 3066 | "geometry": { 3067 | "type": "Point", 3068 | "coordinates": [ 3069 | 143.82334372563113, 3070 | -37.52205381081777 3071 | ] 3072 | }, 3073 | "geometry_name": "geom", 3074 | "properties": { 3075 | "id": "00217203", 3076 | "site": "Wyndholm Reserve", 3077 | "location": "Wyndholm Reserve", 3078 | "feat_type": "SG-Cricket Pitch", 3079 | "easting": 749508, 3080 | "northing": 5843466, 3081 | "measure": 57.6, 3082 | "unit": "Square Metres" 3083 | } 3084 | }, 3085 | { 3086 | "type": "Feature", 3087 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.141", 3088 | "geometry": { 3089 | "type": "Point", 3090 | "coordinates": [ 3091 | 143.8233681148804, 3092 | -37.522062326364605 3093 | ] 3094 | }, 3095 | "geometry_name": "geom", 3096 | "properties": { 3097 | "id": "00217204", 3098 | "site": "Wyndholm Reserve", 3099 | "location": "Wyndholm Reserve", 3100 | "feat_type": "SG-Cricket/Football", 3101 | "easting": 749510, 3102 | "northing": 5843464, 3103 | "measure": 15232, 3104 | "unit": "Square Metres" 3105 | } 3106 | }, 3107 | { 3108 | "type": "Feature", 3109 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.142", 3110 | "geometry": { 3111 | "type": "Point", 3112 | "coordinates": [ 3113 | 143.87469377651436, 3114 | -37.62441709022166 3115 | ] 3116 | }, 3117 | "geometry_name": "geom", 3118 | "properties": { 3119 | "id": "00217205", 3120 | "site": "Yarana Drive Park", 3121 | "location": "Yarana Drive Park", 3122 | "feat_type": "SG-Cricket Pitch", 3123 | "easting": 753699, 3124 | "northing": 5831968, 3125 | "measure": 31, 3126 | "unit": "Square Metres" 3127 | } 3128 | }, 3129 | { 3130 | "type": "Feature", 3131 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.143", 3132 | "geometry": { 3133 | "type": "Point", 3134 | "coordinates": [ 3135 | 143.89628651282752, 3136 | -37.551481238968506 3137 | ] 3138 | }, 3139 | "geometry_name": "geom", 3140 | "properties": { 3141 | "id": "00217212", 3142 | "site": "Yarrowee River Reserve", 3143 | "location": "Brown Hill Reserve (Progress Park)", 3144 | "feat_type": "SG-Cricket Pitch", 3145 | "easting": 755855, 3146 | "northing": 5840003, 3147 | "measure": 38.6, 3148 | "unit": "Square Metres" 3149 | } 3150 | }, 3151 | { 3152 | "type": "Feature", 3153 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.144", 3154 | "geometry": { 3155 | "type": "Point", 3156 | "coordinates": [ 3157 | 143.89631936073906, 3158 | -37.5514763786045 3159 | ] 3160 | }, 3161 | "geometry_name": "geom", 3162 | "properties": { 3163 | "id": "00217213", 3164 | "site": "Yarrowee River Reserve", 3165 | "location": "Brown Hill Reserve (Progress Park)", 3166 | "feat_type": "SG-Cricket Pitch", 3167 | "easting": 755858, 3168 | "northing": 5840004, 3169 | "measure": 37.6, 3170 | "unit": "Square Metres" 3171 | } 3172 | }, 3173 | { 3174 | "type": "Feature", 3175 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.145", 3176 | "geometry": { 3177 | "type": "Point", 3178 | "coordinates": [ 3179 | 143.89636351345266, 3180 | -37.55146943893884 3181 | ] 3182 | }, 3183 | "geometry_name": "geom", 3184 | "properties": { 3185 | "id": "00217214", 3186 | "site": "Yarrowee River Reserve", 3187 | "location": "Brown Hill Reserve (Progress Park)", 3188 | "feat_type": "SG-Cricket Pitch", 3189 | "easting": 755862, 3190 | "northing": 5840005, 3191 | "measure": 37.9, 3192 | "unit": "Square Metres" 3193 | } 3194 | }, 3195 | { 3196 | "type": "Feature", 3197 | "id": "6f254063_19fa_4e5e_90f6_6934d263d70e.146", 3198 | "geometry": { 3199 | "type": "Point", 3200 | "coordinates": [ 3201 | 143.89638483959158, 3202 | -37.55146828475429 3203 | ] 3204 | }, 3205 | "geometry_name": "geom", 3206 | "properties": { 3207 | "id": "00217215", 3208 | "site": "Yarrowee River Reserve", 3209 | "location": "Brown Hill Reserve (Progress Park)", 3210 | "feat_type": "SG-Cricket Pitch", 3211 | "easting": 755864, 3212 | "northing": 5840005, 3213 | "measure": 38.6, 3214 | "unit": "Square Metres" 3215 | } 3216 | } 3217 | ], 3218 | "crs": { 3219 | "type": "name", 3220 | "properties": { 3221 | "name": "urn:ogc:def:crs:EPSG::32754" 3222 | } 3223 | } 3224 | } 3225 | -------------------------------------------------------------------------------- /examples/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Examples for the elm-maps library.", 4 | "repository": "https://github.com/kennib/elm-maps.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "elm-community/list-extra": "6.1.0 <= v < 7.0.0", 13 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 14 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 15 | "elm-lang/http": "1.0.0 <= v < 2.0.0", 16 | "elm-lang/window": "1.0.1 <= v < 2.0.0", 17 | "labzero/elm-google-geocoding": "4.0.1 <= v < 5.0.0", 18 | "mgold/elm-geojson": "2.0.0 <= v < 3.0.0" 19 | }, 20 | "elm-version": "0.18.0 <= v < 0.19.0" 21 | } 22 | -------------------------------------------------------------------------------- /src/Maps.elm: -------------------------------------------------------------------------------- 1 | module Maps exposing 2 | ( Msg 3 | , Model 4 | , updateMap 5 | , updateMarkers 6 | , defaultModel 7 | , subscriptions 8 | , update 9 | , view 10 | , mapView 11 | ) 12 | 13 | {-| Functions for creating a maps program and maniuplating the maps model. 14 | 15 | # Showing a map 16 | You can use the functions below to display a map. 17 | 18 | import Maps 19 | import Html exposing (program) 20 | 21 | main = program 22 | { init = (Maps.defaultModel, Cmd.none) 23 | , subscriptions = Maps.subscriptions 24 | , update = Maps.update 25 | , view = Maps.view 26 | } 27 | 28 | @docs defaultModel 29 | @docs subscriptions 30 | @docs update 31 | @docs view 32 | @docs mapView 33 | 34 | # Update Model 35 | @docs updateMap 36 | @docs updateMarkers 37 | 38 | # Types 39 | The following types are [opaque](http://package.elm-lang.org/help/design-guidelines#keep-tags-and-record-constructors-secret). 40 | Use the functions above to maniuplate and extract information from them. 41 | 42 | @docs Msg 43 | @docs Model 44 | -} 45 | 46 | import Html exposing (Html) 47 | 48 | import Maps.Internal.OpaqueTypes as OpaqueTypes exposing (Model(..), opaqueModel, transparentMap) 49 | import Maps.Marker exposing (Marker) 50 | import Maps.Map exposing (Map) 51 | import Maps.Internal.Maps as Maps 52 | 53 | {-| -} 54 | type alias Msg msg = Maps.Msg msg 55 | {-| -} 56 | type alias Model msg = OpaqueTypes.Model msg 57 | 58 | {-| Change the map inside of a model. 59 | 60 | For example, set the width/height of a map and zoom into Seoul, South Korea: 61 | 62 | import Maps.Geo 63 | import Maps.Map as Map 64 | 65 | let 66 | seoul = Maps.Geo.latLng 37.532600 127.024612 67 | in 68 | model 69 | |> updateMap (Map.setHeight 600) 70 | |> updateMap (Map.setWidth 1000) 71 | |> updateMap (Map.viewBounds <| Maps.Geo.centeredBounds 10 seoul) 72 | 73 | See [Maps.Map](./Maps-Map) for documentation of the Map functions. 74 | -} 75 | updateMap : (Map -> Map) -> Model msg -> Model msg 76 | updateMap update = opaqueModel <| Maps.updateMap <| transparentMap update 77 | 78 | {-| Change the markers inside of the model 79 | 80 | For example, add markers for some Sydney attractions and then another marker for the city center: 81 | 82 | import Maps.Geo 83 | import Maps.Marker as Marker 84 | 85 | let 86 | attractions = 87 | List.map (uncurry Maps.Geo.latLng) 88 | [ (-33.852324, 151.210819) 89 | , (-33.856872, 151.215239) 90 | , (-33.870397, 151.208835) 91 | ] 92 | sydney = Maps.Geo.latLng -33.865143 151.209900 93 | in 94 | model 95 | |> updateMarkers (\markers -> List.map Marker.create attractions ++ markers) 96 | |> updateMarkers ((::) (Marker.create sydney)) 97 | 98 | See [Maps.Marker](./Maps-Marker) for documentation of the Marker functions. 99 | -} 100 | updateMarkers : (List (Marker msg) -> List (Marker msg)) -> Model msg -> Model msg 101 | updateMarkers update = opaqueModel <| Maps.updateMarkers update 102 | 103 | {-| The default model is a map zoomed into Sydney, Australia with no markers. 104 | -} 105 | defaultModel : Model msg 106 | defaultModel = Model <| Maps.defaultModel 107 | 108 | {-| -} 109 | subscriptions : Model msg -> Sub (Msg msg) 110 | subscriptions (Model model) = Maps.subscriptions model 111 | 112 | {-| -} 113 | update : Msg msg -> Model msg -> (Model msg, Cmd (Msg msg)) 114 | update msg (Model model) = 115 | Maps.update msg model 116 | |> Tuple.mapFirst Model 117 | 118 | {-| -} 119 | view : Model msg -> Html (Msg msg) 120 | view (Model model) = Maps.view model 121 | 122 | {-| Transforms the Maps HTML view into an arbitrary HTML view. 123 | Requires a function that can transform `Maps.Msg`s into `msg`s. 124 | 125 | ``` 126 | import Html 127 | import Html.Event exposing (onClick) 128 | import Maps 129 | 130 | type MyMsg = Click | MapsMsg Maps.Msg 131 | 132 | ... 133 | 134 | view msg model = 135 | Html.div 136 | [] 137 | [ Maps.view model.map |> Maps.mapView MapsMsg 138 | , Html.button [ onClick Click ] [ Html.text "Click!" ] 139 | ] 140 | ``` 141 | -} 142 | mapView : (Msg msg -> msg) -> Html (Msg msg) -> Html msg 143 | mapView = Maps.mapView 144 | -------------------------------------------------------------------------------- /src/Maps/Convert.elm: -------------------------------------------------------------------------------- 1 | module Maps.Convert exposing 2 | ( MapSizes 3 | ,latLngToScreenOffset 4 | , screenOffsetToLatLng 5 | ) 6 | 7 | {-| Functions for converting between different map units. 8 | 9 | # Map Size Properties 10 | @docs MapSizes 11 | 12 | # Screen Offset - Latitude/Longitude 13 | @docs screenOffsetToLatLng 14 | @docs latLngToScreenOffset 15 | -} 16 | 17 | import Maps.Geo 18 | import Maps.Internal.Screen as Screen exposing (ZoomLevel) 19 | 20 | {-| The size properties of a map. 21 | The conversion functions that require this type can just be passed a map type. 22 | -} 23 | type alias MapSizes a = { a | tileSize : Float, zoom : ZoomLevel, width : Float, height : Float, center : Maps.Geo.LatLng } 24 | 25 | {-| Take an offset from the top left of the map and convert it to a latitude/longitude. 26 | Note that it requires the dimensions of the map (or the map itself) to calculate this conversion. 27 | -} 28 | screenOffsetToLatLng : MapSizes a -> Screen.Offset -> Maps.Geo.LatLng 29 | screenOffsetToLatLng map offset = 30 | Screen.offsetToLatLng map offset 31 | 32 | {-| Take latitude/longitude anc convert it to an offset from the top left of the map. 33 | Note that it requires the dimensions of the map (or the map itself) to calculate this conversion. 34 | -} 35 | latLngToScreenOffset : MapSizes a -> Maps.Geo.LatLng -> Screen.Offset 36 | latLngToScreenOffset map latLng = 37 | Screen.offsetFromLatLng map latLng 38 | -------------------------------------------------------------------------------- /src/Maps/Geo.elm: -------------------------------------------------------------------------------- 1 | module Maps.Geo exposing 2 | ( LatLng 3 | , Bounds 4 | , latLng 5 | , bounds 6 | , centeredBounds 7 | ) 8 | 9 | {-| Geographic types and constructors. 10 | 11 | # Latitude/Longitude 12 | @docs LatLng 13 | @docs latLng 14 | 15 | # Bounds 16 | @docs Bounds 17 | @docs bounds 18 | @docs centeredBounds 19 | -} 20 | 21 | import Maps.Internal.LatLng as LatLng 22 | import Maps.Internal.Bounds as Bounds 23 | 24 | {-| The LatLng type is a simple record containing latitude and longitude. 25 | 26 | You can create a longitude in two equivalent ways: 27 | 28 | Maps.Geo.latLng 10 -80 == { lat = 10, lng = -80} 29 | -} 30 | type alias LatLng = LatLng.LatLng 31 | 32 | {-| Create a LatLng. 33 | 34 | For example: 35 | 36 | latLng 45 -175 37 | 38 | -} 39 | latLng : Float -> Float -> LatLng 40 | latLng = LatLng.LatLng 41 | 42 | {-| The Bounds type has several variations. 43 | All of them can be used to calculate the position and zoom of a map. 44 | 45 | ## NorthEast/SouthWest bounds 46 | @docs bounds 47 | 48 | ## Center and Zoom Level 49 | @docs centerBounds 50 | -} 51 | type alias Bounds = Bounds.Bounds 52 | 53 | {-| Create a Bounds using a northeast and southwest point. 54 | 55 | For example, the bounds of Ecuador 56 | 57 | ecuador = 58 | bounds 59 | { northEast = latLng 1.4284875 -75.188794 60 | , southWest = latLng -5.0143511 -81.08498089999999 61 | } 62 | -} 63 | bounds : { northEast : LatLng, southWest : LatLng } -> Bounds 64 | bounds = Bounds.Bounds 65 | 66 | {-| Create a Bounds centered on a location with a given zoom level. 67 | 68 | For example, zoomed into the streets of Baku, Azerbaijan: 69 | 70 | baku = 71 | centeredBounds 72 | 14 73 | (latLng 40.409264 49.867092) 74 | -} 75 | centeredBounds : Float -> LatLng -> Bounds 76 | centeredBounds zoom latLng = 77 | Bounds.Centered 78 | { zoom = zoom 79 | , center = latLng 80 | } 81 | -------------------------------------------------------------------------------- /src/Maps/Internal/Bounds.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Bounds exposing 2 | ( Bounds(..) 3 | , zoom 4 | , center 5 | ) 6 | 7 | {-| This module defines the Bounds type for the Maps library. 8 | The Bounds type is used for defining a geographical area. 9 | 10 | # Definition 11 | @docs Bounds 12 | 13 | # Properties 14 | @docs zoom 15 | @docs center 16 | -} 17 | 18 | import Maps.Internal.LatLng as LatLng exposing (LatLng) 19 | import Maps.Internal.Screen as Screen exposing (ZoomLevel) 20 | import Maps.Internal.Utils exposing (wrap) 21 | 22 | {-| The Bounds type defines the bounds of a map. 23 | It can be a rectangular bounding box defined by two points, or a point and a zoom level. 24 | -} 25 | type Bounds 26 | = Bounds 27 | { northEast : LatLng 28 | , southWest : LatLng 29 | } 30 | | Centered 31 | { zoom : Float 32 | , center : LatLng 33 | } 34 | 35 | {-| The zoom function calculates the zoom level necessary to contain the given Bounds. 36 | 37 | Note that the size of the tiles and map are needed to calculate the zoom level. 38 | -} 39 | zoom : Float -> Float -> Float -> Bounds -> ZoomLevel 40 | zoom tileSize mapWidth mapHeight bounds = 41 | case bounds of 42 | Bounds bounds -> 43 | let 44 | (ne, sw) = (bounds.northEast, bounds.southWest) 45 | -- The following assumes a Mercator projection 46 | -- See https://en.wikipedia.org/wiki/Mercator_projection#Alternative_expressions for details 47 | latY lat = sin (lat * pi / 180) 48 | radX2 lat = (logBase e ((1 + latY lat) / (1 - latY lat))) / 2 49 | latRad lat = (max (-pi) <| min (radX2 lat) pi) / 2 50 | latFraction = (latRad ne.lat) - (latRad sw.lat) 51 | lngFraction = ((ne.lng - sw.lng) |> wrap 0 360) / 360 52 | zoom mapSize tileSize frac = logBase 2 (mapSize / tileSize / frac) 53 | in 54 | min 55 | (zoom mapWidth tileSize lngFraction) 56 | (zoom mapHeight tileSize latFraction) 57 | Centered bounds -> 58 | bounds.zoom 59 | 60 | {-| Calculates the center point of a given Bounds. 61 | -} 62 | center : Bounds -> LatLng 63 | center bounds = 64 | case bounds of 65 | Bounds bounds -> 66 | { lat = (bounds.northEast.lat + bounds.southWest.lat) / 2 67 | , lng = (bounds.northEast.lng + bounds.southWest.lng) / 2 68 | } 69 | Centered bounds -> 70 | bounds.center 71 | -------------------------------------------------------------------------------- /src/Maps/Internal/Drag.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Drag exposing 2 | ( Drag 3 | , EventOptions 4 | , start 5 | , drag 6 | , offset 7 | , events 8 | ) 9 | 10 | import Json.Decode as Json 11 | 12 | import Html 13 | import Html.Events exposing (on, onWithOptions, defaultOptions, onMouseUp) 14 | 15 | import Maps.Internal.Screen as Screen 16 | 17 | type Drag 18 | = StartDrag Screen.Offset 19 | | Drag Screen.Offset Screen.Offset 20 | 21 | type alias EventOptions msg = 22 | { dragStart : Screen.Offset -> msg 23 | , dragTo : Screen.Offset -> msg 24 | , dragStop : msg 25 | } 26 | 27 | start : Screen.Offset -> Drag 28 | start = StartDrag 29 | 30 | drag : Screen.Offset -> Drag -> Drag 31 | drag offset state = 32 | case state of 33 | StartDrag start -> Drag start offset 34 | Drag start end -> Drag end offset 35 | 36 | offset : Drag -> Screen.Offset 37 | offset drag = 38 | case drag of 39 | StartDrag _ -> 40 | { x = 0, y = 0 } 41 | Drag start end -> 42 | { x = end.x - start.x, y = end.y - start.y } 43 | 44 | events : EventOptions msg -> Maybe Drag -> List (Html.Attribute msg) 45 | events ({dragStart, dragTo, dragStop}) drag = 46 | [ -- Mouse 47 | if drag == Nothing then 48 | onWithOptions "mousedown" 49 | { defaultOptions | preventDefault = True } 50 | <| Json.map dragStart 51 | <| Screen.decodeOffset 52 | else 53 | on "mousemove" 54 | <| Json.map dragTo 55 | <| Screen.decodeOffset 56 | , -- Mouse 57 | onMouseUp dragStop 58 | , -- Mobile 59 | if drag == Nothing then 60 | onWithOptions "touchstart" 61 | { defaultOptions | preventDefault = True } 62 | <| Json.map dragStart 63 | <| Screen.decodeOffset 64 | else 65 | onWithOptions "touchmove" 66 | { defaultOptions | preventDefault = True } 67 | <| Json.map dragTo 68 | <| Screen.decodeOffset 69 | , -- Mobile 70 | onWithOptions "touchend" 71 | { defaultOptions | preventDefault = True } 72 | <| Json.succeed dragStop 73 | ] 74 | -------------------------------------------------------------------------------- /src/Maps/Internal/LatLng.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.LatLng exposing 2 | ( LatLng 3 | , sydney 4 | ) 5 | 6 | {-| This module defines the LatLng type for the Maps library. 7 | The LatLng type is used for defining geographical points. 8 | 9 | # Definition 10 | @docs LatLng 11 | 12 | # Examples 13 | @docs sydney 14 | -} 15 | 16 | 17 | {-| The LatLng type consists of a [latitude](https://en.wikipedia.org/wiki/Latitude) and [longitude](https://en.wikipedia.org/wiki/Longitude). 18 | -} 19 | type alias LatLng = 20 | { lat : Float 21 | , lng : Float 22 | } 23 | 24 | {-| An example LatLng for Sydney. 25 | 26 | sydney = { lat = -33.865143, lng = 151.209900 } 27 | -} 28 | sydney : LatLng 29 | sydney = { lat = -33.865143, lng = 151.209900 } 30 | -------------------------------------------------------------------------------- /src/Maps/Internal/Map.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Map exposing 2 | ( Map 3 | , Transformation 4 | , setTileServer 5 | , setWidth 6 | , setHeight 7 | , setTileSize 8 | , move 9 | , moveTo 10 | , setZoom 11 | , zoom 12 | , zoomTo 13 | , viewBounds 14 | , drag 15 | , diff 16 | , tiles 17 | , transformationStyle 18 | ) 19 | 20 | {-| This module defines the Map type and functions. 21 | The Map type is used for configuring the view of the map. 22 | 23 | # Definitions 24 | @docs Map 25 | 26 | # Transformations 27 | @docs move 28 | @docs zoom 29 | @docs zoomTo 30 | @docs viewBounds 31 | @docs drag 32 | 33 | # Map Tiles 34 | @docs tiles 35 | 36 | # Map Cache 37 | @docs diff 38 | @docs Transformation 39 | @docs transformationStyle 40 | -} 41 | 42 | import Maps.Internal.Screen as Screen exposing (ZoomLevel) 43 | import Maps.Internal.LatLng as LatLng exposing (LatLng) 44 | import Maps.Internal.Bounds as Bounds exposing (Bounds) 45 | import Maps.Internal.Tile as Tile exposing (Tile) 46 | import Maps.Internal.Drag as Drag exposing (Drag) 47 | import Maps.Internal.Zoom as Zoom 48 | import Maps.Internal.Utils exposing (wrap, cartesianMap) 49 | 50 | {-| The Map type stores the properties neccessary for displaying a map. 51 | 52 | The tileServer property is a URL template of the form 53 | 54 | "http://somedomain.com/blabla/{z}/{x}/{y}.png" 55 | 56 | Where {z} is the zoom level and {x}/{y} are the x/y tile coordinates. 57 | 58 | The zoom and center define the area being viewed. 59 | 60 | The width and height define the width and height of the map in pixels. 61 | 62 | The tileSize defines the size of an individual tile in pixels (this is usually 256px). 63 | -} 64 | type alias Map = 65 | { tileServer : String 66 | , zoom : ZoomLevel 67 | , center : LatLng 68 | , width : Float 69 | , height : Float 70 | , tileSize : Float 71 | } 72 | 73 | {-| The transformations that account for the differences in placement and scale of tiles of a map. 74 | Used for displaying the tile cache. 75 | -} 76 | type Transformation 77 | = Moved Screen.Offset 78 | | Scaled Float 79 | 80 | setTileServer : String -> Map -> Map 81 | setTileServer tileServer map = { map | tileServer = tileServer } 82 | 83 | setWidth : Float -> Map -> Map 84 | setWidth width map = { map | width = width } 85 | 86 | setHeight : Float -> Map -> Map 87 | setHeight height map = { map | height = height } 88 | 89 | setTileSize : Float -> Map -> Map 90 | setTileSize tileSize map = { map | tileSize = tileSize } 91 | 92 | {-| Moves the map the number of pixels given by the offset. 93 | -} 94 | move : Screen.Offset -> Map -> Map 95 | move offset map = 96 | let 97 | centerOffset offset = Screen.Offset (map.width/2 - offset.x) (map.height/2 - offset.y) 98 | in 99 | { map | center = Screen.offsetToLatLng map <| centerOffset offset } 100 | 101 | moveTo : LatLng -> Map -> Map 102 | moveTo latLng map = 103 | { map | center = latLng } 104 | 105 | {-| Sets the zooms to a specific level. 106 | -} 107 | setZoom : ZoomLevel -> Map -> Map 108 | setZoom zoomLevel map = { map | zoom = min 19 <| max 0 <| zoomLevel } 109 | 110 | {-| Zooms the map in or out from the center of the map. 111 | -} 112 | zoom : ZoomLevel -> Map -> Map 113 | zoom zoomLevel map = 114 | { map | zoom = min 19 <| max 0 <| map.zoom + zoomLevel } 115 | 116 | {-| Zooms the map in or out from the point given. 117 | -} 118 | zoomTo : ZoomLevel -> Screen.Offset -> Map -> Map 119 | zoomTo zoomLevel offset map = 120 | map 121 | |> move { x = map.width/2 - offset.x, y = map.height/2 - offset.y } 122 | |> zoom zoomLevel 123 | |> move { x = -(map.width/2 - offset.x), y = -(map.height/2 - offset.y) } 124 | 125 | {-| Moves the map to display the entire bounds given. 126 | -} 127 | viewBounds : Bounds -> Map -> Map 128 | viewBounds bounds map = 129 | let 130 | zoom = Bounds.zoom map.tileSize map.width map.height bounds 131 | in 132 | { map | zoom = zoom, center = Bounds.center bounds } 133 | 134 | {-| Applies a drag (like a mouse drag) to the map 135 | -} 136 | drag : Drag -> Map -> Map 137 | drag dragState map = 138 | move (Drag.offset dragState) map 139 | 140 | {-| Finds the transformations between two maps. 141 | Useful for figuring out how to transform cached tiles into temporary substitutes of loading tiles. 142 | -} 143 | diff : Map -> Map -> List Transformation 144 | diff newMap oldMap = 145 | let 146 | sub a b = { x = a.x - b.x, y = a.y - b.y } 147 | in 148 | [ Moved 149 | <| sub 150 | (Screen.offsetFromLatLng newMap oldMap.center) 151 | (Screen.offsetFromLatLng newMap newMap.center) 152 | , Scaled 153 | <| (\zoom -> 2^zoom) 154 | <| toFloat 155 | <| (-) 156 | (ceiling newMap.zoom) 157 | (ceiling oldMap.zoom) 158 | ] 159 | 160 | {-| Returns the list of tiles necessary to fetch to display the map. 161 | -} 162 | tiles : Map -> List Tile 163 | tiles map = 164 | let 165 | xCount = map.width/map.tileSize 166 | yCount = map.height/map.tileSize 167 | tile = Tile.fromLatLng (toFloat <| ceiling map.zoom) map.center 168 | xTiles = List.range (floor <| -xCount/2) (ceiling <| xCount/2) 169 | yTiles = List.range (floor <| -yCount/2) (ceiling <| yCount/2) 170 | wrapTile = wrap 0 (2^(ceiling map.zoom)) 171 | tileXY x y = 172 | ( Tile.url 173 | map.tileServer 174 | (ceiling map.zoom) 175 | (floor tile.x + x |> wrapTile) 176 | (floor tile.y + y |> wrapTile) 177 | , Tile.Offset 178 | (map.width/2 + (toFloat (floor tile.x) - tile.x + toFloat x) * map.tileSize) 179 | (map.height/2 + (toFloat (floor tile.y) - tile.y + toFloat y) * map.tileSize) 180 | ) 181 | in 182 | cartesianMap tileXY xTiles yTiles 183 | |> List.concat 184 | 185 | {-| Returns a list of CSS properties/values for transforming map tiles. 186 | -} 187 | transformationStyle : Float -> Float -> List Transformation -> List (String, String) 188 | transformationStyle mapWidth mapHeight transforms = 189 | let 190 | transformations transform = 191 | case transform of 192 | Moved offset -> 193 | "translate("++toString offset.x++"px, "++toString offset.y++"px)" 194 | Scaled scale -> 195 | "scale("++toString scale++")" 196 | style = 197 | transforms 198 | |> List.map transformations 199 | |> String.join " " 200 | in 201 | [ ("transform-origin", toString (mapWidth/2)++"px "++toString (mapHeight/2)++"px") 202 | , ("transform", style) 203 | ] 204 | -------------------------------------------------------------------------------- /src/Maps/Internal/Maps.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Maps exposing 2 | ( Msg(..) 3 | , Model 4 | , updateMap 5 | , updateMarkers 6 | , defaultModel 7 | , update 8 | , subscriptions 9 | , view 10 | , mapView 11 | ) 12 | 13 | {-| The Maps library contains the functions neccessary for an 14 | [HTML.program](http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html#program). 15 | 16 | # Creating a map 17 | The quickest way to get up and running is to create a map with default options 18 | 19 | import Maps.Internal 20 | import Html exposing (program) 21 | 22 | main = program <| Maps.map Maps.defaultOptions 23 | 24 | @docs map 25 | @docs Options 26 | @docs defaultOptions 27 | 28 | # Definitions 29 | @docs Msg 30 | @docs Model 31 | 32 | # Program functions 33 | @docs update 34 | @docs subscriptions 35 | @docs view 36 | -} 37 | 38 | import List.Extra as List 39 | 40 | import Json.Decode as Json 41 | 42 | import Html exposing (Html, program) 43 | import Html.Keyed 44 | import Html.Attributes as Attr 45 | import Html.Events exposing (onWithOptions) 46 | 47 | import Maps.Internal.Map as Map exposing (Map) 48 | import Maps.Internal.Screen as Screen exposing (Offset, TwoFingers, ZoomLevel) 49 | import Maps.Internal.LatLng as LatLng exposing (LatLng) 50 | import Maps.Internal.Bounds as Bounds exposing (Bounds) 51 | import Maps.Internal.Marker as Marker exposing (Marker) 52 | import Maps.Internal.Tile as Tile exposing (Tile) 53 | import Maps.Internal.Drag as Drag exposing (Drag) 54 | import Maps.Internal.Pinch as Pinch exposing (Pinch) 55 | import Maps.Internal.Zoom as Zoom 56 | 57 | {-| The map has events for dragging, zooming and setting the bounds displayed by the map. 58 | -} 59 | type Msg msg 60 | = DragStart Offset 61 | | DragTo Offset 62 | | DragStop 63 | | PinchStart TwoFingers 64 | | PinchTo TwoFingers 65 | | PinchStop 66 | | Zoom Offset ZoomLevel 67 | | ExternalMsg msg 68 | 69 | {-| The map's model consists of the [properties necessary to display a static map](Maps-Map#Map), 70 | a cache of the previous map (for simulated zooming/panning before the real tiles load in) 71 | and the state of the map being dragged. 72 | -} 73 | type alias Model msg = 74 | { map : Map 75 | , cache : List Map 76 | , markers : List (Marker msg) 77 | , drag : Maybe Drag 78 | , pinch : Maybe Pinch 79 | } 80 | 81 | updateMap : (Map -> Map) -> Model msg -> Model msg 82 | updateMap update model = 83 | { model 84 | | map = update model.map 85 | , cache = model.map :: model.cache |> List.uniqueBy (.zoom >> ceiling) 86 | } 87 | 88 | updateMarkers : (List (Marker msg) -> List (Marker msg)) -> Model msg -> Model msg 89 | updateMarkers update model = 90 | { model 91 | | markers = update model.markers 92 | } 93 | 94 | {-| A default model that displays Open Street Map tiles looking at Sydney. 95 | -} 96 | defaultModel : Model msg 97 | defaultModel = 98 | let 99 | map = 100 | { tileServer = "http://a.tile.osm.org/{z}/{x}/{y}.png" 101 | , zoom = 10 102 | , center = LatLng.sydney 103 | , width = 600 104 | , height = 400 105 | , tileSize = 256 106 | } 107 | in 108 | { map = map 109 | , cache = [] 110 | , markers = [] 111 | , drag = Nothing 112 | , pinch = Nothing 113 | } 114 | 115 | {-| -} 116 | update : (Msg msg) -> Model msg -> (Model msg, Cmd (Msg msg)) 117 | update msg model = 118 | case msg of 119 | DragStart offset -> 120 | let 121 | dragState = 122 | if model.pinch == Nothing then 123 | Just <| Drag.start offset 124 | else 125 | Nothing 126 | in 127 | ({ model | drag = dragState }, Cmd.none) 128 | DragTo offset -> 129 | let 130 | dragState = 131 | if model.pinch == Nothing then 132 | Maybe.map (Drag.drag offset) model.drag 133 | else 134 | Nothing 135 | draggedMap map = 136 | dragState 137 | |> Maybe.map (flip Map.drag <| map) 138 | |> Maybe.withDefault map 139 | in 140 | (updateMap draggedMap { model | drag = dragState }, Cmd.none) 141 | DragStop -> 142 | ({ model | drag = Nothing }, Cmd.none) 143 | PinchStart fingers -> 144 | ({ model | pinch = Just <| Pinch.start fingers }, Cmd.none) 145 | PinchTo fingers -> 146 | ({ model | pinch = Maybe.map (Pinch.pinch fingers) model.pinch }, Cmd.none) 147 | PinchStop -> 148 | let 149 | zoom = Maybe.map (Zoom.fromPinch model.map.width model.map.height) model.pinch 150 | pinchedMap map = 151 | case zoom of 152 | Just (zoom, offset) -> Map.zoomTo zoom offset map 153 | Nothing -> map 154 | in 155 | (updateMap pinchedMap { model | pinch = Nothing }, Cmd.none) 156 | Zoom offset zoom -> 157 | (updateMap (Map.zoomTo zoom offset) model, Cmd.none) 158 | ExternalMsg msg -> 159 | (model, Cmd.none) 160 | 161 | {-| -} 162 | subscriptions : Model msg -> Sub (Msg msg) 163 | subscriptions map = 164 | Sub.none 165 | 166 | {-| -} 167 | view : Model msg -> Html (Msg msg) 168 | view ({map, cache, markers, pinch, drag} as model) = 169 | Html.div 170 | ([ Attr.style 171 | [ ("width", toString map.width ++ "px") 172 | , ("height", toString map.height ++ "px") 173 | , ("-webkit-touch-callout", "none") 174 | , ("-webkit-user-select", "none") 175 | , ("-khtml-user-select", "none") 176 | , ("-moz-user-select", "none") 177 | , ("-ms-user-select", "none") 178 | , ("user-select", "none") 179 | , ("background-color", "#ddd") 180 | ] 181 | ] 182 | ++ zoomEvents map.zoom 183 | ) 184 | <| 185 | let 186 | zoom = Maybe.map (Zoom.fromPinch map.width map.height) pinch 187 | zoomedMap = Maybe.withDefault map <| Maybe.map (\(zoom, offset) -> Map.zoomTo zoom offset map) zoom 188 | transforms = Map.diff zoomedMap map 189 | in 190 | [ Html.div 191 | [ Attr.style 192 | [ ("position", "absolute") 193 | , ("width", toString map.width ++ "px") 194 | , ("height", toString map.height ++ "px") 195 | , ("overflow", "hidden") 196 | ] 197 | , onWithOptions "mouseDown" 198 | { preventDefault = True, stopPropagation = False } 199 | <| Json.fail "No interaction" 200 | ] 201 | <| List.map (\cachedMap -> tilesView (Map.diff zoomedMap cachedMap) cachedMap) 202 | <| List.reverse 203 | <| cache 204 | , Html.div 205 | ([ Attr.style 206 | [ ("position", "absolute") 207 | , ("width", toString map.width ++ "px") 208 | , ("height", toString map.height ++ "px") 209 | , ("overflow", "hidden") 210 | ] 211 | ] ++ dragEvents drag 212 | ) 213 | [ tilesView transforms map 214 | ] 215 | , Html.div 216 | [ Attr.style 217 | [ ("position", "absolute") 218 | , ("width", toString map.width ++ "px") 219 | , ("height", toString map.height ++ "px") 220 | , ("overflow", "hidden") 221 | , ("pointer-events", "none") 222 | ] 223 | ] 224 | <| List.map (Html.map ExternalMsg) 225 | <| List.map (Marker.view zoomedMap) 226 | <| markers 227 | ] 228 | 229 | {-| Map a Map HTML view to an arbitrary HTML view which wraps map messages. -} 230 | mapView : (Msg msg -> msg) -> Html (Msg msg) -> Html msg 231 | mapView wrapMsg html = 232 | let 233 | mapMsg mapsMsg = 234 | case mapsMsg of 235 | ExternalMsg msg -> msg 236 | mapsMsg -> wrapMsg mapsMsg 237 | in 238 | Html.map mapMsg html 239 | 240 | tilesView : List Map.Transformation -> Map -> Html (Msg msg) 241 | tilesView transforms map = 242 | Html.Keyed.node "div" 243 | [ Attr.style <| Map.transformationStyle map.width map.height <| transforms ] 244 | <| List.map (\((url, offset) as tile) -> (url, Tile.view map.tileSize tile)) 245 | <| Map.tiles map 246 | 247 | zoomEvents : ZoomLevel -> List (Html.Attribute (Msg msg)) 248 | zoomEvents zoom = 249 | Zoom.events { zoom = Zoom, pinchStart = PinchStart, pinchTo = PinchTo, pinchStop = PinchStop } zoom 250 | 251 | dragEvents : Maybe Drag -> List (Html.Attribute (Msg msg)) 252 | dragEvents drag = 253 | Drag.events { dragStart = DragStart, dragTo = DragTo, dragStop = DragStop } drag 254 | -------------------------------------------------------------------------------- /src/Maps/Internal/Marker.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Marker exposing 2 | ( Marker(..) 3 | , view 4 | ) 5 | 6 | import Html exposing (Html) 7 | import Html.Attributes as Attr 8 | 9 | import Maps.Internal.Screen as Screen exposing (ZoomLevel) 10 | import Maps.Internal.LatLng as LatLng exposing (LatLng) 11 | 12 | type Marker msg 13 | = DefaultMarker LatLng 14 | | CustomMarker (Html msg) LatLng 15 | 16 | view : {a | tileSize : Float, zoom : ZoomLevel, width : Float, height : Float, center : LatLng} -> Marker msg -> Html msg 17 | view map marker = 18 | case marker of 19 | DefaultMarker latLng -> 20 | let 21 | offset = Screen.offsetFromLatLng map latLng 22 | width = 18 23 | height = 36 24 | in 25 | Html.span 26 | [ Attr.style 27 | [ ("position", "absolute") 28 | , ("left", toString (offset.x - width/2) ++ "px") 29 | , ("top", toString (offset.y - height) ++ "px") 30 | , ("display", "inline-block") 31 | , ("width", toString width ++ "px") 32 | , ("height", toString height ++ "px") 33 | , ("background-image", "url(\"data:image/svg+xml;utf-8,"++markerSvg++"\")") 34 | , ("background-size", "100% auto") 35 | ] 36 | ] 37 | [ 38 | ] 39 | CustomMarker html latLng -> 40 | let 41 | offset = Screen.offsetFromLatLng map latLng 42 | in 43 | Html.span 44 | [ Attr.style 45 | [ ("position", "absolute") 46 | , ("left", toString offset.x ++ "px") 47 | , ("top", toString offset.y ++ "px") 48 | , ("pointer-events", "initial") 49 | , ("display", "inline-block") 50 | , ("text-align", "center") 51 | , ("-webkit-transform", "translateX(-50%) translateY(-50%)") 52 | , ("-moz-transform", "translateX(-50%) translateY(-50%)") 53 | , ("transform", "translateX(-50%) translateY(-50%)") 54 | ] 55 | ] 56 | [ html 57 | ] 58 | 59 | markerSvg = 60 | """""" 61 | -------------------------------------------------------------------------------- /src/Maps/Internal/OpaqueTypes.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.OpaqueTypes exposing 2 | ( Model(..) 3 | , Map(..) 4 | , opaqueModel 5 | , opaqueMap 6 | , transparentMap 7 | ) 8 | 9 | import Maps.Internal.Map as Map 10 | import Maps.Internal.Maps as Maps 11 | 12 | type Model msg = Model (Maps.Model msg) 13 | type Map = Map Map.Map 14 | 15 | opaqueModel : (Maps.Model msg -> Maps.Model msg ) -> (Model msg -> Model msg) 16 | opaqueModel update (Model model) = Model <| update model 17 | 18 | opaqueMap : (Map.Map -> Map.Map) -> (Map -> Map) 19 | opaqueMap update (Map map) = Map <| update map 20 | 21 | transparentMap : (Map -> Map) -> (Map.Map -> Map.Map) 22 | transparentMap update map = 23 | let 24 | (Map updatedMap) = update <| Map map 25 | in 26 | updatedMap 27 | -------------------------------------------------------------------------------- /src/Maps/Internal/Pinch.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Pinch exposing 2 | ( Pinch 3 | , start 4 | , pinch 5 | , startEnd 6 | ) 7 | 8 | import Maps.Internal.Screen as Screen exposing (TwoFingers) 9 | 10 | type Pinch 11 | = StartPinch TwoFingers 12 | | Pinch TwoFingers TwoFingers 13 | 14 | start : TwoFingers -> Pinch 15 | start = StartPinch 16 | 17 | pinch : TwoFingers -> Pinch -> Pinch 18 | pinch twoFingers state = 19 | case state of 20 | StartPinch start -> Pinch start twoFingers 21 | Pinch start end -> Pinch start twoFingers 22 | 23 | startEnd : Pinch -> (TwoFingers, TwoFingers) 24 | startEnd pinch = 25 | case pinch of 26 | StartPinch start -> (start, start) 27 | Pinch start end -> (start, end) 28 | -------------------------------------------------------------------------------- /src/Maps/Internal/Screen.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Screen exposing 2 | ( Offset 3 | , TwoFingers 4 | , ZoomLevel 5 | , offsetToTileOffset 6 | , offsetFromTileOffset 7 | , offsetToLatLng 8 | , offsetFromLatLng 9 | , decodeOffset 10 | , decodeZoom 11 | , decodeTwoFingers 12 | ) 13 | 14 | {-| The Screen module defines common types and functions relating to Map events. 15 | 16 | # Definition 17 | @docs Offset 18 | @docs TwoFingers 19 | @docs ZoomLevel 20 | 21 | # Conversions 22 | @docs offsetToTileOffset 23 | @docs offsetFromTileOffset 24 | @docs offsetToLatLng 25 | @docs offsetFromLatLng 26 | 27 | # Event Decoders 28 | The following are decoders for getting Screen types from [HTML events](https://developer.mozilla.org/en-US/docs/Web/API/Event) 29 | like the [mouse event](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) etc. 30 | 31 | @docs decodeOffset 32 | @docs decodeZoom 33 | @docs decodeTwoFingers 34 | -} 35 | 36 | import Json.Decode as Json 37 | 38 | import Maps.Internal.Utils exposing (wrap) 39 | import Maps.Internal.LatLng exposing (LatLng) 40 | import Maps.Internal.Tile as Tile 41 | 42 | {-| The Offset type defines an offset as it relates pixels making up a map. 43 | For example the position of a mouse click, or a scroll action. 44 | -} 45 | type alias Offset = 46 | { x : Float 47 | , y : Float 48 | } 49 | 50 | {-| The TwoFingers type defines the position of two fingers on the screen. 51 | It is used for the pinching action for zooming. 52 | -} 53 | type alias TwoFingers = 54 | { length : Float 55 | , center : Offset 56 | } 57 | 58 | {-| The level of zooming for the map. 59 | This number has a [specific relation](http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels) to the map tiles shown. 60 | -} 61 | type alias ZoomLevel = Float 62 | 63 | {-| This function is for converting a Screen.Offset to a Tile.Offset. 64 | It is used to figure out which tile, and subsequently which geographic point, a user clicked on for example. 65 | 66 | Note that it needs to know the zoom level, size of the tiles, size of the map and position of the map to perform this conversion. 67 | -} 68 | offsetToTileOffset : {a | tileSize : Float, zoom : ZoomLevel, width : Float, height : Float, center : LatLng} -> Offset -> Tile.Offset 69 | offsetToTileOffset {tileSize, zoom, width, height, center} offset = 70 | let 71 | centerTile = Tile.fromLatLng (toFloat <| ceiling zoom) center 72 | centerOffset = Offset (offset.x - width/2) (offset.y - height/2) 73 | in 74 | Tile.Offset 75 | (centerTile.x + centerOffset.x/tileSize) 76 | (centerTile.y + centerOffset.y/tileSize) 77 | 78 | {-| This function is for converting a Tile.Offset to a Screen.Offset. 79 | 80 | Note that it needs to know the zoom level, size of the tiles, size of the map and position of the map to perform this conversion. 81 | -} 82 | offsetFromTileOffset : {a | tileSize : Float, zoom : ZoomLevel, width : Float, height : Float, center : LatLng} -> Tile.Offset -> Offset 83 | offsetFromTileOffset {tileSize, zoom, width, height, center} tile = 84 | let 85 | centerTile = Tile.fromLatLng (toFloat <| ceiling zoom) center 86 | in 87 | Offset 88 | ((tile.x - centerTile.x)*tileSize + width/2) 89 | ((tile.y - centerTile.y)*tileSize + height/2) 90 | 91 | {-| This function is for converting a Screen.Offset to a LatLng. 92 | 93 | Note that it needs to know the zoom level, size of the tiles, size of the map and position of the map to perform this conversion. 94 | -} 95 | offsetToLatLng : {a | tileSize : Float, zoom : ZoomLevel, width : Float, height : Float, center : LatLng} -> Offset -> LatLng 96 | offsetToLatLng ({zoom} as map) offset = 97 | offset 98 | |> offsetToTileOffset map 99 | |> \tile -> Tile.toLatLng (toFloat <| ceiling zoom) tile.x tile.y 100 | 101 | {-| This function is for converting a LatLng to a Screen.Offset. 102 | 103 | Note that it needs to know the zoom level, size of the tiles, size of the map and position of the map to perform this conversion. 104 | -} 105 | offsetFromLatLng : {a | tileSize : Float, zoom : ZoomLevel, width : Float, height : Float, center : LatLng} -> LatLng -> Offset 106 | offsetFromLatLng ({tileSize, zoom} as map) latLng = 107 | latLng 108 | |> Tile.fromLatLng (toFloat <| ceiling zoom) 109 | |> offsetFromTileOffset map 110 | 111 | {-| Decodes an HTML event which has an X and Y location into an Offset 112 | -} 113 | decodeOffset : Json.Decoder Offset 114 | decodeOffset = 115 | let 116 | xy = Json.map2 Offset 117 | (Json.field "clientX" Json.float) 118 | (Json.field "clientY" Json.float) 119 | in 120 | Json.oneOf 121 | [ xy 122 | , Json.at ["touches", "0"] xy 123 | ] 124 | 125 | {-| Decodes an [HTML wheel event](https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent) into a ZoomLevel 126 | -} 127 | decodeZoom : Json.Decoder ZoomLevel 128 | decodeZoom = 129 | Json.map scrollToZoom 130 | (Json.field "deltaY" Json.float) 131 | 132 | scrollToZoom : Float -> ZoomLevel 133 | scrollToZoom scroll = 134 | negate (scroll * 0.02) 135 | 136 | {-| Decodes a touch event into a zoom level and movement offset. 137 | -} 138 | decodeTwoFingers : Json.Decoder (Maybe TwoFingers) 139 | decodeTwoFingers = 140 | let 141 | xy = Json.map2 Offset 142 | (Json.field "clientX" Json.float) 143 | (Json.field "clientY" Json.float) 144 | twoFingers first second = 145 | TwoFingers 146 | (((first.x - second.x)^2 + (first.y - second.y)^2)^0.5) 147 | (Offset ((first.x+second.x)/2) ((first.y+second.y)/2)) 148 | in 149 | Json.maybe 150 | <| Json.map2 twoFingers 151 | (Json.at ["touches", "0"] xy) 152 | (Json.at ["touches", "1"] xy) 153 | -------------------------------------------------------------------------------- /src/Maps/Internal/Tile.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Tile exposing 2 | ( Tile 3 | , Offset 4 | , url 5 | , fromLatLng 6 | , toLatLng 7 | , view 8 | ) 9 | 10 | import Regex exposing (HowMany(..), regex) 11 | 12 | import Html exposing (Html) 13 | import Html.Attributes as Attr 14 | 15 | import Maps.Internal.LatLng as LatLng exposing (LatLng) 16 | import Maps.Internal.Utils exposing (wrap, sinh) 17 | 18 | type alias Url = String 19 | type alias Tile = (Url, Offset) 20 | 21 | type alias Offset = 22 | { x : Float 23 | , y : Float 24 | } 25 | 26 | url : String -> Int -> Int -> Int -> Url 27 | url tileServer zoom x y = 28 | tileServer 29 | |> formatInt "{z}" zoom 30 | |> formatInt "{x}" x 31 | |> formatInt "{y}" y 32 | 33 | fromLatLng : Float -> LatLng -> Offset 34 | fromLatLng zoom loc = 35 | let 36 | n = 2 ^ zoom 37 | x = n * ((loc.lng + 180) / 360) |> wrap 0 n 38 | latRad = loc.lat * pi / 180 39 | y = n * (1 - (logBase e <| abs <| tan latRad + (1/cos latRad)) / pi) / 2 40 | in 41 | Offset x y 42 | 43 | toLatLng : Float -> Float -> Float -> LatLng 44 | toLatLng zoom tileX tileY = 45 | let 46 | n = 2 ^ zoom 47 | lngDeg = tileX / n * 360 - 180 |> wrap -180 180 48 | latRad = atan <| sinh <| pi * (1 - 2 * tileY / n) 49 | latDeg = latRad * 180 / pi 50 | in 51 | LatLng latDeg lngDeg 52 | 53 | formatInt : String -> Int -> String -> String 54 | formatInt replace number = 55 | Regex.replace All (regex replace) (\_ -> toString number) 56 | 57 | view : Float -> Tile -> Html msg 58 | view tileSize (url, offset) = 59 | Html.img 60 | [ Attr.src url 61 | , Attr.style 62 | [ ("position", "absolute") 63 | , ("left", toString offset.x ++ "px") 64 | , ("top", toString offset.y ++ "px") 65 | , ("width", toString tileSize ++ "px") 66 | , ("height", toString tileSize ++ "px") 67 | , ("background-color", "rgba(0,0,0, 0)") 68 | ] 69 | ] 70 | [] 71 | -------------------------------------------------------------------------------- /src/Maps/Internal/Utils.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Utils exposing 2 | ( wrap 3 | , sinh 4 | , cartesianMap 5 | ) 6 | 7 | wrap min max n = 8 | if n < min then 9 | wrap min max <| n + (max-min) 10 | else if n >= max then 11 | wrap min max <| n - (max-min) 12 | else 13 | n 14 | 15 | sinh x = 16 | ((e ^ x) - (e ^ -x)) / 2 17 | 18 | cartesianMap : (a -> b -> c) -> List a -> List b -> List (List c) 19 | cartesianMap function rows columns = 20 | (flip function) 21 | |> (flip List.map) columns 22 | |> List.map ((flip List.map) rows) 23 | -------------------------------------------------------------------------------- /src/Maps/Internal/Zoom.elm: -------------------------------------------------------------------------------- 1 | module Maps.Internal.Zoom exposing 2 | ( EventOptions 3 | , fromPinch 4 | , events 5 | ) 6 | 7 | import Json.Decode as Json 8 | 9 | import Html 10 | import Html.Events exposing (on, onWithOptions) 11 | 12 | import Maps.Internal.Screen as Screen exposing (ZoomLevel) 13 | import Maps.Internal.Pinch as Pinch exposing (Pinch) 14 | 15 | type alias EventOptions msg = 16 | { zoom : Screen.Offset -> ZoomLevel -> msg 17 | , pinchStart : Screen.TwoFingers -> msg 18 | , pinchTo : Screen.TwoFingers -> msg 19 | , pinchStop : msg 20 | } 21 | 22 | fromPinch : Float -> Float -> Pinch -> (ZoomLevel, Screen.Offset) 23 | fromPinch mapWidth mapHeight pinch = 24 | let 25 | (start, end) = Pinch.startEnd pinch 26 | in 27 | ( logBase 2 (end.length / start.length) 28 | , start.center 29 | ) 30 | 31 | events : EventOptions msg -> ZoomLevel -> List (Html.Attribute msg) 32 | events ({zoom, pinchStart, pinchTo, pinchStop}) mapZoom = 33 | [ -- Mouse 34 | onWithOptions "dblclick" 35 | { preventDefault = True, stopPropagation = False } 36 | <| Json.map (\offset -> zoom offset 1) 37 | <| Screen.decodeOffset 38 | , --Mouse 39 | onWithOptions "wheel" 40 | { preventDefault = True, stopPropagation = False } 41 | <| Json.map2 42 | zoom 43 | Screen.decodeOffset 44 | Screen.decodeZoom 45 | , -- Mobile 46 | onWithOptions "touchstart" 47 | { preventDefault = True, stopPropagation = False } 48 | <| Json.map (Maybe.withDefault pinchStop) 49 | <| Json.map (Maybe.map pinchStart) 50 | <| Screen.decodeTwoFingers 51 | , -- Mobile 52 | onWithOptions "touchmove" 53 | { preventDefault = True, stopPropagation = False } 54 | <| Json.map (Maybe.withDefault pinchStop) 55 | <| Json.map (Maybe.map pinchTo) 56 | <| Screen.decodeTwoFingers 57 | , -- Mobile 58 | onWithOptions "touchend" 59 | { preventDefault = True, stopPropagation = False } 60 | <| Json.succeed pinchStop 61 | ] 62 | -------------------------------------------------------------------------------- /src/Maps/Map.elm: -------------------------------------------------------------------------------- 1 | module Maps.Map exposing 2 | ( Map 3 | , setTileServer 4 | , setWidth 5 | , setHeight 6 | , setTileSize 7 | , move 8 | , moveTo 9 | , setZoom 10 | , zoom 11 | , zoomTo 12 | , viewBounds 13 | ) 14 | 15 | {-| Functions for manipulating the Map. 16 | 17 | @docs Map 18 | 19 | # Setters 20 | The setters can be used to modify a map 21 | 22 | For example, MapBox tiles on a large map: 23 | 24 | map 25 | |> setTileServer ("https://api.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=" ++ accessToken) 26 | |> setWidth 1200 27 | |> setHeight 800 28 | 29 | @docs setTileServer 30 | @docs setWidth 31 | @docs setHeight 32 | @docs setTileSize 33 | 34 | # Map Movement and Zooming 35 | @docs move 36 | @docs moveTo 37 | @docs setZoom 38 | @docs zoom 39 | @docs zoomTo 40 | @docs viewBounds 41 | -} 42 | 43 | import Maps.Internal.OpaqueTypes as OpaqueTypes exposing (Map(..), opaqueMap) 44 | import Maps.Geo 45 | import Maps.Internal.Map as Map 46 | import Maps.Internal.Screen as Screen 47 | 48 | {-| The map type contains all the information necessary to display a map on the screen. 49 | The map type is opaque, so use the functions in this module to maniuplate the map. 50 | -} 51 | type alias Map = OpaqueTypes.Map 52 | 53 | {-| Set the tile server. 54 | 55 | The format is a URL with {x} {y} {z} placeholders for the x, y and zoom coordinates. 56 | -} 57 | setTileServer : String -> Map -> Map 58 | setTileServer tileServer = opaqueMap <| Map.setTileServer tileServer 59 | 60 | {-| Set the width, as displayed on the screen, of a given Map. 61 | -} 62 | setWidth : Float -> Map -> Map 63 | setWidth width = opaqueMap <| Map.setWidth width 64 | 65 | {-| Set the height, as displayed on the screen, of a given Map. 66 | -} 67 | setHeight : Float -> Map -> Map 68 | setHeight height = opaqueMap <| Map.setHeight height 69 | 70 | {-| Set the width/height in px of a tile. 71 | Note, this is dependent on the tile server, and the default of 256px is almost always correct. 72 | -} 73 | setTileSize : Float -> Map -> Map 74 | setTileSize tileSize = opaqueMap <| Map.setTileSize tileSize 75 | 76 | {-| Move the map a given number of pixels in the x and y dimensions. 77 | 78 | For example, up 10px and right 10px: 79 | 80 | map 81 | |> move { x = 10, y = -10 } 82 | -} 83 | move : Screen.Offset -> Map -> Map 84 | move offset = opaqueMap <| Map.move offset 85 | 86 | {-| Move the center of the map to a specific location. 87 | 88 | For example, move the map to Shanghai: 89 | 90 | map 91 | |> moveTo (Maps.Geo.LatLng 31.267401 121.522179) 92 | -} 93 | moveTo : Maps.Geo.LatLng -> Map -> Map 94 | moveTo latLng = opaqueMap <| Map.moveTo latLng 95 | 96 | {-| Sets the zoom to a specific level 97 | 98 | For example, zoom all the way out 99 | 100 | map 101 | |> zoom 0 102 | 103 | Or all the way in: 104 | 105 | map 106 | |> zoom 19 107 | -} 108 | setZoom : Float -> Map -> Map 109 | setZoom zoomLevel = opaqueMap <| Map.setZoom zoomLevel 110 | 111 | {-| Zoom into the center of the map. 112 | 113 | For example zoom out two levels: 114 | 115 | map 116 | |> zoom -2 117 | -} 118 | zoom : Float -> Map -> Map 119 | zoom zoomLevel = opaqueMap <| Map.zoom zoomLevel 120 | 121 | {-| Zoom into an x,y co-ordinate on the map. 122 | 123 | For example, zoom into the top left corner of the map: 124 | 125 | map 126 | |> zoom 1 { x = 0, y = 0} 127 | -} 128 | zoomTo : Float -> Screen.Offset -> Map -> Map 129 | zoomTo zoom offset = opaqueMap <| Map.zoomTo zoom offset 130 | 131 | {-| Move and zoom the map to cover given the bounds. 132 | 133 | For example, view the bounds of Madagascar: 134 | 135 | let 136 | madagascar = 137 | Maps.Geo.bounds 138 | { northEast = Maps.Geo.latLng -11.9519639 50.48377989999999 139 | , southWest = Maps.Geo.latLng -25.6065157 43.1851395 140 | } 141 | in 142 | map 143 | |> viewBounds madagascar 144 | 145 | -} 146 | viewBounds : Maps.Geo.Bounds -> Map -> Map 147 | viewBounds bounds = opaqueMap <| Map.viewBounds bounds 148 | -------------------------------------------------------------------------------- /src/Maps/Marker.elm: -------------------------------------------------------------------------------- 1 | module Maps.Marker exposing 2 | ( Marker 3 | , create 4 | , createCustom 5 | ) 6 | 7 | {-| Markers are for displaying geographic locations on the map. 8 | 9 | @docs Marker 10 | 11 | # Create a marker 12 | @docs create 13 | @docs createCustom 14 | -} 15 | 16 | import Html exposing (Html) 17 | 18 | import Maps.Geo 19 | import Maps.Internal.Marker as Marker exposing (Marker(..)) 20 | 21 | {-| There are currently two types of marker: 22 | 23 | * A default marker 24 | * A custom HTML marker 25 | -} 26 | type alias Marker msg = Marker.Marker msg 27 | 28 | 29 | {-| Create a default style of marker at the given latitude/longitude. 30 | 31 | import Maps.Marker 32 | import Maps.Geo 33 | 34 | newYork = Maps.Geo.latLng 40.730610 -73.935242 35 | newYorkMarker = Maps.Marker.create newYork 36 | 37 | -} 38 | create : Maps.Geo.LatLng -> Marker msg 39 | create = Marker.DefaultMarker 40 | 41 | {-| Create a custom HTML marker at the given latitude/longitude. 42 | -} 43 | createCustom : Html msg -> Maps.Geo.LatLng -> Marker msg 44 | createCustom = Marker.CustomMarker 45 | -------------------------------------------------------------------------------- /tests/Expects.elm: -------------------------------------------------------------------------------- 1 | module Expects exposing 2 | ( equalMap 3 | , equalLatLng 4 | , equalOffsets 5 | ) 6 | 7 | import Expect exposing (Expectation) 8 | 9 | import Maps.Internal.Utils exposing (wrap) 10 | import Maps.Internal.Map exposing (Map) 11 | import Maps.Internal.LatLng exposing (LatLng) 12 | import Maps.Internal.Screen exposing (Offset) 13 | 14 | equalMap : Float -> Map -> Map -> Expectation 15 | equalMap delta mapA mapB = 16 | Expect.all 17 | [ \(mapA, mapB) -> Expect.equal mapA.tileServer mapB.tileServer 18 | , \(mapA, mapB) -> Expect.equal mapA.zoom mapB.zoom 19 | , \(mapA, mapB) -> equalLatLng delta mapA.center mapB.center 20 | , \(mapA, mapB) -> Expect.equal mapA.width mapB.width 21 | , \(mapA, mapB) -> Expect.equal mapA.height mapB.height 22 | , \(mapA, mapB) -> Expect.equal mapA.tileSize mapB.tileSize 23 | ] 24 | (mapA, mapB) 25 | 26 | equalLatLng : Float -> LatLng -> LatLng -> Expectation 27 | equalLatLng delta latLngA latLngB = 28 | let 29 | latDiff = abs <| latLngA.lat - latLngB.lat 30 | lngDiff = abs <| (latLngA.lng |> wrap -180 180) - (latLngB.lng |> wrap -180 180) 31 | in 32 | Expect.true 33 | ("Expected the two offsets to be within "++toString delta++"\n" 34 | ++ toString latLngA ++ "\n" ++ toString latLngB) 35 | <| latDiff < delta && lngDiff < delta 36 | 37 | equalOffsets : Float -> Offset -> Offset -> Expectation 38 | equalOffsets delta offsetA offsetB = 39 | let 40 | xDiff = abs <| offsetA.x - offsetB.x 41 | yDiff = abs <| offsetA.y - offsetB.y 42 | in 43 | Expect.true 44 | ("Expected the two offsets to be within "++toString delta++"\n" 45 | ++ toString offsetA ++ "\n" ++ toString offsetB) 46 | <| xDiff < delta && yDiff < delta 47 | -------------------------------------------------------------------------------- /tests/Fuzzers.elm: -------------------------------------------------------------------------------- 1 | module Fuzzers exposing 2 | ( map 3 | , offset 4 | , noOffset 5 | , zoomLevel 6 | , latLng 7 | ) 8 | 9 | import Fuzz exposing (..) 10 | import Shrink 11 | 12 | import Random.Pcg as Random 13 | import Lazy.List exposing ((:::), empty) 14 | 15 | import Maps.Internal.Screen as Screen exposing (ZoomLevel) 16 | import Maps.Internal.LatLng exposing (LatLng) 17 | import Maps.Internal.Map exposing (..) 18 | 19 | offset : Fuzzer Screen.Offset 20 | offset = 21 | Fuzz.map2 Screen.Offset 22 | offsetSingle 23 | offsetSingle 24 | 25 | noOffset : Fuzzer Screen.Offset 26 | noOffset = 27 | (constant <| Screen.Offset 0 0) 28 | 29 | offsetSingle : Fuzzer Float 30 | offsetSingle = 31 | signedRange -5000 5000 32 | 33 | map : Fuzzer Map 34 | map = 35 | Fuzz.map5 (Map "http://a.tile.osm.org/{z}/{x}/{y}.png") 36 | zoomLevel -- Zoom level 37 | latLng -- Map center 38 | (constant 500) -- Width 39 | (constant 500) -- Height 40 | (constant 256) -- Tile size 41 | 42 | zoomLevel : Fuzzer ZoomLevel 43 | zoomLevel = 44 | Fuzz.map toFloat 45 | <| intRange 1 20 46 | 47 | latLng : Fuzzer LatLng 48 | latLng = 49 | Fuzz.map2 LatLng 50 | lat 51 | lng 52 | 53 | lat : Fuzzer Float 54 | lat = 55 | signedRange -90 90 56 | 57 | lng : Fuzzer Float 58 | lng = 59 | signedRange -180 180 60 | 61 | signedRange min max = 62 | Fuzz.custom 63 | (Random.float min max) 64 | (shrinkSignedRange min max) 65 | 66 | shrinkSignedRange min max val = 67 | if val == min || val == max || val == 0 then 68 | empty 69 | else if val < min then 70 | min ::: empty 71 | else if val > max then 72 | max ::: empty 73 | else if floor val /= val || ceiling val /= val then 74 | (toFloat <| floor val) ::: 0 ::: empty 75 | else if val < 0 then 76 | (toFloat <| floor val // 2) ::: empty 77 | else 78 | empty 79 | -------------------------------------------------------------------------------- /tests/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (..) 2 | 3 | import Test exposing (..) 4 | 5 | import Tests.Map 6 | import Tests.Screen 7 | import Tests.Tile 8 | 9 | tests : Test 10 | tests = 11 | describe "All tests" 12 | [ describe "Map tests" 13 | [ Tests.Map.moveTest 14 | , Tests.Map.zoomToTest 15 | ] 16 | , describe "Screen tests" 17 | [ Tests.Screen.conversionsTest 18 | ] 19 | , describe "Tile tests" 20 | [ Tests.Tile.conversionsTest 21 | ] 22 | ] 23 | -------------------------------------------------------------------------------- /tests/Tests/Map.elm: -------------------------------------------------------------------------------- 1 | module Tests.Map exposing 2 | ( moveTest 3 | , zoomToTest 4 | ) 5 | 6 | import Expect exposing (Expectation) 7 | import Test exposing (..) 8 | 9 | import Fuzzers exposing (..) 10 | import Expects 11 | 12 | import Maps.Internal.Screen as Screen exposing (ZoomLevel) 13 | import Maps.Internal.Tile as Tile 14 | import Maps.Internal.LatLng exposing (LatLng) 15 | import Maps.Internal.Map exposing (..) 16 | 17 | moveTest : Test 18 | moveTest = 19 | describe "Map movement tests" 20 | [ describe "Fuzz test map movement" 21 | [ fuzz2 noOffset map "Moving a map with no offset should not change the map" 22 | <| \offset map -> 23 | Expects.equalMap 0.0001 24 | map 25 | (move offset map) 26 | , fuzz2 offset map "Moving a map should result in a change in the map" 27 | <| \offset map -> 28 | if offset.x == 0 && offset.y == 0 then 29 | Expect.equal map <| move offset map 30 | else 31 | Expect.notEqual map <| move offset map 32 | ] 33 | ] 34 | 35 | zoomToTest : Test 36 | zoomToTest = 37 | describe "Map zoom to tests" 38 | [ describe "Fuzz test zoom to functionality" 39 | [ fuzz3 zoomLevel offset latLng 40 | "Zooming to an offset should result in that offset's latitude and longitude being at the same offset" 41 | <| \zoomLevel offset center -> 42 | let 43 | map = { tileServer = "", zoom = 15, center = center, width = 5000, height = 5000, tileSize = 256 } 44 | in 45 | Expects.equalLatLng 0.0001 46 | (Screen.offsetToLatLng map offset) 47 | (Screen.offsetToLatLng (zoomTo zoomLevel offset map) offset) 48 | ] 49 | ] 50 | -------------------------------------------------------------------------------- /tests/Tests/Screen.elm: -------------------------------------------------------------------------------- 1 | module Tests.Screen exposing 2 | ( conversionsTest 3 | ) 4 | 5 | import Test exposing (..) 6 | 7 | import Fuzzers exposing (..) 8 | import Expects exposing (..) 9 | 10 | import Maps.Internal.LatLng exposing (LatLng) 11 | import Maps.Internal.Utils exposing (wrap) 12 | import Maps.Internal.Screen exposing (..) 13 | 14 | conversionsTest : Test 15 | conversionsTest = 16 | describe "Screen offset conversion tests" 17 | [ fuzz offset "Converting a screen offset to and from a LatLng should cause no change in the offset" 18 | <| \offset -> 19 | let 20 | map = { zoom = 18, width = 10000, height = 10000, center = {lat = 0, lng = 0}, tileSize = 256 } 21 | loc = offsetToLatLng map offset 22 | convertedOffset = offsetFromLatLng map loc 23 | in 24 | equalOffsets 0.5 25 | offset 26 | convertedOffset 27 | , fuzz latLng "Converting central offset to a LatLng should be the same as the center LatLng of the map" 28 | <| \center -> 29 | let 30 | map = { zoom = 18, width = 512, height = 512, center = center, tileSize = 256 } 31 | latLng = offsetToLatLng map {x = map.width/2, y = map.height/2} 32 | in 33 | equalLatLng 0.0001 34 | center 35 | latLng 36 | ] 37 | -------------------------------------------------------------------------------- /tests/Tests/Tile.elm: -------------------------------------------------------------------------------- 1 | module Tests.Tile exposing 2 | ( conversionsTest 3 | ) 4 | 5 | import Test exposing (..) 6 | 7 | import Fuzzers exposing (..) 8 | import Expects exposing (..) 9 | 10 | import Maps.Internal.LatLng exposing (LatLng) 11 | import Maps.Internal.Tile exposing (..) 12 | 13 | conversionsTest : Test 14 | conversionsTest = 15 | describe "Tile offset conversion tests" 16 | [ fuzz latLng "Converting a LatLng to and from a tile offset should cause no change in the LatLng" 17 | <| \loc -> 18 | let 19 | zoom = 15 20 | tile = fromLatLng zoom loc 21 | convertedLoc = toLatLng zoom tile.x tile.y 22 | in 23 | equalLatLng 0.0001 24 | loc 25 | convertedLoc 26 | ] 27 | -------------------------------------------------------------------------------- /tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Test Suites", 4 | "repository": "https://github.com/kennib/elm-maps.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "../src", 8 | "." 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "eeue56/elm-html-test": "4.1.0 <= v < 5.0.0", 13 | "elm-community/elm-test": "4.0.0 <= v < 5.0.0", 14 | "elm-community/lazy-list": "1.0.0 <= v < 2.0.0", 15 | "elm-community/list-extra": "6.1.0 <= v < 7.0.0", 16 | "elm-community/shrink": "2.0.0 <= v < 3.0.0", 17 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 18 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 19 | "mgold/elm-random-pcg": "5.0.0 <= v < 6.0.0" 20 | }, 21 | "elm-version": "0.18.0 <= v < 0.19.0" 22 | } 23 | --------------------------------------------------------------------------------