@bitalltech-maplibre/core 1.0.0

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.
@@ -0,0 +1,1046 @@
1
+ import maplibregl from "maplibre-gl";
2
+ import { default as default2 } from "maplibre-gl";
3
+ const config = {};
4
+ const setMaplibreToolsConfig = (options) => {
5
+ Object.assign(config, options);
6
+ };
7
+ const getMaplibreToolsConfig = () => ({ ...config });
8
+ const OPENFREEMAP_STYLE_BASE_URL = "https://tiles.openfreemap.org/styles";
9
+ const createOpenFreeMapStyle = (type, label, path) => ({
10
+ type,
11
+ label,
12
+ style: `${OPENFREEMAP_STYLE_BASE_URL}/${path}`
13
+ });
14
+ const OPENFREEMAP_STYLES = {
15
+ "openfreemap-liberty": createOpenFreeMapStyle(
16
+ "openfreemap-liberty",
17
+ "OpenFreeMap Liberty",
18
+ "liberty"
19
+ ),
20
+ "openfreemap-bright": createOpenFreeMapStyle(
21
+ "openfreemap-bright",
22
+ "OpenFreeMap Bright",
23
+ "bright"
24
+ ),
25
+ "openfreemap-positron": createOpenFreeMapStyle(
26
+ "openfreemap-positron",
27
+ "OpenFreeMap Positron",
28
+ "positron"
29
+ ),
30
+ "openfreemap-dark": createOpenFreeMapStyle(
31
+ "openfreemap-dark",
32
+ "OpenFreeMap Dark",
33
+ "dark"
34
+ ),
35
+ "openfreemap-fiord": createOpenFreeMapStyle(
36
+ "openfreemap-fiord",
37
+ "OpenFreeMap Fiord",
38
+ "fiord"
39
+ )
40
+ };
41
+ const OPENFREEMAP_STYLE_TYPES = Object.keys(
42
+ OPENFREEMAP_STYLES
43
+ );
44
+ const getOpenFreeMapStyle = (type = "openfreemap-liberty") => OPENFREEMAP_STYLES[type].style;
45
+ const TDT_SUBDOMAINS = ["0", "1", "2", "3", "4", "5", "6", "7"];
46
+ const TDT_ATTRIBUTION = "© 天地图";
47
+ const TDT_MAX_ZOOM = 18;
48
+ const TDT_STYLE_TYPES = ["tdt-image", "tdt-image-label"];
49
+ const getToken = (token) => {
50
+ const tdtToken = token || getMaplibreToolsConfig().tdtToken;
51
+ if (!tdtToken) {
52
+ throw new Error("Tianditu token is required. Pass token or call setMaplibreToolsConfig({ tdtToken }).");
53
+ }
54
+ return tdtToken;
55
+ };
56
+ const createTdtTileUrls = (tileType, token) => {
57
+ const tdtToken = getToken(token);
58
+ return TDT_SUBDOMAINS.map(
59
+ (subdomain) => `https://t${subdomain}.tianditu.gov.cn/DataServer?T=${tileType}&x={x}&y={y}&l={z}&tk=${tdtToken}`
60
+ );
61
+ };
62
+ const createTiandituRasterSource = (tileType, options = {}) => ({
63
+ type: "raster",
64
+ tiles: createTdtTileUrls(tileType, options.token),
65
+ tileSize: 256,
66
+ minzoom: 0,
67
+ maxzoom: TDT_MAX_ZOOM,
68
+ attribution: TDT_ATTRIBUTION
69
+ });
70
+ const createTiandituImageStyle = (options = {}) => {
71
+ const withLabel = options.label !== false;
72
+ const sources = {
73
+ "tdt-image": createTiandituRasterSource("img_w", options)
74
+ };
75
+ const layers = [
76
+ {
77
+ id: "tdt-image",
78
+ type: "raster",
79
+ source: "tdt-image"
80
+ }
81
+ ];
82
+ if (withLabel) {
83
+ sources["tdt-image-label"] = createTiandituRasterSource("cia_w", options);
84
+ layers.push({
85
+ id: "tdt-image-label",
86
+ type: "raster",
87
+ source: "tdt-image-label"
88
+ });
89
+ }
90
+ return {
91
+ version: 8,
92
+ sources,
93
+ layers
94
+ };
95
+ };
96
+ const BASE_MAP_TYPES = [
97
+ ...Object.keys(OPENFREEMAP_STYLES),
98
+ "tdt-image",
99
+ "tdt-image-label"
100
+ ];
101
+ const isBaseMapType = (type) => BASE_MAP_TYPES.includes(type);
102
+ const getBaseMapStyle = (type = "openfreemap-liberty", options = {}) => {
103
+ if (type === "tdt-image") {
104
+ return createTiandituImageStyle({ ...options, label: false });
105
+ }
106
+ if (type === "tdt-image-label") {
107
+ return createTiandituImageStyle({ ...options, label: true });
108
+ }
109
+ return getOpenFreeMapStyle(type);
110
+ };
111
+ const setBaseMap = (map, type, options = {}) => {
112
+ const { diff = false, ...styleOptions } = options;
113
+ map.setStyle(getBaseMapStyle(type, styleOptions), { diff });
114
+ };
115
+ const EARTH_RADIUS = 6378137;
116
+ const DEFAULT_SEGMENTS = 64;
117
+ const toRadians = (value) => value * Math.PI / 180;
118
+ const toDegrees = (value) => value * 180 / Math.PI;
119
+ const normalizeLongitude = (value) => {
120
+ if (value > 180) {
121
+ return value - 360;
122
+ }
123
+ if (value < -180) {
124
+ return value + 360;
125
+ }
126
+ return value;
127
+ };
128
+ const normalizeAngle = (angle) => (angle % 360 + 360) % 360;
129
+ const getSweepAngle = (startAngle, endAngle) => {
130
+ const sweep = normalizeAngle(endAngle) - normalizeAngle(startAngle);
131
+ return sweep <= 0 ? sweep + 360 : sweep;
132
+ };
133
+ const getDestination = (center, distance, bearing) => {
134
+ const angularDistance = distance / EARTH_RADIUS;
135
+ const bearingRadians = toRadians(bearing);
136
+ const latitudeRadians = toRadians(center[1]);
137
+ const longitudeRadians = toRadians(center[0]);
138
+ const nextLatitude = Math.asin(
139
+ Math.sin(latitudeRadians) * Math.cos(angularDistance) + Math.cos(latitudeRadians) * Math.sin(angularDistance) * Math.cos(bearingRadians)
140
+ );
141
+ const nextLongitude = longitudeRadians + Math.atan2(
142
+ Math.sin(bearingRadians) * Math.sin(angularDistance) * Math.cos(latitudeRadians),
143
+ Math.cos(angularDistance) - Math.sin(latitudeRadians) * Math.sin(nextLatitude)
144
+ );
145
+ return [
146
+ normalizeLongitude(toDegrees(nextLongitude)),
147
+ toDegrees(nextLatitude)
148
+ ];
149
+ };
150
+ const createAngleStops = (startAngle, endAngle, segmentCount = DEFAULT_SEGMENTS) => {
151
+ const sweep = getSweepAngle(startAngle, endAngle);
152
+ const segments = Math.max(12, Math.ceil(segmentCount * sweep / 360));
153
+ return Array.from(
154
+ { length: segments + 1 },
155
+ (_value, index) => normalizeAngle(startAngle + sweep * index / segments)
156
+ );
157
+ };
158
+ const closeCoordinates = (coordinates) => {
159
+ if (coordinates.length === 0) {
160
+ return coordinates;
161
+ }
162
+ const [firstLongitude, firstLatitude] = coordinates[0];
163
+ const lastCoordinate = coordinates[coordinates.length - 1];
164
+ if (lastCoordinate[0] === firstLongitude && lastCoordinate[1] === firstLatitude) {
165
+ return coordinates;
166
+ }
167
+ return [...coordinates, [firstLongitude, firstLatitude]];
168
+ };
169
+ const createCircleCoordinates = (center, radius, segmentCount = DEFAULT_SEGMENTS) => closeCoordinates(
170
+ Array.from(
171
+ { length: segmentCount },
172
+ (_value, index) => getDestination(center, radius, 360 * index / segmentCount)
173
+ )
174
+ );
175
+ const createCirclePolygon = (center, radius, segmentCount = DEFAULT_SEGMENTS) => ({
176
+ type: "Polygon",
177
+ coordinates: [createCircleCoordinates(center, radius, segmentCount)]
178
+ });
179
+ const createCircleLine = (center, radius, segmentCount = DEFAULT_SEGMENTS) => ({
180
+ type: "LineString",
181
+ coordinates: createCircleCoordinates(center, radius, segmentCount)
182
+ });
183
+ const createSectorPolygon = (center, radius, startAngle, endAngle, segmentCount = DEFAULT_SEGMENTS) => {
184
+ const arcCoordinates = createAngleStops(
185
+ startAngle,
186
+ endAngle,
187
+ segmentCount
188
+ ).map((bearing) => getDestination(center, radius, bearing));
189
+ return {
190
+ type: "Polygon",
191
+ coordinates: [[center, ...arcCoordinates, center]]
192
+ };
193
+ };
194
+ const createArcLine = (center, radius, startAngle, endAngle, segmentCount = DEFAULT_SEGMENTS) => ({
195
+ type: "LineString",
196
+ coordinates: createAngleStops(startAngle, endAngle, segmentCount).map(
197
+ (bearing) => getDestination(center, radius, bearing)
198
+ )
199
+ });
200
+ const createRayLine = (center, radius, bearing) => ({
201
+ type: "LineString",
202
+ coordinates: [center, getDestination(center, radius, bearing)]
203
+ });
204
+ const createFeature = (geometry, properties) => ({
205
+ type: "Feature",
206
+ geometry,
207
+ properties
208
+ });
209
+ const createFeatureCollection = (features) => ({
210
+ type: "FeatureCollection",
211
+ features
212
+ });
213
+ const removeLayerIfExists = (map, id) => {
214
+ if (map.getLayer(id)) {
215
+ map.removeLayer(id);
216
+ }
217
+ };
218
+ const removeSourceIfExists = (map, id) => {
219
+ if (map.getSource(id)) {
220
+ map.removeSource(id);
221
+ }
222
+ };
223
+ const setLayersVisibility = (map, layerIds, visible) => {
224
+ const visibility = visible ? "visible" : "none";
225
+ layerIds.forEach((layerId) => {
226
+ if (map.getLayer(layerId)) {
227
+ map.setLayoutProperty(layerId, "visibility", visibility);
228
+ }
229
+ });
230
+ };
231
+ const createManagedEffect = (map, initialOptions, renderEffect) => {
232
+ const effectId = initialOptions.id;
233
+ const sourceId = `${effectId}-source`;
234
+ let options = { ...initialOptions };
235
+ let layerIds = [];
236
+ let sourceIds = [sourceId];
237
+ let removed = false;
238
+ let waitingForStyle = false;
239
+ let disposeAnimation;
240
+ const stopAnimation = () => {
241
+ disposeAnimation == null ? void 0 : disposeAnimation();
242
+ disposeAnimation = void 0;
243
+ };
244
+ const clearEffect = () => {
245
+ stopAnimation();
246
+ [...layerIds].reverse().forEach((layerId) => {
247
+ removeLayerIfExists(map, layerId);
248
+ });
249
+ [...sourceIds].reverse().forEach((id) => {
250
+ removeSourceIfExists(map, id);
251
+ });
252
+ layerIds = [];
253
+ sourceIds = [sourceId];
254
+ };
255
+ const setData = (data) => {
256
+ const source = map.getSource(sourceId);
257
+ if (source) {
258
+ source.setData(data);
259
+ }
260
+ };
261
+ const renderNow = () => {
262
+ var _a, _b;
263
+ if (removed) {
264
+ return;
265
+ }
266
+ clearEffect();
267
+ const nextRender = renderEffect(options);
268
+ map.addSource(sourceId, {
269
+ type: "geojson",
270
+ data: nextRender.data
271
+ });
272
+ (_a = nextRender.canvasSources) == null ? void 0 : _a.forEach((canvasSource) => {
273
+ map.addSource(canvasSource.id, canvasSource.source);
274
+ });
275
+ sourceIds = [
276
+ sourceId,
277
+ ...((_b = nextRender.canvasSources) == null ? void 0 : _b.map((canvasSource) => canvasSource.id)) || []
278
+ ];
279
+ layerIds = nextRender.layers.map((layer) => layer.id);
280
+ nextRender.layers.forEach((layer) => {
281
+ var _a2;
282
+ const layerSource = layer.type === "raster" && ((_a2 = nextRender.canvasSources) == null ? void 0 : _a2[0]) ? nextRender.canvasSources[0].id : sourceId;
283
+ map.addLayer(
284
+ {
285
+ ...layer,
286
+ source: layerSource
287
+ },
288
+ options.beforeId
289
+ );
290
+ });
291
+ setLayersVisibility(map, layerIds, options.visible !== false);
292
+ if (nextRender.startAnimation) {
293
+ disposeAnimation = nextRender.startAnimation({
294
+ getOptions: () => options,
295
+ setData
296
+ }) || void 0;
297
+ }
298
+ };
299
+ const handleStyleData = () => {
300
+ if (!waitingForStyle || removed || !map.isStyleLoaded()) {
301
+ return;
302
+ }
303
+ waitingForStyle = false;
304
+ map.off("styledata", handleStyleData);
305
+ renderNow();
306
+ };
307
+ const requestRender = () => {
308
+ if (removed) {
309
+ return;
310
+ }
311
+ if (!map.isStyleLoaded()) {
312
+ if (!waitingForStyle) {
313
+ waitingForStyle = true;
314
+ map.on("styledata", handleStyleData);
315
+ }
316
+ return;
317
+ }
318
+ if (waitingForStyle) {
319
+ waitingForStyle = false;
320
+ map.off("styledata", handleStyleData);
321
+ }
322
+ renderNow();
323
+ };
324
+ requestRender();
325
+ return {
326
+ id: effectId,
327
+ update(nextOptions) {
328
+ if (removed) {
329
+ return;
330
+ }
331
+ options = {
332
+ ...options,
333
+ ...nextOptions,
334
+ id: effectId
335
+ };
336
+ requestRender();
337
+ },
338
+ show() {
339
+ if (removed) {
340
+ return;
341
+ }
342
+ options = {
343
+ ...options,
344
+ visible: true
345
+ };
346
+ setLayersVisibility(map, layerIds, true);
347
+ },
348
+ hide() {
349
+ if (removed) {
350
+ return;
351
+ }
352
+ options = {
353
+ ...options,
354
+ visible: false
355
+ };
356
+ setLayersVisibility(map, layerIds, false);
357
+ },
358
+ remove() {
359
+ if (removed) {
360
+ return;
361
+ }
362
+ removed = true;
363
+ if (waitingForStyle) {
364
+ waitingForStyle = false;
365
+ map.off("styledata", handleStyleData);
366
+ }
367
+ clearEffect();
368
+ }
369
+ };
370
+ };
371
+ const createPulseEffectData = (options, phase = 0) => {
372
+ const baseRadius = Math.max(10, options.radius ?? 70);
373
+ const maxRadius = Math.max(baseRadius, options.maxRadius ?? baseRadius * 2.3);
374
+ const pulseCount = Math.max(1, options.pulseCount ?? 2);
375
+ const lineWidth = Math.max(1, options.lineWidth ?? 2);
376
+ const pulseOpacity = options.pulseOpacity ?? 0.9;
377
+ const features = [
378
+ createFeature(createCirclePolygon(options.center, baseRadius), {
379
+ kind: "core"
380
+ })
381
+ ];
382
+ for (let index = 0; index < pulseCount; index += 1) {
383
+ const progress = (phase + index / pulseCount) % 1;
384
+ const currentRadius = baseRadius + (maxRadius - baseRadius) * progress;
385
+ features.push(
386
+ createFeature(createCircleLine(options.center, currentRadius), {
387
+ kind: "pulse",
388
+ opacity: pulseOpacity * (1 - progress),
389
+ width: lineWidth * (1 - progress * 0.35)
390
+ })
391
+ );
392
+ }
393
+ return createFeatureCollection(features);
394
+ };
395
+ const createPulseMarkerRender = (options) => {
396
+ const color = options.color || "#ff3b30";
397
+ const fillColor = options.fillColor || color;
398
+ return {
399
+ data: createPulseEffectData(options),
400
+ layers: [
401
+ {
402
+ id: `${options.id}-core`,
403
+ type: "fill",
404
+ filter: ["==", ["get", "kind"], "core"],
405
+ paint: {
406
+ "fill-color": fillColor,
407
+ "fill-opacity": options.fillOpacity ?? 0.28
408
+ }
409
+ },
410
+ {
411
+ id: `${options.id}-pulse`,
412
+ type: "line",
413
+ filter: ["==", ["get", "kind"], "pulse"],
414
+ paint: {
415
+ "line-color": color,
416
+ "line-opacity": ["coalesce", ["get", "opacity"], options.pulseOpacity ?? 0.9],
417
+ "line-width": ["coalesce", ["get", "width"], options.lineWidth ?? 2]
418
+ }
419
+ }
420
+ ],
421
+ startAnimation({ getOptions, setData }) {
422
+ const duration = Math.max(400, getOptions().duration ?? 1800);
423
+ let frameId = 0;
424
+ const startTime = performance.now();
425
+ const tick = (timestamp) => {
426
+ const currentOptions = getOptions();
427
+ const phase = (timestamp - startTime) % duration / duration;
428
+ setData(createPulseEffectData(currentOptions, phase));
429
+ frameId = requestAnimationFrame(tick);
430
+ };
431
+ frameId = requestAnimationFrame(tick);
432
+ return () => {
433
+ cancelAnimationFrame(frameId);
434
+ };
435
+ }
436
+ };
437
+ };
438
+ const createRingPulseEffectData = (options, phase = 0) => {
439
+ var _a, _b;
440
+ const baseRadius = Math.max(16, options.radius ?? 110);
441
+ const pulseScale = Math.max(0, options.pulseScale ?? 0.16);
442
+ const pulseProgress = (1 - Math.cos(phase * Math.PI * 2)) / 2;
443
+ const currentRadius = baseRadius * (1 + pulseScale * pulseProgress);
444
+ const haloRadius = currentRadius * 1.28;
445
+ const ringCount = Math.max(1, options.ringCount ?? ((_a = options.radii) == null ? void 0 : _a.length) ?? 3);
446
+ const maxRadius = Math.max(
447
+ currentRadius * 2.2,
448
+ options.maxRadius ?? (((_b = options.radii) == null ? void 0 : _b.length) ? Math.max(...options.radii) : baseRadius * (ringCount * 2.5))
449
+ );
450
+ const ringSpacing = maxRadius / ringCount;
451
+ const features = [
452
+ createFeature(createCirclePolygon(options.center, haloRadius), {
453
+ kind: "halo",
454
+ opacity: (options.haloOpacity ?? 0.16) * (0.75 + pulseProgress * 0.65)
455
+ }),
456
+ createFeature(createCirclePolygon(options.center, currentRadius), {
457
+ kind: "core",
458
+ opacity: (options.fillOpacity ?? 0.28) * (0.88 + pulseProgress * 0.2)
459
+ }),
460
+ createFeature(createCircleLine(options.center, currentRadius * 1.06), {
461
+ kind: "core-ring",
462
+ opacity: (options.lineOpacity ?? 0.95) * (0.8 + pulseProgress * 0.2),
463
+ width: Math.max(1, (options.lineWidth ?? 3) * (0.92 + pulseProgress * 0.12))
464
+ })
465
+ ];
466
+ for (let index = 0; index < ringCount; index += 1) {
467
+ const logicalRadius = (index + phase + 1) * ringSpacing % maxRadius || maxRadius;
468
+ const radiusProgress = logicalRadius / maxRadius;
469
+ features.push(
470
+ createFeature(createCircleLine(options.center, logicalRadius), {
471
+ kind: "ring",
472
+ opacity: Math.max(
473
+ 0.18,
474
+ (options.lineOpacity ?? 0.95) * (1 - radiusProgress * 0.42)
475
+ ),
476
+ width: Math.max(
477
+ 1,
478
+ (options.lineWidth ?? 3) * (1 - radiusProgress * 0.18)
479
+ )
480
+ })
481
+ );
482
+ }
483
+ return createFeatureCollection(features);
484
+ };
485
+ const createRingPulseMarkerRender = (options) => {
486
+ const color = options.color || "#ef4444";
487
+ const fillColor = options.fillColor || color;
488
+ return {
489
+ data: createRingPulseEffectData(options),
490
+ layers: [
491
+ {
492
+ id: `${options.id}-halo`,
493
+ type: "fill",
494
+ filter: ["==", ["get", "kind"], "halo"],
495
+ paint: {
496
+ "fill-color": fillColor,
497
+ "fill-opacity": ["coalesce", ["get", "opacity"], options.haloOpacity ?? 0.16]
498
+ }
499
+ },
500
+ {
501
+ id: `${options.id}-core`,
502
+ type: "fill",
503
+ filter: ["==", ["get", "kind"], "core"],
504
+ paint: {
505
+ "fill-color": fillColor,
506
+ "fill-opacity": ["coalesce", ["get", "opacity"], options.fillOpacity ?? 0.28]
507
+ }
508
+ },
509
+ {
510
+ id: `${options.id}-rings`,
511
+ type: "line",
512
+ filter: [
513
+ "any",
514
+ ["==", ["get", "kind"], "ring"]
515
+ // ["==", ["get", "kind"], "core-ring"],
516
+ ],
517
+ paint: {
518
+ "line-color": color,
519
+ "line-opacity": ["coalesce", ["get", "opacity"], options.lineOpacity ?? 0.95],
520
+ "line-width": ["coalesce", ["get", "width"], options.lineWidth ?? 3]
521
+ }
522
+ }
523
+ ],
524
+ startAnimation({ getOptions, setData }) {
525
+ const duration = Math.max(600, getOptions().duration ?? 2e3);
526
+ let frameId = 0;
527
+ const startTime = performance.now();
528
+ const tick = (timestamp) => {
529
+ const currentOptions = getOptions();
530
+ const phase = (timestamp - startTime) % duration / duration;
531
+ setData(createRingPulseEffectData(currentOptions, phase));
532
+ frameId = requestAnimationFrame(tick);
533
+ };
534
+ frameId = requestAnimationFrame(tick);
535
+ return () => {
536
+ cancelAnimationFrame(frameId);
537
+ };
538
+ }
539
+ };
540
+ };
541
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
542
+ const parseColor = (color) => {
543
+ const value = color.trim();
544
+ const hex = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
545
+ if (hex) {
546
+ const source = hex[1];
547
+ const normalized = source.length === 3 ? source.split("").map((item) => item + item).join("") : source;
548
+ return [
549
+ parseInt(normalized.slice(0, 2), 16),
550
+ parseInt(normalized.slice(2, 4), 16),
551
+ parseInt(normalized.slice(4, 6), 16)
552
+ ];
553
+ }
554
+ const rgb = value.match(
555
+ /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i
556
+ );
557
+ if (rgb) {
558
+ return [
559
+ clamp(Number(rgb[1]), 0, 255),
560
+ clamp(Number(rgb[2]), 0, 255),
561
+ clamp(Number(rgb[3]), 0, 255)
562
+ ];
563
+ }
564
+ return void 0;
565
+ };
566
+ const normalizeCanvasAngle = (angle) => (angle % 360 + 360) % 360;
567
+ const getClockwiseSweep = (startAngle, endAngle) => {
568
+ const sweep = normalizeCanvasAngle(endAngle) - normalizeCanvasAngle(startAngle);
569
+ return sweep <= 0 ? sweep + 360 : sweep;
570
+ };
571
+ const getCanvasArcAngle = (bearing) => (bearing - 90) * Math.PI / 180;
572
+ const toRgbaColor = (color, opacity) => {
573
+ const rgb = parseColor(color);
574
+ if (!rgb) {
575
+ return color;
576
+ }
577
+ return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${clamp(opacity, 0, 1)})`;
578
+ };
579
+ const createSectorCanvasCoordinates = (center, radius) => {
580
+ const north = getDestination(center, radius, 0)[1];
581
+ const east = getDestination(center, radius, 90)[0];
582
+ const south = getDestination(center, radius, 180)[1];
583
+ const west = getDestination(center, radius, 270)[0];
584
+ return [
585
+ [west, north],
586
+ [east, north],
587
+ [east, south],
588
+ [west, south]
589
+ ];
590
+ };
591
+ const createSectorCanvas = () => {
592
+ const canvas = document.createElement("canvas");
593
+ canvas.width = 768;
594
+ canvas.height = 768;
595
+ return canvas;
596
+ };
597
+ const getSectorGradientColors = (options) => {
598
+ var _a;
599
+ const colors = (_a = options.gradient) == null ? void 0 : _a.colors;
600
+ return Array.isArray(colors) && colors.length >= 2 ? colors : void 0;
601
+ };
602
+ const drawSectorGradientCanvas = (canvas, options, angleOffset = 0) => {
603
+ const context = canvas.getContext("2d");
604
+ const colors = getSectorGradientColors(options);
605
+ if (!context || !options.gradient || !colors) {
606
+ return;
607
+ }
608
+ const size = canvas.width;
609
+ const center = size / 2;
610
+ const radius = center - 2;
611
+ const startAngle = options.startAngle + angleOffset;
612
+ const sweep = getClockwiseSweep(startAngle, options.endAngle + angleOffset);
613
+ const startCanvasAngle = getCanvasArcAngle(startAngle);
614
+ const endCanvasAngle = startCanvasAngle + sweep * Math.PI / 180;
615
+ const baseOpacity = options.gradient.opacity ?? options.opacity ?? 0.28;
616
+ const centerOpacity = options.gradient.centerOpacity ?? options.gradient.tailOpacity ?? baseOpacity * 0.25;
617
+ const edgeOpacity = options.gradient.edgeOpacity ?? options.gradient.headOpacity ?? baseOpacity;
618
+ const gradient = context.createRadialGradient(
619
+ center,
620
+ center,
621
+ 0,
622
+ center,
623
+ center,
624
+ radius
625
+ );
626
+ colors.forEach((color, index) => {
627
+ const progress = index / Math.max(1, colors.length - 1);
628
+ const opacity = centerOpacity + (edgeOpacity - centerOpacity) * progress;
629
+ gradient.addColorStop(progress, toRgbaColor(color, opacity));
630
+ });
631
+ context.clearRect(0, 0, size, size);
632
+ context.save();
633
+ context.beginPath();
634
+ context.moveTo(center, center);
635
+ context.arc(center, center, radius, startCanvasAngle, endCanvasAngle, false);
636
+ context.closePath();
637
+ context.clip();
638
+ context.fillStyle = gradient;
639
+ context.fillRect(0, 0, size, size);
640
+ context.restore();
641
+ };
642
+ const createSectorGradientCanvas = (options) => {
643
+ if (!getSectorGradientColors(options)) {
644
+ return void 0;
645
+ }
646
+ const canvas = createSectorCanvas();
647
+ drawSectorGradientCanvas(canvas, options);
648
+ return canvas;
649
+ };
650
+ const createSectorFillFeatures = (options, startAngle, endAngle) => {
651
+ if (getSectorGradientColors(options)) {
652
+ return [];
653
+ }
654
+ return [
655
+ createFeature(
656
+ createSectorPolygon(options.center, options.radius, startAngle, endAngle),
657
+ {
658
+ kind: "sector",
659
+ color: options.color || "#ff453a",
660
+ opacity: options.opacity ?? 0.28
661
+ }
662
+ )
663
+ ];
664
+ };
665
+ const createSectorScanData = (options, angleOffset = 0) => {
666
+ const startAngle = options.startAngle + angleOffset;
667
+ const endAngle = options.endAngle + angleOffset;
668
+ const features = [
669
+ ...createSectorFillFeatures(options, startAngle, endAngle),
670
+ createFeature(createRayLine(options.center, options.radius, startAngle), {
671
+ kind: "frame"
672
+ }),
673
+ createFeature(createRayLine(options.center, options.radius, endAngle), {
674
+ kind: "frame"
675
+ })
676
+ ];
677
+ return createFeatureCollection(features);
678
+ };
679
+ const createSectorScanRender = (options) => {
680
+ const canvas = createSectorGradientCanvas(options);
681
+ const fillLayer = canvas ? {
682
+ id: `${options.id}-sector-fill`,
683
+ type: "raster",
684
+ paint: {
685
+ "raster-opacity": 1,
686
+ "raster-fade-duration": 0
687
+ }
688
+ } : {
689
+ id: `${options.id}-sector-fill`,
690
+ type: "fill",
691
+ filter: ["==", ["get", "kind"], "sector"],
692
+ paint: {
693
+ "fill-color": ["coalesce", ["get", "color"], options.color || "#ff453a"],
694
+ "fill-opacity": ["coalesce", ["get", "opacity"], options.opacity ?? 0.28],
695
+ "fill-antialias": false
696
+ }
697
+ };
698
+ return {
699
+ data: createSectorScanData(options),
700
+ canvasSources: canvas ? [
701
+ {
702
+ id: `${options.id}-canvas-source`,
703
+ source: {
704
+ type: "canvas",
705
+ canvas,
706
+ coordinates: createSectorCanvasCoordinates(
707
+ options.center,
708
+ options.radius
709
+ ),
710
+ animate: Boolean(options.rotationSpeed && options.rotationSpeed !== 0)
711
+ }
712
+ }
713
+ ] : void 0,
714
+ layers: [
715
+ fillLayer,
716
+ {
717
+ id: `${options.id}-sector-frame`,
718
+ type: "line",
719
+ filter: ["==", ["get", "kind"], "frame"],
720
+ paint: {
721
+ "line-color": options.outlineColor || options.color || "#ff453a",
722
+ "line-opacity": options.outlineOpacity ?? 0.95,
723
+ "line-width": options.outlineWidth ?? 2
724
+ }
725
+ }
726
+ ],
727
+ startAnimation: options.rotationSpeed && options.rotationSpeed !== 0 ? ({ getOptions, setData }) => {
728
+ const startTime = performance.now();
729
+ let frameId = 0;
730
+ const tick = (timestamp) => {
731
+ const currentOptions = getOptions();
732
+ const angleOffset = (timestamp - startTime) / 1e3 * (currentOptions.rotationSpeed || 0);
733
+ if (canvas) {
734
+ drawSectorGradientCanvas(canvas, currentOptions, angleOffset);
735
+ }
736
+ setData(createSectorScanData(currentOptions, angleOffset));
737
+ frameId = requestAnimationFrame(tick);
738
+ };
739
+ frameId = requestAnimationFrame(tick);
740
+ return () => {
741
+ cancelAnimationFrame(frameId);
742
+ };
743
+ } : void 0
744
+ };
745
+ };
746
+ const createDirectionalPulseData = (options, leadingDistance) => {
747
+ const radius = Math.max(40, options.radius);
748
+ const direction = options.direction ?? 270;
749
+ const spread = Math.min(180, Math.max(8, options.spread ?? 72));
750
+ const lineCount = Math.max(1, options.lineCount ?? 7);
751
+ const lineSpacing = Math.max(20, options.lineSpacing ?? Math.max(80, radius / 9));
752
+ const phaseDistance = leadingDistance ?? 0;
753
+ const innerRadius = Math.max(
754
+ 0,
755
+ Math.min(radius * 0.35, options.innerRadius ?? Math.min(lineSpacing * 0.4, 120))
756
+ );
757
+ const startAngle = direction - spread / 2;
758
+ const endAngle = direction + spread / 2;
759
+ const features = [];
760
+ for (let index = 0; index < lineCount; index += 1) {
761
+ const currentRadius = innerRadius + phaseDistance + index * lineSpacing;
762
+ if (currentRadius > radius) {
763
+ continue;
764
+ }
765
+ const distanceProgress = currentRadius / radius;
766
+ const headProgress = 1 - index / Math.max(1, lineCount);
767
+ features.push(
768
+ createFeature(
769
+ createArcLine(options.center, currentRadius, startAngle, endAngle),
770
+ {
771
+ kind: "directional-pulse-line",
772
+ opacity: Math.max(
773
+ 0.08,
774
+ (options.opacity ?? 0.92) * (0.42 + headProgress * 0.58) * (1 - distanceProgress * 0.28)
775
+ ),
776
+ width: Math.max(
777
+ 1,
778
+ (options.lineWidth ?? 2.8) * (0.86 + headProgress * 0.24) * (1 - distanceProgress * 0.08)
779
+ )
780
+ }
781
+ )
782
+ );
783
+ }
784
+ return createFeatureCollection(features);
785
+ };
786
+ const createDirectionalPulseRender = (options) => ({
787
+ data: createDirectionalPulseData(options),
788
+ layers: [
789
+ {
790
+ id: `${options.id}-directional-pulse`,
791
+ type: "line",
792
+ filter: ["==", ["get", "kind"], "directional-pulse-line"],
793
+ layout: {
794
+ "line-cap": "round",
795
+ "line-join": "round"
796
+ },
797
+ paint: {
798
+ "line-color": options.color || "#ef4444",
799
+ "line-opacity": ["coalesce", ["get", "opacity"], options.opacity ?? 0.92],
800
+ "line-width": ["coalesce", ["get", "width"], options.lineWidth ?? 2.8]
801
+ }
802
+ }
803
+ ],
804
+ startAnimation: options.speed && options.speed > 0 ? ({ getOptions, setData }) => {
805
+ const startTime = performance.now();
806
+ let frameId = 0;
807
+ const tick = (timestamp) => {
808
+ const currentOptions = getOptions();
809
+ const radius = Math.max(40, currentOptions.radius);
810
+ const effectiveLineSpacing = Math.max(
811
+ 20,
812
+ currentOptions.lineSpacing ?? Math.max(80, radius / 9)
813
+ );
814
+ const leadingDistance = (timestamp - startTime) / 1e3 * (currentOptions.speed ?? 0) % effectiveLineSpacing;
815
+ setData(createDirectionalPulseData(currentOptions, leadingDistance));
816
+ frameId = requestAnimationFrame(tick);
817
+ };
818
+ frameId = requestAnimationFrame(tick);
819
+ return () => {
820
+ cancelAnimationFrame(frameId);
821
+ };
822
+ } : void 0
823
+ });
824
+ const createRadarSweepData = (options, angleOffset = 0) => {
825
+ var _a;
826
+ const ringCount = Math.max(0, options.ringCount ?? 4);
827
+ const trailCount = Math.max(1, options.trailCount ?? 8);
828
+ const sweepAngle = Math.max(12, options.sweepAngle ?? 42);
829
+ const trailGap = sweepAngle * 0.22;
830
+ const tailOpacity = options.tailOpacity ?? 0.04;
831
+ const headOpacity = options.headOpacity ?? Math.max(0.24, options.opacity ?? 0.3);
832
+ const sweepStartAngle = (options.sweepStartAngle ?? 0) + angleOffset;
833
+ const showCrosshair = options.showCrosshair !== false;
834
+ const showDistanceLabels = options.showDistanceLabels !== false;
835
+ const coreRadius = Math.max(
836
+ 20,
837
+ options.coreRadius ?? Math.min(160, options.radius * 0.035)
838
+ );
839
+ const features = [];
840
+ for (let index = ringCount; index >= 1; index -= 1) {
841
+ const distance = options.radius * index / ringCount;
842
+ features.push(
843
+ createFeature(
844
+ createCircleLine(options.center, distance),
845
+ {
846
+ kind: "ring"
847
+ }
848
+ )
849
+ );
850
+ if (showDistanceLabels) {
851
+ const text = ((_a = options.distanceLabelFormatter) == null ? void 0 : _a.call(options, distance, index)) ?? `${Math.round(distance)}`;
852
+ [
853
+ { bearing: 0, textAnchor: "bottom" },
854
+ { bearing: 90, textAnchor: "left" },
855
+ { bearing: 180, textAnchor: "top" },
856
+ { bearing: 270, textAnchor: "right" }
857
+ ].forEach((label) => {
858
+ features.push(
859
+ createFeature(
860
+ {
861
+ type: "Point",
862
+ coordinates: getDestination(options.center, distance, label.bearing)
863
+ },
864
+ {
865
+ kind: "distance-label",
866
+ text,
867
+ textAnchor: label.textAnchor
868
+ }
869
+ )
870
+ );
871
+ });
872
+ }
873
+ }
874
+ if (showCrosshair) {
875
+ features.push(
876
+ createFeature(
877
+ {
878
+ type: "LineString",
879
+ coordinates: [
880
+ getDestination(options.center, options.radius, 180),
881
+ getDestination(options.center, options.radius, 0)
882
+ ]
883
+ },
884
+ {
885
+ kind: "crosshair"
886
+ }
887
+ ),
888
+ createFeature(
889
+ {
890
+ type: "LineString",
891
+ coordinates: [
892
+ getDestination(options.center, options.radius, 270),
893
+ getDestination(options.center, options.radius, 90)
894
+ ]
895
+ },
896
+ {
897
+ kind: "crosshair"
898
+ }
899
+ )
900
+ );
901
+ }
902
+ features.push(
903
+ createFeature(createCirclePolygon(options.center, coreRadius), {
904
+ kind: "core"
905
+ })
906
+ );
907
+ for (let index = trailCount - 1; index >= 0; index -= 1) {
908
+ const startAngle = sweepStartAngle - trailGap * index;
909
+ const endAngle = startAngle + sweepAngle;
910
+ const progress = (trailCount - index) / trailCount;
911
+ const opacity = tailOpacity + (headOpacity - tailOpacity) * progress * progress;
912
+ features.push(
913
+ createFeature(
914
+ createSectorPolygon(options.center, options.radius, startAngle, endAngle),
915
+ {
916
+ kind: "sweep",
917
+ opacity
918
+ }
919
+ )
920
+ );
921
+ }
922
+ features.push(
923
+ createFeature(
924
+ createSectorPolygon(
925
+ options.center,
926
+ options.radius,
927
+ sweepStartAngle + sweepAngle * 0.42,
928
+ sweepStartAngle + sweepAngle
929
+ ),
930
+ {
931
+ kind: "head",
932
+ opacity: Math.min(1, headOpacity + 0.08)
933
+ }
934
+ )
935
+ );
936
+ return createFeatureCollection(features);
937
+ };
938
+ const createRadarSweepRender = (options) => ({
939
+ data: createRadarSweepData(options),
940
+ layers: [
941
+ {
942
+ id: `${options.id}-radar-trail`,
943
+ type: "fill",
944
+ filter: ["==", ["get", "kind"], "sweep"],
945
+ paint: {
946
+ "fill-color": options.color || "#22c55e",
947
+ "fill-opacity": ["coalesce", ["get", "opacity"], options.opacity ?? 0.32]
948
+ }
949
+ },
950
+ {
951
+ id: `${options.id}-radar-rings`,
952
+ type: "line",
953
+ filter: ["==", ["get", "kind"], "ring"],
954
+ paint: {
955
+ "line-color": options.ringColor || options.color || "#22c55e",
956
+ "line-opacity": options.ringOpacity ?? 0.95,
957
+ "line-width": options.ringWidth ?? 2
958
+ }
959
+ },
960
+ {
961
+ id: `${options.id}-radar-crosshair`,
962
+ type: "line",
963
+ filter: ["==", ["get", "kind"], "crosshair"],
964
+ paint: {
965
+ "line-color": options.crosshairColor || options.ringColor || options.color || "#22c55e",
966
+ "line-opacity": options.crosshairOpacity ?? 0.5,
967
+ "line-width": options.crosshairWidth ?? 1.4
968
+ }
969
+ },
970
+ {
971
+ id: `${options.id}-radar-distance-labels`,
972
+ type: "symbol",
973
+ filter: ["==", ["get", "kind"], "distance-label"],
974
+ layout: {
975
+ "text-field": ["get", "text"],
976
+ "text-size": options.distanceLabelSize ?? 12,
977
+ "text-anchor": ["get", "textAnchor"],
978
+ "text-allow-overlap": true,
979
+ "text-ignore-placement": true
980
+ },
981
+ paint: {
982
+ "text-color": options.distanceLabelColor || options.ringColor || options.color || "#22c55e",
983
+ "text-opacity": options.distanceLabelOpacity ?? 0.92,
984
+ "text-halo-color": options.distanceLabelHaloColor || "rgba(15, 23, 42, 0.42)",
985
+ "text-halo-width": 1
986
+ }
987
+ }
988
+ ],
989
+ startAnimation({ getOptions, setData }) {
990
+ const startTime = performance.now();
991
+ let frameId = 0;
992
+ const tick = (timestamp) => {
993
+ const currentOptions = getOptions();
994
+ const angleOffset = (timestamp - startTime) / 1e3 * (currentOptions.rotationSpeed ?? 36);
995
+ setData(createRadarSweepData(currentOptions, angleOffset));
996
+ frameId = requestAnimationFrame(tick);
997
+ };
998
+ frameId = requestAnimationFrame(tick);
999
+ return () => {
1000
+ cancelAnimationFrame(frameId);
1001
+ };
1002
+ }
1003
+ });
1004
+ const addPulseMarker = (map, options) => createManagedEffect(map, options, createPulseMarkerRender);
1005
+ const addRingPulseMarker = (map, options) => createManagedEffect(map, options, createRingPulseMarkerRender);
1006
+ const addSectorScan = (map, options) => createManagedEffect(map, options, createSectorScanRender);
1007
+ const addDirectionalPulse = (map, options) => createManagedEffect(map, options, createDirectionalPulseRender);
1008
+ const addRadarSweep = (map, options) => createManagedEffect(map, options, createRadarSweepRender);
1009
+ const resolveStyle = (style, tdtToken) => {
1010
+ if (!style) {
1011
+ return getBaseMapStyle("openfreemap-liberty");
1012
+ }
1013
+ if (typeof style === "string" && isBaseMapType(style)) {
1014
+ return getBaseMapStyle(style, { token: tdtToken });
1015
+ }
1016
+ return style;
1017
+ };
1018
+ const createMap = (options) => {
1019
+ const { style, tdtToken, ...mapOptions } = options;
1020
+ return new maplibregl.Map({
1021
+ ...mapOptions,
1022
+ style: resolveStyle(style, tdtToken)
1023
+ });
1024
+ };
1025
+ export {
1026
+ BASE_MAP_TYPES,
1027
+ OPENFREEMAP_STYLES,
1028
+ OPENFREEMAP_STYLE_BASE_URL,
1029
+ OPENFREEMAP_STYLE_TYPES,
1030
+ TDT_STYLE_TYPES,
1031
+ addDirectionalPulse,
1032
+ addPulseMarker,
1033
+ addRadarSweep,
1034
+ addRingPulseMarker,
1035
+ addSectorScan,
1036
+ createMap,
1037
+ createTiandituImageStyle,
1038
+ createTiandituRasterSource,
1039
+ getBaseMapStyle,
1040
+ getMaplibreToolsConfig,
1041
+ getOpenFreeMapStyle,
1042
+ isBaseMapType,
1043
+ default2 as maplibregl,
1044
+ setBaseMap,
1045
+ setMaplibreToolsConfig
1046
+ };