@maptiler/sdk 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +15 -5
- package/.github/pull_request_template.md +11 -0
- package/.github/workflows/format-lint.yml +24 -0
- package/CHANGELOG.md +105 -51
- package/colorramp.md +93 -0
- package/dist/maptiler-sdk.d.ts +1226 -124
- package/dist/maptiler-sdk.min.mjs +3 -1
- package/dist/maptiler-sdk.mjs +3582 -483
- package/dist/maptiler-sdk.mjs.map +1 -1
- package/dist/maptiler-sdk.umd.js +4524 -863
- package/dist/maptiler-sdk.umd.js.map +1 -1
- package/dist/maptiler-sdk.umd.min.js +51 -49
- package/package.json +27 -13
- package/readme.md +493 -5
- package/rollup.config.js +2 -16
- package/src/Map.ts +515 -359
- package/src/MaptilerGeolocateControl.ts +23 -20
- package/src/MaptilerLogoControl.ts +3 -3
- package/src/MaptilerNavigationControl.ts +9 -6
- package/src/MaptilerTerrainControl.ts +15 -14
- package/src/Minimap.ts +373 -0
- package/src/Point.ts +3 -5
- package/src/colorramp.ts +1216 -0
- package/src/config.ts +4 -3
- package/src/converters/index.ts +1 -0
- package/src/converters/xml.ts +681 -0
- package/src/defaults.ts +1 -1
- package/src/helpers/index.ts +27 -0
- package/src/helpers/stylehelper.ts +395 -0
- package/src/helpers/vectorlayerhelpers.ts +1511 -0
- package/src/index.ts +90 -121
- package/src/language.ts +116 -79
- package/src/mapstyle.ts +4 -2
- package/src/tools.ts +68 -16
- package/tsconfig.json +8 -5
- package/vite.config.ts +10 -0
- package/demos/maptiler-sdk.css +0 -147
- package/demos/maptiler-sdk.umd.js +0 -4041
- package/demos/mountain.html +0 -67
- package/demos/simple.html +0 -67
- package/demos/transform-request.html +0 -81
|
@@ -0,0 +1,1511 @@
|
|
|
1
|
+
import type { Geometry, FeatureCollection, GeoJsonProperties } from "geojson";
|
|
2
|
+
import type {
|
|
3
|
+
DataDrivenPropertyValueSpecification,
|
|
4
|
+
PropertyValueSpecification,
|
|
5
|
+
} from "maplibre-gl";
|
|
6
|
+
import type { Map } from "../Map";
|
|
7
|
+
import { config } from "../config";
|
|
8
|
+
import { isUUID, jsonParseNoThrow } from "../tools";
|
|
9
|
+
import {
|
|
10
|
+
computeRampedOutlineWidth,
|
|
11
|
+
generateRandomLayerName,
|
|
12
|
+
generateRandomSourceName,
|
|
13
|
+
getRandomColor,
|
|
14
|
+
paintColorOptionsToPaintSpec,
|
|
15
|
+
rampedOptionsToLayerPaintSpec,
|
|
16
|
+
dashArrayMaker,
|
|
17
|
+
colorDrivenByProperty,
|
|
18
|
+
radiusDrivenByProperty,
|
|
19
|
+
opacityDrivenByProperty,
|
|
20
|
+
heatmapIntensityFromColorRamp,
|
|
21
|
+
rampedPropertyValueWeight,
|
|
22
|
+
radiusDrivenByPropertyHeatmap,
|
|
23
|
+
} from "./stylehelper";
|
|
24
|
+
|
|
25
|
+
import { gpx, gpxOrKml, kml } from "../converters";
|
|
26
|
+
import { ColorRampCollection, ColorRamp } from "../colorramp";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Array of string values that depend on zoom level
|
|
30
|
+
*/
|
|
31
|
+
export type ZoomStringValues = Array<{
|
|
32
|
+
/**
|
|
33
|
+
* Zoom level
|
|
34
|
+
*/
|
|
35
|
+
zoom: number;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Value for the given zoom level
|
|
39
|
+
*/
|
|
40
|
+
value: string;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
*
|
|
45
|
+
* Array of number values that depend on zoom level
|
|
46
|
+
*/
|
|
47
|
+
export type ZoomNumberValues = Array<{
|
|
48
|
+
/**
|
|
49
|
+
* Zoom level
|
|
50
|
+
*/
|
|
51
|
+
zoom: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Value for the given zoom level
|
|
55
|
+
*/
|
|
56
|
+
value: number;
|
|
57
|
+
}>;
|
|
58
|
+
|
|
59
|
+
export type PropertyValues = Array<{
|
|
60
|
+
/**
|
|
61
|
+
* Value of the property (input)
|
|
62
|
+
*/
|
|
63
|
+
propertyValue: number;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Value to associate it with (output)
|
|
67
|
+
*/
|
|
68
|
+
value: number;
|
|
69
|
+
}>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Describes how to render a cluster of points
|
|
73
|
+
*/
|
|
74
|
+
export type DataDrivenStyle = Array<{
|
|
75
|
+
/**
|
|
76
|
+
* Numerical value to observe and apply the style upon.
|
|
77
|
+
* In case of clusters, the value to observe is automatically the number of elements in a cluster.
|
|
78
|
+
* In other cases, it can be a provided value.
|
|
79
|
+
*/
|
|
80
|
+
value: number;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Radius of the cluster circle
|
|
84
|
+
*/
|
|
85
|
+
pointRadius: number;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Color of the cluster
|
|
89
|
+
*/
|
|
90
|
+
color: string;
|
|
91
|
+
}>;
|
|
92
|
+
|
|
93
|
+
export type CommonShapeLayerOptions = {
|
|
94
|
+
/**
|
|
95
|
+
* ID to give to the layer.
|
|
96
|
+
* If not provided, an auto-generated ID of the for "maptiler-layer-xxxxxx" will be auto-generated,
|
|
97
|
+
* with "xxxxxx" being a random string.
|
|
98
|
+
*/
|
|
99
|
+
layerId?: string;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* ID to give to the geojson source.
|
|
103
|
+
* If not provided, an auto-generated ID of the for "maptiler-source-xxxxxx" will be auto-generated,
|
|
104
|
+
* with "xxxxxx" being a random string.
|
|
105
|
+
*/
|
|
106
|
+
sourceId?: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A geojson Feature collection or a URL to a geojson or the UUID of a MapTiler Cloud dataset.
|
|
110
|
+
*/
|
|
111
|
+
data: FeatureCollection | string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The ID of an existing layer to insert the new layer before, resulting in the new layer appearing
|
|
115
|
+
* visually beneath the existing layer. If this argument is not specified, the layer will be appended
|
|
116
|
+
* to the end of the layers array and appear visually above all other layers.
|
|
117
|
+
*/
|
|
118
|
+
beforeId?: string;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Zoom level at which it starts to show.
|
|
122
|
+
* Default: `0`
|
|
123
|
+
*/
|
|
124
|
+
minzoom?: number;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Zoom level after which it no longer show.
|
|
128
|
+
* Default: `22`
|
|
129
|
+
*/
|
|
130
|
+
maxzoom?: number;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether or not to add an outline.
|
|
134
|
+
* Default: `false`
|
|
135
|
+
*/
|
|
136
|
+
outline?: boolean;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Color of the outline. This is can be a constant color string or a definition based on zoom levels.
|
|
140
|
+
* Applies only if `.outline` is `true`.
|
|
141
|
+
* Default: `white`
|
|
142
|
+
*/
|
|
143
|
+
outlineColor?: string | ZoomStringValues;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Width of the outline (relative to screen-space). This is can be a constant width or a definition based on zoom levels.
|
|
147
|
+
* Applies only if `.outline` is `true`.
|
|
148
|
+
* Default: `1`
|
|
149
|
+
*/
|
|
150
|
+
outlineWidth?: number | ZoomNumberValues;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Opacity of the outline. This is can be a constant opacity in [0, 1] or a definition based on zoom levels
|
|
154
|
+
* Applies only if `.outline` is `true`.
|
|
155
|
+
* Default: `1`
|
|
156
|
+
*/
|
|
157
|
+
outlineOpacity?: number | ZoomNumberValues;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export type PolylineLayerOptions = CommonShapeLayerOptions & {
|
|
161
|
+
/**
|
|
162
|
+
* Color of the line (or polyline). This is can be a constant color string or a definition based on zoom levels.
|
|
163
|
+
* Default: a color randomly pick from a list
|
|
164
|
+
*/
|
|
165
|
+
lineColor?: string | ZoomStringValues;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Width of the line (relative to screen-space). This is can be a constant width or a definition based on zoom levels
|
|
169
|
+
* Default: `3`
|
|
170
|
+
*/
|
|
171
|
+
lineWidth?: number | ZoomNumberValues;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Opacity of the line. This is can be a constant opacity in [0, 1] or a definition based on zoom levels.
|
|
175
|
+
* Default: `1`
|
|
176
|
+
*/
|
|
177
|
+
lineOpacity?: number | ZoomNumberValues;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* How blury the line is, with `0` being no blur and `10` and beyond being quite blurry.
|
|
181
|
+
* Default: `0`
|
|
182
|
+
*/
|
|
183
|
+
lineBlur?: number | ZoomNumberValues;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Draws a line casing outside of a line's actual path. Value indicates the width of the inner gap.
|
|
187
|
+
* Default: `0`
|
|
188
|
+
*/
|
|
189
|
+
lineGapWidth?: number | ZoomNumberValues;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Sequence of line and void to create a dash pattern. The unit is the line width so that
|
|
193
|
+
* a dash array value of `[3, 1]` will create a segment worth 3 times the width of the line,
|
|
194
|
+
* followed by a spacing worth 1 time the line width, and then repeat.
|
|
195
|
+
*
|
|
196
|
+
* Alternatively, this property can be a string made of underscore and whitespace characters
|
|
197
|
+
* such as `"___ _ "` and internaly this will be translated into [3, 1, 1, 1]. Note that
|
|
198
|
+
* this way of describing dash arrays with a string only works for integer values.
|
|
199
|
+
*
|
|
200
|
+
* Dash arrays can contain more than 2 element to create more complex patters. For instance
|
|
201
|
+
* a dash array value of [3, 2, 1, 2] will create the following sequence:
|
|
202
|
+
* - a segment worth 3 times the width
|
|
203
|
+
* - a spacing worth 2 times the width
|
|
204
|
+
* - a segment worth 1 times the width
|
|
205
|
+
* - a spacing worth 2 times the width
|
|
206
|
+
* - repeat
|
|
207
|
+
*
|
|
208
|
+
* Default: no dash pattern
|
|
209
|
+
*/
|
|
210
|
+
lineDashArray?: Array<number> | string;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The display of line endings for both the line and the outline (if `.outline` is `true`)
|
|
214
|
+
* - "butt": A cap with a squared-off end which is drawn to the exact endpoint of the line.
|
|
215
|
+
* - "round": A cap with a rounded end which is drawn beyond the endpoint of the line at a radius of one-half of the line's width and centered on the endpoint of the line.
|
|
216
|
+
* - "square": A cap with a squared-off end which is drawn beyond the endpoint of the line at a distance of one-half of the line's width.
|
|
217
|
+
* Default: "round"
|
|
218
|
+
*/
|
|
219
|
+
lineCap?: "butt" | "round" | "square";
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* The display of lines when joining for both the line and the outline (if `.outline` is `true`)
|
|
223
|
+
* - "bevel": A join with a squared-off end which is drawn beyond the endpoint of the line at a distance of one-half of the line's width.
|
|
224
|
+
* - "round": A join with a rounded end which is drawn beyond the endpoint of the line at a radius of one-half of the line's width and centered on the endpoint of the line.
|
|
225
|
+
* - "miter": A join with a sharp, angled corner which is drawn with the outer sides beyond the endpoint of the path until they meet.
|
|
226
|
+
* Default: "round"
|
|
227
|
+
*/
|
|
228
|
+
lineJoin?: "bevel" | "round" | "miter";
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* How blury the outline is, with `0` being no blur and `10` and beyond being quite blurry.
|
|
232
|
+
* Applies only if `.outline` is `true`.
|
|
233
|
+
* Default: `0`
|
|
234
|
+
*/
|
|
235
|
+
outlineBlur?: number | ZoomNumberValues;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export type PolygonLayerOptions = CommonShapeLayerOptions & {
|
|
239
|
+
/**
|
|
240
|
+
* Color of the polygon. This is can be a constant color string or a definition based on zoom levels.
|
|
241
|
+
* Default: a color randomly pick from a list
|
|
242
|
+
*/
|
|
243
|
+
fillColor?: string | ZoomStringValues;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Opacity of the polygon. This is can be a constant opacity in [0, 1] or a definition based on zoom levels
|
|
247
|
+
* Default: `1`
|
|
248
|
+
*/
|
|
249
|
+
fillOpacity?: ZoomNumberValues;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Position of the outline with regard to the polygon edge (when `.outline` is `true`)
|
|
253
|
+
* Default: `"center"`
|
|
254
|
+
*/
|
|
255
|
+
outlinePosition: "center" | "inside" | "outside";
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Sequence of line and void to create a dash pattern. The unit is the line width so that
|
|
259
|
+
* a dash array value of `[3, 1]` will create a segment worth 3 times the width of the line,
|
|
260
|
+
* followed by a spacing worth 1 time the line width, and then repeat.
|
|
261
|
+
*
|
|
262
|
+
* Alternatively, this property can be a string made of underscore and whitespace characters
|
|
263
|
+
* such as `"___ _ "` and internaly this will be translated into [3, 1, 1, 1]. Note that
|
|
264
|
+
* this way of describing dash arrays with a string only works for integer values.
|
|
265
|
+
*
|
|
266
|
+
* Dash arrays can contain more than 2 element to create more complex patters. For instance
|
|
267
|
+
* a dash array value of [3, 2, 1, 2] will create the following sequence:
|
|
268
|
+
* - a segment worth 3 times the width
|
|
269
|
+
* - a spacing worth 2 times the width
|
|
270
|
+
* - a segment worth 1 times the width
|
|
271
|
+
* - a spacing worth 2 times the width
|
|
272
|
+
* - repeat
|
|
273
|
+
*
|
|
274
|
+
* Default: no dash pattern
|
|
275
|
+
*/
|
|
276
|
+
outlineDashArray?: Array<number> | string;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The display of line endings for both the line and the outline (if `.outline` is `true`)
|
|
280
|
+
* - "butt": A cap with a squared-off end which is drawn to the exact endpoint of the line.
|
|
281
|
+
* - "round": A cap with a rounded end which is drawn beyond the endpoint of the line at a radius of one-half of the line's width and centered on the endpoint of the line.
|
|
282
|
+
* - "square": A cap with a squared-off end which is drawn beyond the endpoint of the line at a distance of one-half of the line's width.
|
|
283
|
+
* Default: "round"
|
|
284
|
+
*/
|
|
285
|
+
outlineCap?: "butt" | "round" | "square";
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The display of lines when joining for both the line and the outline (if `.outline` is `true`)
|
|
289
|
+
* - "bevel": A join with a squared-off end which is drawn beyond the endpoint of the line at a distance of one-half of the line's width.
|
|
290
|
+
* - "round": A join with a rounded end which is drawn beyond the endpoint of the line at a radius of one-half of the line's width and centered on the endpoint of the line.
|
|
291
|
+
* - "miter": A join with a sharp, angled corner which is drawn with the outer sides beyond the endpoint of the path until they meet.
|
|
292
|
+
* Default: "round"
|
|
293
|
+
*/
|
|
294
|
+
outlineJoin?: "bevel" | "round" | "miter";
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The pattern is an image URL to be put as a repeated background pattern of the polygon.
|
|
298
|
+
* Default: `null` (no pattern, `fillColor` will be used)
|
|
299
|
+
*/
|
|
300
|
+
pattern?: string | null;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* How blury the outline is, with `0` being no blur and `10` and beyond being quite blurry.
|
|
304
|
+
* Applies only if `.outline` is `true`.
|
|
305
|
+
* Default: `0`
|
|
306
|
+
*/
|
|
307
|
+
outlineBlur?: number | ZoomNumberValues;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export type PointLayerOptions = CommonShapeLayerOptions & {
|
|
311
|
+
/**
|
|
312
|
+
* Can be a unique point color as a string (CSS color such as "#FF0000" or "red").
|
|
313
|
+
* Alternatively, the color can be a ColorRamp with a range.
|
|
314
|
+
* In case of `.cluster` being `true`, the range of the ColorRamp will be addressed with the number of elements in
|
|
315
|
+
* the cluster. If `.cluster` is `false`, the color will be addressed using the value of the `.property`.
|
|
316
|
+
* If no `.property` is given but `.pointColor` is a ColorRamp, the chosen color is the one at the lower bound of the ColorRamp.
|
|
317
|
+
* Default: a color randomly pick from a list
|
|
318
|
+
*/
|
|
319
|
+
pointColor?: string | ColorRamp;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Radius of the points. Can be a fixed size or a value dependant on the zoom.
|
|
323
|
+
* If `.pointRadius` is not provided, the radius will depend on the size of each cluster (if `.cluster` is `true`)
|
|
324
|
+
* or on the value of each point (if `.property` is provided and `.pointColor` is a ColorRamp).
|
|
325
|
+
* The radius will be between `.minPointRadius` and `.maxPointRadius`
|
|
326
|
+
*/
|
|
327
|
+
pointRadius?: number | ZoomNumberValues;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The minimum point radius posible.
|
|
331
|
+
* Default: `10`
|
|
332
|
+
*/
|
|
333
|
+
minPointRadius?: number;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* The maximum point radius posible.
|
|
337
|
+
* Default: `40`
|
|
338
|
+
*/
|
|
339
|
+
maxPointRadius?: number;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* The point property to observe and apply the radius and color upon.
|
|
343
|
+
* This is ignored if `.cluster` is `true` as the observed value will be fiorced to being the number
|
|
344
|
+
* of elements in each cluster.
|
|
345
|
+
*
|
|
346
|
+
* Default: none
|
|
347
|
+
*/
|
|
348
|
+
property?: string;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Opacity of the point or icon. This is can be a constant opacity in [0, 1] or a definition based on zoom levels.
|
|
352
|
+
* Alternatively, if not provided but the `.pointColor` is a ColorRamp, the opacity will be extracted from tha alpha
|
|
353
|
+
* component if present.
|
|
354
|
+
* Default: `1`
|
|
355
|
+
*/
|
|
356
|
+
pointOpacity?: number | ZoomNumberValues;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* If `true`, the points will keep their circular shape align with the wiewport.
|
|
360
|
+
* If `false`, the points will be like flatten on the map. This difference shows
|
|
361
|
+
* when the map is tilted.
|
|
362
|
+
* Default: `true`
|
|
363
|
+
*/
|
|
364
|
+
alignOnViewport?: boolean;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Whether the points should cluster
|
|
368
|
+
*/
|
|
369
|
+
cluster?: boolean;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Shows a label with the numerical value id `true`.
|
|
373
|
+
* If `.cluster` is `true`, the value will be the numebr of elements in the cluster.
|
|
374
|
+
*
|
|
375
|
+
*
|
|
376
|
+
* Default: `true` if `cluster` or `dataDrivenStyleProperty` are used, `false` otherwise.
|
|
377
|
+
*/
|
|
378
|
+
showLabel?: boolean;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* text color used for the number elements in each cluster.
|
|
382
|
+
* Applicable only when `cluster` is `true`.
|
|
383
|
+
* Default: `#000000` (black)
|
|
384
|
+
*/
|
|
385
|
+
labelColor?: string;
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* text size used for the number elements in each cluster.
|
|
389
|
+
* Applicable only when `cluster` is `true`.
|
|
390
|
+
* Default: `12`
|
|
391
|
+
*/
|
|
392
|
+
labelSize?: number;
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Only if `.cluster` is `false`.
|
|
396
|
+
* If the radius is driven by a property, then it will also scale by zoomming if `.zoomCompensation` is `true`.
|
|
397
|
+
* If `false`, the radius will not adapt according to the zoom level.
|
|
398
|
+
* Default: `true`
|
|
399
|
+
*/
|
|
400
|
+
zoomCompensation?: boolean;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
export type HeatmapLayerOptions = {
|
|
404
|
+
/**
|
|
405
|
+
* ID to give to the layer.
|
|
406
|
+
* If not provided, an auto-generated ID of the for "maptiler-layer-xxxxxx" will be auto-generated,
|
|
407
|
+
* with "xxxxxx" being a random string.
|
|
408
|
+
*/
|
|
409
|
+
layerId?: string;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* ID to give to the geojson source.
|
|
413
|
+
* If not provided, an auto-generated ID of the for "maptiler-source-xxxxxx" will be auto-generated,
|
|
414
|
+
* with "xxxxxx" being a random string.
|
|
415
|
+
*/
|
|
416
|
+
sourceId?: string;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* A geojson Feature collection or a URL to a geojson or the UUID of a MapTiler Cloud dataset.
|
|
420
|
+
*/
|
|
421
|
+
data: FeatureCollection | string;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The ID of an existing layer to insert the new layer before, resulting in the new layer appearing
|
|
425
|
+
* visually beneath the existing layer. If this argument is not specified, the layer will be appended
|
|
426
|
+
* to the end of the layers array and appear visually above all other layers.
|
|
427
|
+
*/
|
|
428
|
+
beforeId?: string;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Zoom level at which it starts to show.
|
|
432
|
+
* Default: `0`
|
|
433
|
+
*/
|
|
434
|
+
minzoom?: number;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Zoom level after which it no longer show.
|
|
438
|
+
* Default: `22`
|
|
439
|
+
*/
|
|
440
|
+
maxzoom?: number;
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* The ColorRamp instance to use for visualization. The color ramp is expected to be defined in the
|
|
444
|
+
* range `[0, 1]` or else will be forced to this range.
|
|
445
|
+
* Default: `ColorRampCollection.TURBO`
|
|
446
|
+
*/
|
|
447
|
+
colorRamp?: ColorRamp;
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Use a property to apply a weight to each data point. Using a property requires also using
|
|
451
|
+
* the options `.propertyValueWeight` or otherwise will be ignored.
|
|
452
|
+
* Default: none, the points will all have a weight of `1`.
|
|
453
|
+
*/
|
|
454
|
+
property?: string;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The weight to give to each data point. If of type `PropertyValueWeights`, then the options `.property`
|
|
458
|
+
* must also be provided. If used a number, all data points will be weighted by the same number (which is of little interest)
|
|
459
|
+
*/
|
|
460
|
+
weight?: PropertyValues | number;
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* The radius (in screenspace) can be:
|
|
464
|
+
* - a fixed number that will be constant across zoom level
|
|
465
|
+
* - of type `ZoomNumberValues` to be ramped accoding to zoom level (`.zoomCompensation` will then be ignored)
|
|
466
|
+
* - of type `PropertyValues` to be driven by the value of a property.
|
|
467
|
+
* If so, the option `.property` must be provided and will still be resized according to zoom level,
|
|
468
|
+
* unless the option `.zoomCompensation` is set to `false`.
|
|
469
|
+
*
|
|
470
|
+
* Default:
|
|
471
|
+
*/
|
|
472
|
+
radius?: number | ZoomNumberValues | PropertyValues;
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* The opacity can be a fixed value or zoom-driven.
|
|
476
|
+
* Default: fades-in 0.25z after minzoom and fade-out 0.25z before maxzoom
|
|
477
|
+
*/
|
|
478
|
+
opacity?: number | ZoomNumberValues;
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* The intensity is zoom-dependent. By default, the intensity is going to be scaled by zoom to preserve
|
|
482
|
+
* a natural aspect or the data distribution.
|
|
483
|
+
*/
|
|
484
|
+
intensity?: number | ZoomNumberValues;
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* If the radius is driven by a property, then it will also scale by zoomming if `.zoomCompensation` is `true`.
|
|
488
|
+
* If `false`, the radius will not adapt according to the zoom level.
|
|
489
|
+
* Default: `true`
|
|
490
|
+
*/
|
|
491
|
+
zoomCompensation?: boolean;
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Add a polyline to the map from various sources and with builtin styling.
|
|
496
|
+
* Compatible sources:
|
|
497
|
+
* - gpx content as string
|
|
498
|
+
* - gpx file from URL
|
|
499
|
+
* - kml content from string
|
|
500
|
+
* - kml from url
|
|
501
|
+
* - geojson from url
|
|
502
|
+
* - geojson content as string
|
|
503
|
+
* - geojson content as JS object
|
|
504
|
+
* - uuid of a MapTiler Cloud dataset
|
|
505
|
+
*
|
|
506
|
+
* The method also gives the possibility to add an outline layer (if `options.outline` is `true`)
|
|
507
|
+
* and if so , the returned property `polylineOutlineLayerId` will be a string. As a result, two layers
|
|
508
|
+
* would be added.
|
|
509
|
+
*
|
|
510
|
+
* The default styling creates a line layer of constant width of 3px, the color will be randomly picked
|
|
511
|
+
* from a curated list of colors and the opacity will be 1.
|
|
512
|
+
* If the outline is enabled, the outline width is of 1px at all zoom levels, the color is white and
|
|
513
|
+
* the opacity is 1.
|
|
514
|
+
*
|
|
515
|
+
* Those style properties can be changed and ramped according to zoom level using an easier syntax.
|
|
516
|
+
*
|
|
517
|
+
*/
|
|
518
|
+
export async function addPolyline(
|
|
519
|
+
/**
|
|
520
|
+
* Map instance to add a polyline layer to
|
|
521
|
+
*/
|
|
522
|
+
map: Map,
|
|
523
|
+
/**
|
|
524
|
+
* Options related to adding a polyline layer
|
|
525
|
+
*/
|
|
526
|
+
options: PolylineLayerOptions,
|
|
527
|
+
/**
|
|
528
|
+
* When the polyline data is loaded from a distant source, these options are propagated to the call of `fetch`
|
|
529
|
+
*/
|
|
530
|
+
fetchOptions: RequestInit = {},
|
|
531
|
+
): Promise<{
|
|
532
|
+
polylineLayerId: string;
|
|
533
|
+
polylineOutlineLayerId: string;
|
|
534
|
+
polylineSourceId: string;
|
|
535
|
+
}> {
|
|
536
|
+
// We need to have the sourceId of the sourceData
|
|
537
|
+
if (!options.sourceId && !options.data) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
"Creating a polyline layer requires an existing .sourceId or a valid .data property",
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// We are going to evaluate the content of .data, if provided
|
|
544
|
+
let data = options.data;
|
|
545
|
+
|
|
546
|
+
if (typeof data === "string") {
|
|
547
|
+
// if options.data exists and is a uuid string, we consider that it points to a MapTiler Dataset
|
|
548
|
+
if (isUUID(data)) {
|
|
549
|
+
data = `https://api.maptiler.com/data/${options.data}/features.json?key=${config.apiKey}`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// options.data could be a url to a .gpx file
|
|
553
|
+
else if (data.split(".").pop()?.toLowerCase().trim() === "gpx") {
|
|
554
|
+
// fetch the file
|
|
555
|
+
const res = await fetch(data, fetchOptions);
|
|
556
|
+
const gpxStr = await res.text();
|
|
557
|
+
// Convert it to geojson. Will throw is invalid GPX content
|
|
558
|
+
data = gpx(gpxStr);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// options.data could be a url to a .kml file
|
|
562
|
+
else if (data.split(".").pop()?.toLowerCase().trim() === "kml") {
|
|
563
|
+
// fetch the file
|
|
564
|
+
const res = await fetch(data, fetchOptions);
|
|
565
|
+
const kmlStr = await res.text();
|
|
566
|
+
// Convert it to geojson. Will throw is invalid GPX content
|
|
567
|
+
data = kml(kmlStr);
|
|
568
|
+
} else {
|
|
569
|
+
// From this point, we consider that the string content provided could
|
|
570
|
+
// be the string content of one of the compatible format (GeoJSON, KML, GPX)
|
|
571
|
+
const tmpData =
|
|
572
|
+
jsonParseNoThrow<FeatureCollection<Geometry, GeoJsonProperties>>(
|
|
573
|
+
data,
|
|
574
|
+
) ?? gpxOrKml(data);
|
|
575
|
+
if (tmpData) data = tmpData;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (!data) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
"Polyline data was provided as string but is incompatible with valid formats.",
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return addGeoJSONPolyline(map, {
|
|
586
|
+
...options,
|
|
587
|
+
data,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Add a polyline from a GeoJSON object
|
|
593
|
+
*/
|
|
594
|
+
function addGeoJSONPolyline(
|
|
595
|
+
map: Map,
|
|
596
|
+
// The data or data source is expected to contain LineStrings or MultiLineStrings
|
|
597
|
+
options: PolylineLayerOptions,
|
|
598
|
+
): {
|
|
599
|
+
/**
|
|
600
|
+
* ID of the main line layer
|
|
601
|
+
*/
|
|
602
|
+
polylineLayerId: string;
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* ID of the outline layer (will be `""` if no outline)
|
|
606
|
+
*/
|
|
607
|
+
polylineOutlineLayerId: string;
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* ID of the data source
|
|
611
|
+
*/
|
|
612
|
+
polylineSourceId: string;
|
|
613
|
+
} {
|
|
614
|
+
if (options.layerId && map.getLayer(options.layerId)) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
`A layer already exists with the layer id: ${options.layerId}`,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const sourceId = options.sourceId ?? generateRandomSourceName();
|
|
621
|
+
const layerId = options.layerId ?? generateRandomLayerName();
|
|
622
|
+
|
|
623
|
+
const returnedInfo = {
|
|
624
|
+
polylineLayerId: layerId,
|
|
625
|
+
polylineOutlineLayerId: "",
|
|
626
|
+
polylineSourceId: sourceId,
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// A new source is added if the map does not have this sourceId and the data is provided
|
|
630
|
+
if (options.data && !map.getSource(sourceId)) {
|
|
631
|
+
// Adding the source
|
|
632
|
+
map.addSource(sourceId, {
|
|
633
|
+
type: "geojson",
|
|
634
|
+
data: options.data,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const lineWidth = options.lineWidth ?? 3;
|
|
639
|
+
const lineColor = options.lineColor ?? getRandomColor();
|
|
640
|
+
const lineOpacity = options.lineOpacity ?? 1;
|
|
641
|
+
const lineBlur = options.lineBlur ?? 0;
|
|
642
|
+
const lineGapWidth = options.lineGapWidth ?? 0;
|
|
643
|
+
let lineDashArray = options.lineDashArray ?? null;
|
|
644
|
+
const outlineWidth = options.outlineWidth ?? 1;
|
|
645
|
+
const outlineColor = options.outlineColor ?? "#FFFFFF";
|
|
646
|
+
const outlineOpacity = options.outlineOpacity ?? 1;
|
|
647
|
+
const outlineBlur = options.outlineBlur ?? 0;
|
|
648
|
+
|
|
649
|
+
if (typeof lineDashArray === "string") {
|
|
650
|
+
lineDashArray = dashArrayMaker(lineDashArray);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// We want to create an outline for this line layer
|
|
654
|
+
if (options.outline === true) {
|
|
655
|
+
const outlineLayerId = `${layerId}_outline`;
|
|
656
|
+
returnedInfo.polylineOutlineLayerId = outlineLayerId;
|
|
657
|
+
|
|
658
|
+
map.addLayer(
|
|
659
|
+
{
|
|
660
|
+
id: outlineLayerId,
|
|
661
|
+
type: "line",
|
|
662
|
+
source: sourceId,
|
|
663
|
+
layout: {
|
|
664
|
+
"line-join": options.lineJoin ?? "round",
|
|
665
|
+
"line-cap": options.lineCap ?? "round",
|
|
666
|
+
},
|
|
667
|
+
minzoom: options.minzoom ?? 0,
|
|
668
|
+
maxzoom: options.maxzoom ?? 23,
|
|
669
|
+
paint: {
|
|
670
|
+
"line-opacity":
|
|
671
|
+
typeof outlineOpacity === "number"
|
|
672
|
+
? outlineOpacity
|
|
673
|
+
: rampedOptionsToLayerPaintSpec(outlineOpacity),
|
|
674
|
+
"line-color":
|
|
675
|
+
typeof outlineColor === "string"
|
|
676
|
+
? outlineColor
|
|
677
|
+
: paintColorOptionsToPaintSpec(outlineColor),
|
|
678
|
+
"line-width": computeRampedOutlineWidth(lineWidth, outlineWidth),
|
|
679
|
+
"line-blur":
|
|
680
|
+
typeof outlineBlur === "number"
|
|
681
|
+
? outlineBlur
|
|
682
|
+
: rampedOptionsToLayerPaintSpec(outlineBlur),
|
|
683
|
+
},
|
|
684
|
+
},
|
|
685
|
+
options.beforeId,
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
map.addLayer(
|
|
690
|
+
{
|
|
691
|
+
id: layerId,
|
|
692
|
+
type: "line",
|
|
693
|
+
source: sourceId,
|
|
694
|
+
layout: {
|
|
695
|
+
"line-join": options.lineJoin ?? "round",
|
|
696
|
+
"line-cap": options.lineCap ?? "round",
|
|
697
|
+
},
|
|
698
|
+
minzoom: options.minzoom ?? 0,
|
|
699
|
+
maxzoom: options.maxzoom ?? 23,
|
|
700
|
+
paint: {
|
|
701
|
+
"line-opacity":
|
|
702
|
+
typeof lineOpacity === "number"
|
|
703
|
+
? lineOpacity
|
|
704
|
+
: rampedOptionsToLayerPaintSpec(lineOpacity),
|
|
705
|
+
"line-color":
|
|
706
|
+
typeof lineColor === "string"
|
|
707
|
+
? lineColor
|
|
708
|
+
: paintColorOptionsToPaintSpec(lineColor),
|
|
709
|
+
"line-width":
|
|
710
|
+
typeof lineWidth === "number"
|
|
711
|
+
? lineWidth
|
|
712
|
+
: rampedOptionsToLayerPaintSpec(lineWidth),
|
|
713
|
+
|
|
714
|
+
"line-blur":
|
|
715
|
+
typeof lineBlur === "number"
|
|
716
|
+
? lineBlur
|
|
717
|
+
: rampedOptionsToLayerPaintSpec(lineBlur),
|
|
718
|
+
|
|
719
|
+
"line-gap-width":
|
|
720
|
+
typeof lineGapWidth === "number"
|
|
721
|
+
? lineGapWidth
|
|
722
|
+
: rampedOptionsToLayerPaintSpec(lineGapWidth),
|
|
723
|
+
|
|
724
|
+
// For some reasons passing "line-dasharray" with the value "undefined"
|
|
725
|
+
// results in no showing the line while it should have the same behavior
|
|
726
|
+
// of not adding the property "line-dasharray" as all.
|
|
727
|
+
// As a workaround, we are inlining the addition of the prop with a conditional
|
|
728
|
+
// which is less readable.
|
|
729
|
+
...(lineDashArray && { "line-dasharray": lineDashArray }),
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
options.beforeId,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
return returnedInfo;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Add a polygon with styling options.
|
|
740
|
+
*/
|
|
741
|
+
export function addPolygon(
|
|
742
|
+
map: Map,
|
|
743
|
+
// this Feature collection is expected to contain on LineStrings and MultiLinestrings
|
|
744
|
+
options: PolygonLayerOptions,
|
|
745
|
+
): {
|
|
746
|
+
/**
|
|
747
|
+
* ID of the fill layer
|
|
748
|
+
*/
|
|
749
|
+
polygonLayerId: string;
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* ID of the outline layer (will be `""` if no outline)
|
|
753
|
+
*/
|
|
754
|
+
polygonOutlineLayerId: string;
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* ID of the source that contains the data
|
|
758
|
+
*/
|
|
759
|
+
polygonSourceId: string;
|
|
760
|
+
} {
|
|
761
|
+
if (options.layerId && map.getLayer(options.layerId)) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`A layer already exists with the layer id: ${options.layerId}`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const sourceId = options.sourceId ?? generateRandomSourceName();
|
|
768
|
+
const layerId = options.layerId ?? generateRandomLayerName();
|
|
769
|
+
|
|
770
|
+
const returnedInfo = {
|
|
771
|
+
polygonLayerId: layerId,
|
|
772
|
+
polygonOutlineLayerId: options.outline ? `${layerId}_outline` : "",
|
|
773
|
+
polygonSourceId: sourceId,
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// A new source is added if the map does not have this sourceId and the data is provided
|
|
777
|
+
if (options.data && !map.getSource(sourceId)) {
|
|
778
|
+
let data: string | FeatureCollection = options.data;
|
|
779
|
+
|
|
780
|
+
// If is a UUID, we extend it to be the URL to a MapTiler Cloud hosted dataset
|
|
781
|
+
if (typeof data === "string" && isUUID(data)) {
|
|
782
|
+
data = `https://api.maptiler.com/data/${data}/features.json?key=${config.apiKey}`;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Adding the source
|
|
786
|
+
map.addSource(sourceId, {
|
|
787
|
+
type: "geojson",
|
|
788
|
+
data: data,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
let outlineDashArray = options.outlineDashArray ?? null;
|
|
793
|
+
const outlineWidth = options.outlineWidth ?? 1;
|
|
794
|
+
const outlineColor = options.outlineColor ?? "#FFFFFF";
|
|
795
|
+
const outlineOpacity = options.outlineOpacity ?? 1;
|
|
796
|
+
const outlineBlur = options.outlineBlur ?? 0;
|
|
797
|
+
const fillColor = options.fillColor ?? getRandomColor();
|
|
798
|
+
const fillOpacity = options.fillOpacity ?? 1;
|
|
799
|
+
const outlinePosition = options.outlinePosition ?? "center";
|
|
800
|
+
const pattern = options.pattern ?? null;
|
|
801
|
+
|
|
802
|
+
if (typeof outlineDashArray === "string") {
|
|
803
|
+
outlineDashArray = dashArrayMaker(outlineDashArray);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const addLayers = (patternImageId: string | null = null) => {
|
|
807
|
+
map.addLayer(
|
|
808
|
+
{
|
|
809
|
+
id: layerId,
|
|
810
|
+
type: "fill",
|
|
811
|
+
source: sourceId,
|
|
812
|
+
minzoom: options.minzoom ?? 0,
|
|
813
|
+
maxzoom: options.maxzoom ?? 23,
|
|
814
|
+
paint: {
|
|
815
|
+
"fill-color":
|
|
816
|
+
typeof fillColor === "string"
|
|
817
|
+
? fillColor
|
|
818
|
+
: paintColorOptionsToPaintSpec(fillColor),
|
|
819
|
+
|
|
820
|
+
"fill-opacity":
|
|
821
|
+
typeof fillOpacity === "number"
|
|
822
|
+
? fillOpacity
|
|
823
|
+
: rampedOptionsToLayerPaintSpec(fillOpacity),
|
|
824
|
+
|
|
825
|
+
// Adding a pattern if provided
|
|
826
|
+
...(patternImageId && { "fill-pattern": patternImageId }),
|
|
827
|
+
},
|
|
828
|
+
},
|
|
829
|
+
options.beforeId,
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
// We want to create an outline for this line layer
|
|
833
|
+
if (options.outline === true) {
|
|
834
|
+
let computedOutlineOffset:
|
|
835
|
+
| DataDrivenPropertyValueSpecification<number>
|
|
836
|
+
| number;
|
|
837
|
+
|
|
838
|
+
if (outlinePosition === "inside") {
|
|
839
|
+
if (typeof outlineWidth === "number") {
|
|
840
|
+
computedOutlineOffset = 0.5 * outlineWidth;
|
|
841
|
+
} else {
|
|
842
|
+
computedOutlineOffset = rampedOptionsToLayerPaintSpec(
|
|
843
|
+
outlineWidth.map(({ zoom, value }) => ({
|
|
844
|
+
zoom,
|
|
845
|
+
value: 0.5 * value,
|
|
846
|
+
})),
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
} else if (outlinePosition === "outside") {
|
|
850
|
+
if (typeof outlineWidth === "number") {
|
|
851
|
+
computedOutlineOffset = -0.5 * outlineWidth;
|
|
852
|
+
} else {
|
|
853
|
+
computedOutlineOffset = rampedOptionsToLayerPaintSpec(
|
|
854
|
+
outlineWidth.map((el) => ({
|
|
855
|
+
zoom: el.zoom,
|
|
856
|
+
value: -0.5 * el.value,
|
|
857
|
+
})),
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
} else {
|
|
861
|
+
computedOutlineOffset = 0;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
map.addLayer(
|
|
865
|
+
{
|
|
866
|
+
id: returnedInfo.polygonOutlineLayerId,
|
|
867
|
+
type: "line",
|
|
868
|
+
source: sourceId,
|
|
869
|
+
layout: {
|
|
870
|
+
"line-join": options.outlineJoin ?? "round",
|
|
871
|
+
"line-cap": options.outlineCap ?? "butt",
|
|
872
|
+
},
|
|
873
|
+
minzoom: options.minzoom ?? 0,
|
|
874
|
+
maxzoom: options.maxzoom ?? 23,
|
|
875
|
+
paint: {
|
|
876
|
+
"line-opacity":
|
|
877
|
+
typeof outlineOpacity === "number"
|
|
878
|
+
? outlineOpacity
|
|
879
|
+
: rampedOptionsToLayerPaintSpec(outlineOpacity),
|
|
880
|
+
"line-color":
|
|
881
|
+
typeof outlineColor === "string"
|
|
882
|
+
? outlineColor
|
|
883
|
+
: paintColorOptionsToPaintSpec(outlineColor),
|
|
884
|
+
"line-width":
|
|
885
|
+
typeof outlineWidth === "number"
|
|
886
|
+
? outlineWidth
|
|
887
|
+
: rampedOptionsToLayerPaintSpec(outlineWidth),
|
|
888
|
+
"line-blur":
|
|
889
|
+
typeof outlineBlur === "number"
|
|
890
|
+
? outlineBlur
|
|
891
|
+
: rampedOptionsToLayerPaintSpec(outlineBlur),
|
|
892
|
+
|
|
893
|
+
"line-offset": computedOutlineOffset,
|
|
894
|
+
|
|
895
|
+
// For some reasons passing "line-dasharray" with the value "undefined"
|
|
896
|
+
// results in no showing the line while it should have the same behavior
|
|
897
|
+
// of not adding the property "line-dasharray" as all.
|
|
898
|
+
// As a workaround, we are inlining the addition of the prop with a conditional
|
|
899
|
+
// which is less readable.
|
|
900
|
+
...(outlineDashArray && {
|
|
901
|
+
"line-dasharray": outlineDashArray as PropertyValueSpecification<
|
|
902
|
+
number[]
|
|
903
|
+
>,
|
|
904
|
+
}),
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
options.beforeId,
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
if (pattern) {
|
|
913
|
+
if (map.hasImage(pattern)) {
|
|
914
|
+
addLayers(pattern);
|
|
915
|
+
} else {
|
|
916
|
+
map.loadImage(
|
|
917
|
+
pattern,
|
|
918
|
+
|
|
919
|
+
// (error?: Error | null, image?: HTMLImageElement | ImageBitmap | null, expiry?: ExpiryData | null)
|
|
920
|
+
(
|
|
921
|
+
error: Error | null | undefined,
|
|
922
|
+
image: HTMLImageElement | ImageBitmap | null | undefined,
|
|
923
|
+
) => {
|
|
924
|
+
// Throw an error if something goes wrong.
|
|
925
|
+
if (error) {
|
|
926
|
+
console.error("Could not load the pattern image.", error.message);
|
|
927
|
+
return addLayers();
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (!image) {
|
|
931
|
+
console.error(
|
|
932
|
+
`An image cannot be created from the pattern URL ${pattern}.`,
|
|
933
|
+
);
|
|
934
|
+
return addLayers();
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Add the image to the map style, using the image URL as an ID
|
|
938
|
+
map.addImage(pattern, image);
|
|
939
|
+
|
|
940
|
+
addLayers(pattern);
|
|
941
|
+
},
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
addLayers();
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return returnedInfo;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Add a point layer from a GeoJSON source (or an existing sourceId) with many styling options
|
|
953
|
+
*/
|
|
954
|
+
export function addPoint(
|
|
955
|
+
/**
|
|
956
|
+
* The Map instance to add a point layer to
|
|
957
|
+
*/
|
|
958
|
+
map: Map,
|
|
959
|
+
// The data or data source is expected to contain LineStrings or MultiLineStrings
|
|
960
|
+
options: PointLayerOptions,
|
|
961
|
+
): {
|
|
962
|
+
/**
|
|
963
|
+
* ID of the unclustered point layer
|
|
964
|
+
*/
|
|
965
|
+
pointLayerId: string;
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* ID of the clustered point layer (empty if `cluster` options id `false`)
|
|
969
|
+
*/
|
|
970
|
+
clusterLayerId: string;
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* ID of the layer that shows the count of elements in each cluster (empty if `cluster` options id `false`)
|
|
974
|
+
*/
|
|
975
|
+
labelLayerId: string;
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* ID of the data source
|
|
979
|
+
*/
|
|
980
|
+
pointSourceId: string;
|
|
981
|
+
} {
|
|
982
|
+
if (options.layerId && map.getLayer(options.layerId)) {
|
|
983
|
+
throw new Error(
|
|
984
|
+
`A layer already exists with the layer id: ${options.layerId}`,
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const minPointRadius = options.minPointRadius ?? 10;
|
|
989
|
+
const maxPointRadius = options.maxPointRadius ?? 50;
|
|
990
|
+
const cluster = options.cluster ?? false;
|
|
991
|
+
const nbDefaultDataDrivenStyleSteps = 20;
|
|
992
|
+
const colorramp = Array.isArray(options.pointColor)
|
|
993
|
+
? options.pointColor
|
|
994
|
+
: ColorRampCollection.TURBO.scale(
|
|
995
|
+
10,
|
|
996
|
+
options.cluster ? 10000 : 1000,
|
|
997
|
+
).resample("ease-out-square");
|
|
998
|
+
const colorRampBounds = colorramp.getBounds();
|
|
999
|
+
const sourceId = options.sourceId ?? generateRandomSourceName();
|
|
1000
|
+
const layerId = options.layerId ?? generateRandomLayerName();
|
|
1001
|
+
const showLabel = options.showLabel ?? cluster;
|
|
1002
|
+
const alignOnViewport = options.alignOnViewport ?? true;
|
|
1003
|
+
const outline = options.outline ?? false;
|
|
1004
|
+
const outlineOpacity = options.outlineOpacity ?? 1;
|
|
1005
|
+
const outlineWidth = options.outlineWidth ?? 1;
|
|
1006
|
+
const outlineColor = options.outlineColor ?? "#FFFFFF";
|
|
1007
|
+
let pointOpacity;
|
|
1008
|
+
const zoomCompensation = options.zoomCompensation ?? true;
|
|
1009
|
+
const minzoom = options.minzoom ?? 0;
|
|
1010
|
+
const maxzoom = options.maxzoom ?? 23;
|
|
1011
|
+
|
|
1012
|
+
if (typeof options.pointOpacity === "number") {
|
|
1013
|
+
pointOpacity = options.pointOpacity;
|
|
1014
|
+
} else if (Array.isArray(options.pointOpacity)) {
|
|
1015
|
+
pointOpacity = rampedOptionsToLayerPaintSpec(options.pointOpacity);
|
|
1016
|
+
} else if (options.cluster) {
|
|
1017
|
+
pointOpacity = opacityDrivenByProperty(colorramp, "point_count");
|
|
1018
|
+
} else if (options.property) {
|
|
1019
|
+
pointOpacity = opacityDrivenByProperty(colorramp, options.property);
|
|
1020
|
+
} else {
|
|
1021
|
+
pointOpacity = rampedOptionsToLayerPaintSpec([
|
|
1022
|
+
{ zoom: minzoom, value: 0 },
|
|
1023
|
+
{ zoom: minzoom + 0.25, value: 1 },
|
|
1024
|
+
{ zoom: maxzoom - 0.25, value: 1 },
|
|
1025
|
+
{ zoom: maxzoom, value: 0 },
|
|
1026
|
+
]);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const returnedInfo = {
|
|
1030
|
+
pointLayerId: layerId,
|
|
1031
|
+
clusterLayerId: "",
|
|
1032
|
+
labelLayerId: "",
|
|
1033
|
+
pointSourceId: sourceId,
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
// A new source is added if the map does not have this sourceId and the data is provided
|
|
1037
|
+
if (options.data && !map.getSource(sourceId)) {
|
|
1038
|
+
let data: string | FeatureCollection = options.data;
|
|
1039
|
+
|
|
1040
|
+
// If is a UUID, we extend it to be the URL to a MapTiler Cloud hosted dataset
|
|
1041
|
+
if (typeof data === "string" && isUUID(data)) {
|
|
1042
|
+
data = `https://api.maptiler.com/data/${data}/features.json?key=${config.apiKey}`;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Adding the source
|
|
1046
|
+
map.addSource(sourceId, {
|
|
1047
|
+
type: "geojson",
|
|
1048
|
+
data: data,
|
|
1049
|
+
cluster,
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (cluster) {
|
|
1054
|
+
// If using clusters, the size and color of the circles (clusters) are driven by the
|
|
1055
|
+
// numbner of elements they contain and cannot be driven by the zoom level or a property
|
|
1056
|
+
|
|
1057
|
+
returnedInfo.clusterLayerId = `${layerId}_cluster`;
|
|
1058
|
+
|
|
1059
|
+
const clusterStyle: DataDrivenStyle = Array.from(
|
|
1060
|
+
{ length: nbDefaultDataDrivenStyleSteps },
|
|
1061
|
+
(_, i) => {
|
|
1062
|
+
const value =
|
|
1063
|
+
colorRampBounds.min +
|
|
1064
|
+
(i * (colorRampBounds.max - colorRampBounds.min)) /
|
|
1065
|
+
(nbDefaultDataDrivenStyleSteps - 1);
|
|
1066
|
+
return {
|
|
1067
|
+
value,
|
|
1068
|
+
pointRadius:
|
|
1069
|
+
minPointRadius +
|
|
1070
|
+
(maxPointRadius - minPointRadius) *
|
|
1071
|
+
Math.pow(i / (nbDefaultDataDrivenStyleSteps - 1), 0.5),
|
|
1072
|
+
color: colorramp.getColorHex(value),
|
|
1073
|
+
};
|
|
1074
|
+
},
|
|
1075
|
+
);
|
|
1076
|
+
|
|
1077
|
+
map.addLayer(
|
|
1078
|
+
{
|
|
1079
|
+
id: returnedInfo.clusterLayerId,
|
|
1080
|
+
type: "circle",
|
|
1081
|
+
source: sourceId,
|
|
1082
|
+
filter: ["has", "point_count"],
|
|
1083
|
+
paint: {
|
|
1084
|
+
// 'circle-color': options.pointColor ?? colorDrivenByProperty(clusterStyle, "point_count"),
|
|
1085
|
+
"circle-color":
|
|
1086
|
+
typeof options.pointColor === "string"
|
|
1087
|
+
? options.pointColor
|
|
1088
|
+
: colorDrivenByProperty(clusterStyle, "point_count"),
|
|
1089
|
+
|
|
1090
|
+
"circle-radius":
|
|
1091
|
+
typeof options.pointRadius === "number"
|
|
1092
|
+
? options.pointRadius
|
|
1093
|
+
: Array.isArray(options.pointRadius)
|
|
1094
|
+
? rampedOptionsToLayerPaintSpec(options.pointRadius)
|
|
1095
|
+
: radiusDrivenByProperty(clusterStyle, "point_count", false),
|
|
1096
|
+
|
|
1097
|
+
"circle-pitch-alignment": alignOnViewport ? "viewport" : "map",
|
|
1098
|
+
"circle-pitch-scale": "map", // scale with camera distance regardless of viewport/biewport alignement
|
|
1099
|
+
"circle-opacity": pointOpacity,
|
|
1100
|
+
...(outline && {
|
|
1101
|
+
"circle-stroke-opacity":
|
|
1102
|
+
typeof outlineOpacity === "number"
|
|
1103
|
+
? outlineOpacity
|
|
1104
|
+
: rampedOptionsToLayerPaintSpec(outlineOpacity),
|
|
1105
|
+
|
|
1106
|
+
"circle-stroke-width":
|
|
1107
|
+
typeof outlineWidth === "number"
|
|
1108
|
+
? outlineWidth
|
|
1109
|
+
: rampedOptionsToLayerPaintSpec(outlineWidth),
|
|
1110
|
+
|
|
1111
|
+
"circle-stroke-color":
|
|
1112
|
+
typeof outlineColor === "string"
|
|
1113
|
+
? outlineColor
|
|
1114
|
+
: paintColorOptionsToPaintSpec(outlineColor),
|
|
1115
|
+
}),
|
|
1116
|
+
},
|
|
1117
|
+
minzoom,
|
|
1118
|
+
maxzoom,
|
|
1119
|
+
},
|
|
1120
|
+
options.beforeId,
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
// Adding the layer of unclustered point (visible only when ungrouped)
|
|
1124
|
+
map.addLayer(
|
|
1125
|
+
{
|
|
1126
|
+
id: returnedInfo.pointLayerId,
|
|
1127
|
+
type: "circle",
|
|
1128
|
+
source: sourceId,
|
|
1129
|
+
filter: ["!", ["has", "point_count"]],
|
|
1130
|
+
paint: {
|
|
1131
|
+
"circle-pitch-alignment": alignOnViewport ? "viewport" : "map",
|
|
1132
|
+
"circle-pitch-scale": "map", // scale with camera distance regardless of viewport/biewport alignement
|
|
1133
|
+
// 'circle-color': options.pointColor ?? clusterStyle[0].color,
|
|
1134
|
+
"circle-color":
|
|
1135
|
+
typeof options.pointColor === "string"
|
|
1136
|
+
? options.pointColor
|
|
1137
|
+
: colorramp.getColorHex(colorramp.getBounds().min),
|
|
1138
|
+
"circle-radius":
|
|
1139
|
+
typeof options.pointRadius === "number"
|
|
1140
|
+
? options.pointRadius
|
|
1141
|
+
: Array.isArray(options.pointRadius)
|
|
1142
|
+
? rampedOptionsToLayerPaintSpec(options.pointRadius)
|
|
1143
|
+
: clusterStyle[0].pointRadius * 0.75,
|
|
1144
|
+
"circle-opacity": pointOpacity,
|
|
1145
|
+
...(outline && {
|
|
1146
|
+
"circle-stroke-opacity":
|
|
1147
|
+
typeof outlineOpacity === "number"
|
|
1148
|
+
? outlineOpacity
|
|
1149
|
+
: rampedOptionsToLayerPaintSpec(outlineOpacity),
|
|
1150
|
+
|
|
1151
|
+
"circle-stroke-width":
|
|
1152
|
+
typeof outlineWidth === "number"
|
|
1153
|
+
? outlineWidth
|
|
1154
|
+
: rampedOptionsToLayerPaintSpec(outlineWidth),
|
|
1155
|
+
|
|
1156
|
+
"circle-stroke-color":
|
|
1157
|
+
typeof outlineColor === "string"
|
|
1158
|
+
? outlineColor
|
|
1159
|
+
: paintColorOptionsToPaintSpec(outlineColor),
|
|
1160
|
+
}),
|
|
1161
|
+
},
|
|
1162
|
+
minzoom,
|
|
1163
|
+
maxzoom,
|
|
1164
|
+
},
|
|
1165
|
+
options.beforeId,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// Not displaying clusters
|
|
1170
|
+
else {
|
|
1171
|
+
let pointColor: DataDrivenPropertyValueSpecification<string> =
|
|
1172
|
+
typeof options.pointColor === "string"
|
|
1173
|
+
? options.pointColor
|
|
1174
|
+
: Array.isArray(options.pointColor)
|
|
1175
|
+
? options.pointColor.getColorHex(options.pointColor.getBounds().min) // if color ramp is given, we choose the first color of it, even if the property may not be provided
|
|
1176
|
+
: getRandomColor();
|
|
1177
|
+
|
|
1178
|
+
let pointRadius: DataDrivenPropertyValueSpecification<number> =
|
|
1179
|
+
typeof options.pointRadius === "number"
|
|
1180
|
+
? zoomCompensation
|
|
1181
|
+
? rampedOptionsToLayerPaintSpec([
|
|
1182
|
+
{ zoom: 0, value: options.pointRadius * 0.025 },
|
|
1183
|
+
{ zoom: 2, value: options.pointRadius * 0.05 },
|
|
1184
|
+
{ zoom: 4, value: options.pointRadius * 0.1 },
|
|
1185
|
+
{ zoom: 8, value: options.pointRadius * 0.25 },
|
|
1186
|
+
{ zoom: 16, value: options.pointRadius * 1 },
|
|
1187
|
+
])
|
|
1188
|
+
: options.pointRadius
|
|
1189
|
+
: Array.isArray(options.pointRadius)
|
|
1190
|
+
? rampedOptionsToLayerPaintSpec(options.pointRadius)
|
|
1191
|
+
: zoomCompensation
|
|
1192
|
+
? rampedOptionsToLayerPaintSpec([
|
|
1193
|
+
{ zoom: 0, value: minPointRadius * 0.05 },
|
|
1194
|
+
{ zoom: 2, value: minPointRadius * 0.1 },
|
|
1195
|
+
{ zoom: 4, value: minPointRadius * 0.2 },
|
|
1196
|
+
{ zoom: 8, value: minPointRadius * 0.5 },
|
|
1197
|
+
{ zoom: 16, value: minPointRadius * 1 },
|
|
1198
|
+
])
|
|
1199
|
+
: minPointRadius;
|
|
1200
|
+
|
|
1201
|
+
// If the styling depends on a property, then we build a custom style
|
|
1202
|
+
if (options.property && Array.isArray(options.pointColor)) {
|
|
1203
|
+
const dataDrivenStyle: DataDrivenStyle = Array.from(
|
|
1204
|
+
{ length: nbDefaultDataDrivenStyleSteps },
|
|
1205
|
+
(_, i) => {
|
|
1206
|
+
const value =
|
|
1207
|
+
colorRampBounds.min +
|
|
1208
|
+
(i * (colorRampBounds.max - colorRampBounds.min)) /
|
|
1209
|
+
(nbDefaultDataDrivenStyleSteps - 1);
|
|
1210
|
+
return {
|
|
1211
|
+
value,
|
|
1212
|
+
pointRadius:
|
|
1213
|
+
typeof options.pointRadius === "number"
|
|
1214
|
+
? options.pointRadius
|
|
1215
|
+
: minPointRadius +
|
|
1216
|
+
(maxPointRadius - minPointRadius) *
|
|
1217
|
+
Math.pow(i / (nbDefaultDataDrivenStyleSteps - 1), 0.5),
|
|
1218
|
+
color:
|
|
1219
|
+
typeof options.pointColor === "string"
|
|
1220
|
+
? options.pointColor
|
|
1221
|
+
: colorramp.getColorHex(value),
|
|
1222
|
+
};
|
|
1223
|
+
},
|
|
1224
|
+
);
|
|
1225
|
+
pointColor = colorDrivenByProperty(dataDrivenStyle, options.property);
|
|
1226
|
+
pointRadius = radiusDrivenByProperty(
|
|
1227
|
+
dataDrivenStyle,
|
|
1228
|
+
options.property,
|
|
1229
|
+
zoomCompensation,
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Adding the layer of unclustered point
|
|
1234
|
+
map.addLayer(
|
|
1235
|
+
{
|
|
1236
|
+
id: returnedInfo.pointLayerId,
|
|
1237
|
+
type: "circle",
|
|
1238
|
+
source: sourceId,
|
|
1239
|
+
layout: {
|
|
1240
|
+
// Contrary to labels, we want to see the small one in front. Weirdly "circle-sort-key" works in the opposite direction as "symbol-sort-key".
|
|
1241
|
+
"circle-sort-key": options.property
|
|
1242
|
+
? ["/", 1, ["get", options.property]]
|
|
1243
|
+
: 0,
|
|
1244
|
+
},
|
|
1245
|
+
paint: {
|
|
1246
|
+
"circle-pitch-alignment": alignOnViewport ? "viewport" : "map",
|
|
1247
|
+
"circle-pitch-scale": "map", // scale with camera distance regardless of viewport/biewport alignement
|
|
1248
|
+
"circle-color": pointColor,
|
|
1249
|
+
"circle-opacity": pointOpacity,
|
|
1250
|
+
"circle-radius": pointRadius,
|
|
1251
|
+
|
|
1252
|
+
...(outline && {
|
|
1253
|
+
"circle-stroke-opacity":
|
|
1254
|
+
typeof outlineOpacity === "number"
|
|
1255
|
+
? outlineOpacity
|
|
1256
|
+
: rampedOptionsToLayerPaintSpec(outlineOpacity),
|
|
1257
|
+
|
|
1258
|
+
"circle-stroke-width":
|
|
1259
|
+
typeof outlineWidth === "number"
|
|
1260
|
+
? outlineWidth
|
|
1261
|
+
: rampedOptionsToLayerPaintSpec(outlineWidth),
|
|
1262
|
+
|
|
1263
|
+
"circle-stroke-color":
|
|
1264
|
+
typeof outlineColor === "string"
|
|
1265
|
+
? outlineColor
|
|
1266
|
+
: paintColorOptionsToPaintSpec(outlineColor),
|
|
1267
|
+
}),
|
|
1268
|
+
},
|
|
1269
|
+
minzoom,
|
|
1270
|
+
maxzoom,
|
|
1271
|
+
},
|
|
1272
|
+
options.beforeId,
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (showLabel !== false && (options.cluster || options.property)) {
|
|
1277
|
+
returnedInfo.labelLayerId = `${layerId}_label`;
|
|
1278
|
+
const labelColor = options.labelColor ?? "#fff";
|
|
1279
|
+
const labelSize = options.labelSize ?? 12;
|
|
1280
|
+
|
|
1281
|
+
// With clusters, a layer with clouster count is also added
|
|
1282
|
+
map.addLayer(
|
|
1283
|
+
{
|
|
1284
|
+
id: returnedInfo.labelLayerId,
|
|
1285
|
+
type: "symbol",
|
|
1286
|
+
source: sourceId,
|
|
1287
|
+
filter: [
|
|
1288
|
+
"has",
|
|
1289
|
+
options.cluster ? "point_count" : (options.property as string),
|
|
1290
|
+
],
|
|
1291
|
+
layout: {
|
|
1292
|
+
"text-field": options.cluster
|
|
1293
|
+
? "{point_count_abbreviated}"
|
|
1294
|
+
: `{${options.property as string}}`,
|
|
1295
|
+
"text-font": ["Noto Sans Regular"],
|
|
1296
|
+
"text-size": labelSize,
|
|
1297
|
+
"text-pitch-alignment": alignOnViewport ? "viewport" : "map",
|
|
1298
|
+
"symbol-sort-key": [
|
|
1299
|
+
"/",
|
|
1300
|
+
1,
|
|
1301
|
+
[
|
|
1302
|
+
"get",
|
|
1303
|
+
options.cluster ? "point_count" : (options.property as string),
|
|
1304
|
+
],
|
|
1305
|
+
], // so that the largest value goes on top
|
|
1306
|
+
},
|
|
1307
|
+
paint: {
|
|
1308
|
+
"text-color": labelColor,
|
|
1309
|
+
"text-opacity": pointOpacity,
|
|
1310
|
+
},
|
|
1311
|
+
minzoom,
|
|
1312
|
+
maxzoom,
|
|
1313
|
+
},
|
|
1314
|
+
options.beforeId,
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
return returnedInfo;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Add a polyline witgh optional outline from a GeoJSON object
|
|
1322
|
+
*/
|
|
1323
|
+
export function addHeatmap(
|
|
1324
|
+
/**
|
|
1325
|
+
* Map instance to add a heatmap layer to
|
|
1326
|
+
*/
|
|
1327
|
+
map: Map,
|
|
1328
|
+
// The data or data source is expected to contain LineStrings or MultiLineStrings
|
|
1329
|
+
options: HeatmapLayerOptions,
|
|
1330
|
+
): {
|
|
1331
|
+
/**
|
|
1332
|
+
* ID of the heatmap layer
|
|
1333
|
+
*/
|
|
1334
|
+
heatmapLayerId: string;
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* ID of the data source
|
|
1338
|
+
*/
|
|
1339
|
+
heatmapSourceId: string;
|
|
1340
|
+
} {
|
|
1341
|
+
if (options.layerId && map.getLayer(options.layerId)) {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
`A layer already exists with the layer id: ${options.layerId}`,
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const sourceId = options.sourceId ?? generateRandomSourceName();
|
|
1348
|
+
const layerId = options.layerId ?? generateRandomLayerName();
|
|
1349
|
+
const minzoom = options.minzoom ?? 0;
|
|
1350
|
+
const maxzoom = options.maxzoom ?? 23;
|
|
1351
|
+
const zoomCompensation = options.zoomCompensation ?? true;
|
|
1352
|
+
|
|
1353
|
+
const opacity = options.opacity ?? [
|
|
1354
|
+
{ zoom: minzoom, value: 0 },
|
|
1355
|
+
{ zoom: minzoom + 0.25, value: 1 },
|
|
1356
|
+
{ zoom: maxzoom - 0.25, value: 1 },
|
|
1357
|
+
{ zoom: maxzoom, value: 0 },
|
|
1358
|
+
];
|
|
1359
|
+
|
|
1360
|
+
// const colorRamp = "colorRamp" in options
|
|
1361
|
+
let colorRamp = Array.isArray(options.colorRamp)
|
|
1362
|
+
? options.colorRamp
|
|
1363
|
+
: ColorRampCollection.TURBO.transparentStart();
|
|
1364
|
+
|
|
1365
|
+
// making sure the color ramp has [0, 1] bounds
|
|
1366
|
+
const crBounds = colorRamp.getBounds();
|
|
1367
|
+
if (crBounds.min !== 0 || crBounds.max !== 1) {
|
|
1368
|
+
colorRamp = colorRamp.scale(0, 1);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// making sure the color ramp has is transparent in 0
|
|
1372
|
+
if (!colorRamp.hasTransparentStart()) {
|
|
1373
|
+
colorRamp = colorRamp.transparentStart();
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const intensity = options.intensity ?? [
|
|
1377
|
+
{ zoom: 0, value: 0.01 },
|
|
1378
|
+
{ zoom: 4, value: 0.2 },
|
|
1379
|
+
{ zoom: 16, value: 1 },
|
|
1380
|
+
];
|
|
1381
|
+
|
|
1382
|
+
const property = options.property ?? null;
|
|
1383
|
+
const propertyValueWeight = options.weight ?? 1;
|
|
1384
|
+
|
|
1385
|
+
let heatmapWeight: DataDrivenPropertyValueSpecification<number> = 1; // = typeof propertyValueWeights === "number" ? propertyValueWeights : 1;
|
|
1386
|
+
|
|
1387
|
+
if (property) {
|
|
1388
|
+
if (typeof propertyValueWeight === "number") {
|
|
1389
|
+
heatmapWeight = propertyValueWeight;
|
|
1390
|
+
|
|
1391
|
+
// In case this numerical weight was provided by the user and not be the default value:
|
|
1392
|
+
if (typeof options.weight === "number") {
|
|
1393
|
+
console.warn(
|
|
1394
|
+
"The option `.property` is ignored when `.propertyValueWeights` is not of type `PropertyValueWeights`",
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
} else if (Array.isArray(propertyValueWeight)) {
|
|
1398
|
+
heatmapWeight = rampedPropertyValueWeight(propertyValueWeight, property);
|
|
1399
|
+
} else {
|
|
1400
|
+
console.warn(
|
|
1401
|
+
"The option `.property` is ignored when `.propertyValueWeights` is not of type `PropertyValueWeights`",
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
} else {
|
|
1405
|
+
if (typeof propertyValueWeight === "number") {
|
|
1406
|
+
heatmapWeight = propertyValueWeight;
|
|
1407
|
+
} else if (Array.isArray(propertyValueWeight)) {
|
|
1408
|
+
console.warn(
|
|
1409
|
+
"The options `.propertyValueWeights` can only be used when `.property` is provided.",
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
const defaultRadiusZoomRamping = [
|
|
1415
|
+
{ zoom: 0, value: 50 * 0.025 },
|
|
1416
|
+
{ zoom: 2, value: 50 * 0.05 },
|
|
1417
|
+
{ zoom: 4, value: 50 * 0.1 },
|
|
1418
|
+
{ zoom: 8, value: 50 * 0.25 },
|
|
1419
|
+
{ zoom: 16, value: 50 },
|
|
1420
|
+
];
|
|
1421
|
+
|
|
1422
|
+
const radius =
|
|
1423
|
+
options.radius ?? (zoomCompensation ? defaultRadiusZoomRamping : 10);
|
|
1424
|
+
|
|
1425
|
+
let radiusHeatmap: DataDrivenPropertyValueSpecification<number> = 1;
|
|
1426
|
+
|
|
1427
|
+
if (typeof radius === "number") {
|
|
1428
|
+
radiusHeatmap = radius;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// Radius is provided as a zoom-ramping array
|
|
1432
|
+
else if (Array.isArray(radius) && "zoom" in radius[0]) {
|
|
1433
|
+
radiusHeatmap = rampedOptionsToLayerPaintSpec(radius as ZoomNumberValues);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// Radius is provided as data driven
|
|
1437
|
+
else if (property && Array.isArray(radius) && "propertyValue" in radius[0]) {
|
|
1438
|
+
radiusHeatmap = radiusDrivenByPropertyHeatmap(
|
|
1439
|
+
radius as unknown as PropertyValues,
|
|
1440
|
+
property,
|
|
1441
|
+
zoomCompensation,
|
|
1442
|
+
);
|
|
1443
|
+
} else if (
|
|
1444
|
+
!property &&
|
|
1445
|
+
Array.isArray(radius) &&
|
|
1446
|
+
"propertyValue" in radius[0]
|
|
1447
|
+
) {
|
|
1448
|
+
radiusHeatmap = rampedOptionsToLayerPaintSpec(
|
|
1449
|
+
defaultRadiusZoomRamping as ZoomNumberValues,
|
|
1450
|
+
);
|
|
1451
|
+
console.warn(
|
|
1452
|
+
"The option `.radius` can only be property-driven if the option `.property` is provided.",
|
|
1453
|
+
);
|
|
1454
|
+
} else {
|
|
1455
|
+
radiusHeatmap = rampedOptionsToLayerPaintSpec(
|
|
1456
|
+
defaultRadiusZoomRamping as ZoomNumberValues,
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const returnedInfo = {
|
|
1461
|
+
heatmapLayerId: layerId,
|
|
1462
|
+
heatmapSourceId: sourceId,
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
// A new source is added if the map does not have this sourceId and the data is provided
|
|
1466
|
+
if (options.data && !map.getSource(sourceId)) {
|
|
1467
|
+
let data: string | FeatureCollection = options.data;
|
|
1468
|
+
|
|
1469
|
+
// If is a UUID, we extend it to be the URL to a MapTiler Cloud hosted dataset
|
|
1470
|
+
if (typeof data === "string" && isUUID(data)) {
|
|
1471
|
+
data = `https://api.maptiler.com/data/${data}/features.json?key=${config.apiKey}`;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// Adding the source
|
|
1475
|
+
map.addSource(sourceId, {
|
|
1476
|
+
type: "geojson",
|
|
1477
|
+
data: data,
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
map.addLayer({
|
|
1482
|
+
id: layerId,
|
|
1483
|
+
type: "heatmap",
|
|
1484
|
+
source: sourceId,
|
|
1485
|
+
minzoom,
|
|
1486
|
+
maxzoom,
|
|
1487
|
+
paint: {
|
|
1488
|
+
"heatmap-weight": heatmapWeight,
|
|
1489
|
+
|
|
1490
|
+
"heatmap-intensity":
|
|
1491
|
+
typeof intensity === "number"
|
|
1492
|
+
? intensity
|
|
1493
|
+
: (rampedOptionsToLayerPaintSpec(
|
|
1494
|
+
intensity,
|
|
1495
|
+
) as PropertyValueSpecification<number>),
|
|
1496
|
+
|
|
1497
|
+
"heatmap-color": heatmapIntensityFromColorRamp(colorRamp),
|
|
1498
|
+
|
|
1499
|
+
"heatmap-radius": radiusHeatmap,
|
|
1500
|
+
|
|
1501
|
+
"heatmap-opacity":
|
|
1502
|
+
typeof opacity === "number"
|
|
1503
|
+
? opacity
|
|
1504
|
+
: (rampedOptionsToLayerPaintSpec(
|
|
1505
|
+
opacity,
|
|
1506
|
+
) as PropertyValueSpecification<number>),
|
|
1507
|
+
},
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
return returnedInfo;
|
|
1511
|
+
}
|