aboutsummaryrefslogtreecommitdiffstats
path: root/src/Mapbox/Source.elm
blob: 5ca38ea997dfc4034f3f60e36ec68d614c13c211 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
module Mapbox.Source exposing
    ( Source, SourceOption
    , Id, Url, decode
    , vector, vectorFromUrl, VectorSource
    , raster, tileSize, rasterFromUrl, RasterSource
    , rasterDEMMapbox, rasterDEMTerrarium
    , geoJSONFromUrl, geoJSONFromValue, GeoJSONSource, buffer, tolerance, cluster, clusterRadius, clusterProperties, lineMetrics, generateIds
    , Coords, image, video, staticCanvas, animatedCanvas
    , bounds, minzoom, maxzoom, attribution, scheme, Scheme(..)
    , encode, getId
    )

{-|


# Sources

@docs Source, SourceOption

@docs Id, Url, decode


### Vector

@docs vector, vectorFromUrl, VectorSource


### Raster

@docs raster, tileSize, rasterFromUrl, RasterSource


### Raster DEM

@docs rasterDEMMapbox, rasterDEMTerrarium


### GeoJSON

@docs geoJSONFromUrl, geoJSONFromValue, GeoJSONSource, buffer, tolerance, cluster, clusterRadius, clusterProperties, lineMetrics, generateIds


### Image, Video & Canvas

@docs Coords, image, video, staticCanvas, animatedCanvas


### Tiled sources

Tiled sources can also take the following attributes:

@docs bounds, minzoom, maxzoom, attribution, scheme, Scheme


### Working with sources

@docs encode, getId

-}

import Dict
import Internal
import Json.Decode as Decode exposing (Decoder)
import Json.Encode exposing (Value)
import LngLat exposing (LngLat)
import Mapbox.Expression as Expression exposing (DataExpression, Expression)


{-| Every layer is identified by an id.
-}
type alias Id =
    String


{-| Represents a URL. For tiles hosted by Mapbox, the "url" value should be of the form mapbox://mapid.
-}
type alias Url =
    String


{-| Sources supply data to be shown on the map. Adding a source won't immediately make data appear on the map because sources don't contain styling details like color or width. Layers refer to a source and give it a visual representation. This makes it possible to style the same source in different ways, like differentiating between types of roads in a highways layer.
-}
type Source
    = Source String Value


{-| `XYZ`: Slippy map tilenames scheme.

`TMS`: OSGeo spec scheme.

-}
type Scheme
    = XYZ
    | TMS


{-| Marks attributes that are only applicable to vector sources
-}
type VectorSource
    = VectorSource


{-| Marks attributes that are only applicable to raster sources
-}
type RasterSource
    = RasterSource


{-| Marks attributes that are only applicable to GeoJSON sources
-}
type GeoJSONSource
    = GeoJSONSource


{-| Some sources can take options.
-}
type SourceOption sourceType
    = SourceOption String Value


{-| -}
encode : Source -> Value
encode (Source _ value) =
    value


{-| -}
decode : Decoder (List Source)
decode =
    Decode.dict Decode.value
        |> Decode.map (Dict.toList >> List.map tupleToSource)


tupleToSource : ( String, Value ) -> Source
tupleToSource ( id, value ) =
    Source id value


{-| -}
getId : Source -> String
getId (Source k _) =
    k


{-| The longitude and latitude of the southwest and northeast corners of the source's bounding box. When this property is included in a source, no tiles outside of the given bounds are requested by Mapbox GL.
-}
bounds : LngLat -> LngLat -> SourceOption any
bounds sw ne =
    SourceOption "bounds" (Json.Encode.list Json.Encode.float [ sw.lng, sw.lat, ne.lng, ne.lat ])


{-| Minimum zoom level for which tiles are available, as in the TileJSON spec.
-}
minzoom : Float -> SourceOption any
minzoom z =
    SourceOption "minzoom" (Json.Encode.float z)


{-| Maximum zoom level for which tiles are available, as in the TileJSON spec. Data from tiles at the maxzoom are used when displaying the map at higher zoom levels.
-}
maxzoom : Float -> SourceOption any
maxzoom z =
    SourceOption "maxzoom" (Json.Encode.float z)


{-| Contains an attribution to be displayed when the map is shown to a user.
-}
attribution : String -> SourceOption any
attribution att =
    SourceOption "attribution" (Json.Encode.string att)


{-| The minimum visual size to display tiles for this layer.
-}
tileSize : Int -> SourceOption RasterSource
tileSize ts =
    SourceOption "minzoom" (Json.Encode.int ts)


{-| Size of the tile buffer on each side. A value of 0 produces no buffer. A value of 512 produces a buffer as wide as the tile itself. Larger values produce fewer rendering artifacts near tile edges and slower performance. Defaults to 128.
-}
buffer : Int -> SourceOption GeoJSONSource
buffer int =
    SourceOption "buffer" (Json.Encode.int int)


{-| Douglas-Peucker simplification tolerance (higher means simpler geometries and faster performance). Defaults to 0.375.
-}
tolerance : Float -> SourceOption GeoJSONSource
tolerance float =
    SourceOption "tolerance" (Json.Encode.float float)


{-| If the data is a collection of point features, setting this clusters the points by radius into groups.

Cluster groups become new Point features in the source with additional properties:

  - `cluster` Is `True` if the point is a cluster
  - `cluster_id` A unqiue id for the cluster.
  - `point_count` Number of original points grouped into this cluster
  - `point_count_abbreviated` An abbreviated point count

-}
cluster : SourceOption GeoJSONSource
cluster =
    Json.Encode.bool True |> SourceOption "cluster"


{-| Radius of each cluster if clustering is enabled. A value of 512 indicates a radius equal to the width of a tile.
-}
clusterRadius : Float -> SourceOption GeoJSONSource
clusterRadius =
    Json.Encode.float >> SourceOption "clusterRadius"


{-| Max zoom on which to cluster points if clustering is enabled. Defaults to one zoom less than maxzoom (so that last zoom features are not clustered).
-}
clusterMaxZoom : Float -> SourceOption GeoJSONSource
clusterMaxZoom =
    Json.Encode.float >> SourceOption "clusterMaxZoom"


{-| When clustering, you may want to aggregate values from the points included in the cluster. This function allows you to associate a name that will be the property name with a fold that computes the aggregation. For example:

    clusterProperties
        [ ( "sum", E.plus, E.getProperty (str "scalerank") )
        , ( "max", \point aggregate -> E.max aggregate point, E.getProperty (str "scalerank") )
        ]

Would produce clusters with two additional properties: `sum` and `max`, which you could then further use in data driven styling.

-}
clusterProperties : List ( String, Expression DataExpression a -> Expression DataExpression b -> Expression DataExpression b, Expression DataExpression a ) -> SourceOption GeoJSONSource
clusterProperties =
    List.map
        (\( name, fold, map ) ->
            ( name
            , Json.Encode.list identity
                [ Expression.encode <| fold (Expression.getProperty (Expression.str name)) (Internal.Expression (Json.Encode.list Json.Encode.string [ "accumulated" ]))
                , Expression.encode <| map
                ]
            )
        )
        >> Json.Encode.object
        >> SourceOption "clusterProperties"


{-| Whether to calculate line distance metrics. This is required for line layers that specify `lineGradient` values.
-}
lineMetrics : Bool -> SourceOption GeoJSONSource
lineMetrics =
    Json.Encode.bool >> SourceOption "lineMetrics"


{-| When set, the feature.id property will be auto assigned based on its index in the features array, over-writing any previous values.
-}
generateIds : SourceOption GeoJSONSource
generateIds =
    Json.Encode.bool True |> SourceOption "generateId"


{-| Influences the y direction of the tile coordinates. The global-mercator (aka Spherical Mercator) profile is assumed.
-}
scheme : Scheme -> SourceOption any
scheme s =
    case s of
        XYZ ->
            SourceOption "scheme" (Json.Encode.string "xyz")

        TMS ->
            SourceOption "scheme" (Json.Encode.string "tms")


{-| A vector tile source. Tiles must be in [Mapbox Vector Tile format](https://www.mapbox.com/developers/vector-tiles/). All geometric coordinates in vector tiles must be between `-1 * extent` and `(extent * 2) - 1` inclusive. All layers that use a vector source must specify a `sourceLayer` value.

The first argument is the layers id, the second is a url to a [TileJSON specification](https://github.com/mapbox/tilejson-spec) that configures the source.

-}
vectorFromUrl : Id -> Url -> Source
vectorFromUrl id url =
    Source id (Json.Encode.object [ ( "type", Json.Encode.string "vector" ), ( "url", Json.Encode.string url ) ])


{-| A vector tile source. Tiles must be in [Mapbox Vector Tile format](https://www.mapbox.com/developers/vector-tiles/). All geometric coordinates in vector tiles must be between `-1 * extent` and `(extent * 2) - 1` inclusive. All layers that use a vector source must specify a `sourceLayer` value.

This takes an array of one or more tile source URLs, as in the TileJSON spec.

-}
vector : Id -> List Url -> List (SourceOption VectorSource) -> Source
vector id urls options =
    ( "tiles", Json.Encode.list Json.Encode.string urls )
        :: ( "type", Json.Encode.string "vector" )
        :: List.map (\(SourceOption k v) -> ( k, v )) options
        |> Json.Encode.object
        |> Source id


{-| A raster tile source configured from a TileJSON spec.
-}
rasterFromUrl : Id -> Url -> Source
rasterFromUrl id url =
    Source id (Json.Encode.object [ ( "type", Json.Encode.string "raster" ), ( "url", Json.Encode.string url ) ])


{-| A raster tile source. Takes a list of one or more tile source URLs, as in the TileJSON spec.
-}
raster : Id -> List Url -> List (SourceOption RasterSource) -> Source
raster id urls options =
    ( "tiles", Json.Encode.list Json.Encode.string urls )
        :: ( "type", Json.Encode.string "raster" )
        :: List.map (\(SourceOption k v) -> ( k, v )) options
        |> Json.Encode.object
        |> Source id


{-| The [Mapbox Terrain RGB](https://blog.mapbox.com/global-elevation-data-6689f1d0ba65) DEM source.
-}
rasterDEMMapbox : Id -> Source
rasterDEMMapbox id =
    Source id
        (Json.Encode.object
            [ ( "type", Json.Encode.string "raster-dem" )
            , ( "url", Json.Encode.string "mapbox://mapbox.terrain-rgb" )
            , ( "encoding", Json.Encode.string "mapbox" )
            ]
        )


{-| A raster DEM source in the Terarrium format.
-}
rasterDEMTerrarium : Id -> Url -> List (SourceOption RasterSource) -> Source
rasterDEMTerrarium id url options =
    ( "type", Json.Encode.string "raster-dem" )
        :: ( "url", Json.Encode.string url )
        :: ( "encoding", Json.Encode.string "terrarium" )
        :: List.map (\(SourceOption k v) -> ( k, v )) options
        |> Json.Encode.object
        |> Source id


{-| A remote GeoJSON source.
-}
geoJSONFromUrl : Id -> Url -> List (SourceOption GeoJSONSource) -> Source
geoJSONFromUrl id url options =
    ( "data", Json.Encode.string url )
        :: ( "type", Json.Encode.string "geojson" )
        :: List.map (\(SourceOption k v) -> ( k, v )) options
        |> Json.Encode.object
        |> Source id


{-| A GeoJSON source from some local data.
-}
geoJSONFromValue : Id -> List (SourceOption GeoJSONSource) -> Value -> Source
geoJSONFromValue id options data =
    ( "data", data )
        :: ( "type", Json.Encode.string "geojson" )
        :: List.map (\(SourceOption k v) -> ( k, v )) options
        |> Json.Encode.object
        |> Source id


{-| `(longitude, latitude)` pairs for the corners. You can use the type alias constructor in clockwise order: top left, top right, bottom right, bottom left.
-}
type alias Coords =
    { topLeft : LngLat
    , topRight : LngLat
    , bottomRight : LngLat
    , bottomLeft : LngLat
    }


encodeCoordinates : Coords -> Value
encodeCoordinates { topLeft, topRight, bottomRight, bottomLeft } =
    Json.Encode.list LngLat.encodeAsPair
        [ topLeft
        , topRight
        , bottomRight
        , bottomLeft
        ]


{-| An image source
-}
image : Id -> Url -> Coords -> Source
image id url coordinates =
    [ ( "type", Json.Encode.string "image" )
    , ( "url", Json.Encode.string url )
    , ( "coordinates", encodeCoordinates coordinates )
    ]
        |> Json.Encode.object
        |> Source id


{-| A video source. For each URL in the list, a video element source will be created, in order to support same media in multiple formats supported by different browsers.
-}
video : Id -> List Url -> Coords -> Source
video id urls coordinates =
    [ ( "type", Json.Encode.string "video" )
    , ( "urls", Json.Encode.list Json.Encode.string urls )
    , ( "coordinates", encodeCoordinates coordinates )
    ]
        |> Json.Encode.object
        |> Source id


{-| A data source containing the contents of an HTML canvas. The second argument must be the DOM ID of the canvas element. This method is only appropriate with a static Canvas (i.e. one that doesn't change), as it will be cached to improve performance.
-}
staticCanvas : Id -> String -> Coords -> Source
staticCanvas id domid coordinates =
    [ ( "type", Json.Encode.string "canvas" )
    , ( "animate", Json.Encode.bool False )
    , ( "canvas", Json.Encode.string domid )
    , ( "coordinates", encodeCoordinates coordinates )
    ]
        |> Json.Encode.object
        |> Source id


{-| A data source containing the contents of an HTML canvas. The second argument must be the DOM ID of the canvas element. This method is only appropriate with an animated Canvas (i.e. one that changes over time).
-}
animatedCanvas : Id -> String -> Coords -> Source
animatedCanvas id domid coordinates =
    [ ( "type", Json.Encode.string "canvas" )
    , ( "animate", Json.Encode.bool True )
    , ( "canvas", Json.Encode.string domid )
    , ( "coordinates", encodeCoordinates coordinates )
    ]
        |> Json.Encode.object
        |> Source id