solid-map-gl 2.1.0 → 2.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.
@@ -1,38 +1,19 @@
1
+ import {
2
+ MapProvider,
3
+ useMapContext
4
+ } from "../chunk/N4NHJCAS.jsx";
5
+
1
6
  // src/components/MapGL/index.tsx
2
7
  import {
3
8
  createSignal,
4
- createEffect as createEffect2,
9
+ createEffect,
5
10
  onMount,
6
11
  onCleanup,
7
12
  on
8
13
  } from "solid-js";
9
14
  import { isServer } from "solid-js/web";
10
15
 
11
- // src/components/MapProvider/index.tsx
12
- import { createContext, useContext, createEffect } from "solid-js";
13
- import { createStore } from "solid-js/store";
14
- var defaultState = {
15
- map: null,
16
- mapLib: null,
17
- isMapLibre: false,
18
- constants: {},
19
- themeVersion: 0
20
- };
21
- var MapContext = createContext([defaultState]);
22
- var useMapContext = () => useContext(MapContext);
23
- var MapProvider = (props) => {
24
- const [state, setState] = createStore({ ...defaultState });
25
- props.map && setState("map", props.map);
26
- props.mapLib && setState("mapLib", props.mapLib);
27
- setState("isMapLibre", !!props.isMapLibre);
28
- setState("constants", props.constants || {});
29
- setState("themeVersion", props.themeVersion || 0);
30
- createEffect(() => setState("constants", props.constants || {}));
31
- createEffect(() => setState("themeVersion", props.themeVersion || 0));
32
- return <MapContext.Provider value={[state]}>{props.children}</MapContext.Provider>;
33
- };
34
-
35
- // src/events.ts
16
+ // src/lib/events.ts
36
17
  var mapEvents = [
37
18
  "onMouseDown",
38
19
  "onMouseUp",
@@ -111,7 +92,7 @@ var drawEvents = [
111
92
  "onActionable"
112
93
  ];
113
94
 
114
- // src/mapStyles.ts
95
+ // src/lib/mapStyles.ts
115
96
  var mapBase = "mapbox://styles/mapbox";
116
97
  var gitBase = "https://raw.githubusercontent.com/";
117
98
  var gishub = "https://raw.githubusercontent.com/GIShub4/map-styles/main/";
@@ -229,6 +210,79 @@ var rasterStyleList = {
229
210
  }
230
211
  };
231
212
 
213
+ // src/components/MapGL/tilesSettled.ts
214
+ var nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));
215
+ var hasActiveFadeTransition = (map) => {
216
+ try {
217
+ return !!map.style?.hasTransitions?.();
218
+ } catch {
219
+ return false;
220
+ }
221
+ };
222
+ function pollTilesLoaded(map, timeout) {
223
+ return new Promise((resolve) => {
224
+ const deadline = Date.now() + timeout;
225
+ const poll = () => {
226
+ if (map.areTilesLoaded()) {
227
+ resolve(true);
228
+ return;
229
+ }
230
+ if (Date.now() >= deadline) {
231
+ resolve(false);
232
+ return;
233
+ }
234
+ setTimeout(poll, 100);
235
+ };
236
+ poll();
237
+ });
238
+ }
239
+ async function settleAfterIdle(map, options = {}) {
240
+ const { timeout = 1e4, fadeMargin = 400 } = options;
241
+ await pollTilesLoaded(map, timeout);
242
+ map.triggerRepaint();
243
+ await nextFrame();
244
+ await nextFrame();
245
+ if (fadeMargin > 0) {
246
+ const fadeDeadline = Date.now() + Math.max(fadeMargin, 2e3);
247
+ while (hasActiveFadeTransition(map) && Date.now() < fadeDeadline) {
248
+ map.triggerRepaint();
249
+ await nextFrame();
250
+ }
251
+ await new Promise((resolve) => setTimeout(resolve, fadeMargin));
252
+ map.triggerRepaint();
253
+ await nextFrame();
254
+ await nextFrame();
255
+ }
256
+ }
257
+ function waitForIdleAndSettle(map, options = {}) {
258
+ return new Promise((resolve) => {
259
+ map.once("idle", () => {
260
+ settleAfterIdle(map, options).then(resolve);
261
+ });
262
+ });
263
+ }
264
+ function disableRasterFade(map) {
265
+ for (const layer of map.getStyle()?.layers ?? []) {
266
+ try {
267
+ map.setPaintProperty(layer.id, "raster-fade-duration", 0);
268
+ } catch {
269
+ }
270
+ }
271
+ }
272
+
273
+ // src/components/MapGL/offscreenCapture.ts
274
+ function createCapturer(map, settleOptions) {
275
+ return {
276
+ map,
277
+ waitUntilSettled: () => waitForIdleAndSettle(map, settleOptions),
278
+ capture: (type = "image/jpeg", quality = 0.92) => map.getCanvas().toDataURL(type, quality),
279
+ captureWhenSettled: async (type = "image/jpeg", quality = 0.92) => {
280
+ await waitForIdleAndSettle(map, settleOptions);
281
+ return map.getCanvas().toDataURL(type, quality);
282
+ }
283
+ };
284
+ }
285
+
232
286
  // src/components/MapGL/index.tsx
233
287
  var MapGL = (props) => {
234
288
  let map;
@@ -237,6 +291,7 @@ var MapGL = (props) => {
237
291
  let mutationObserver;
238
292
  let mapLib;
239
293
  let isMapLibre = false;
294
+ let tilesLoadedGeneration = 0;
240
295
  const [mapLoaded, setMapLoaded] = createSignal(null);
241
296
  const [themeVersion, setThemeVersion] = createSignal(0);
242
297
  const [darkMode, setDarkMode] = createSignal(
@@ -254,6 +309,14 @@ var MapGL = (props) => {
254
309
  ) || style : style;
255
310
  };
256
311
  onMount(async () => {
312
+ try {
313
+ await setupMap();
314
+ } catch (error) {
315
+ console.error("[MapGL] Failed to initialize map:", error);
316
+ props.onError?.(error);
317
+ }
318
+ });
319
+ const setupMap = async () => {
257
320
  mapLib = props.mapLib || await import("mapbox-gl");
258
321
  if (!mapLib.Map) mapLib = window["maplibregl"] || window["mapboxgl"];
259
322
  if (typeof mapLib.supported === "function" && !mapLib.supported())
@@ -269,7 +332,11 @@ var MapGL = (props) => {
269
332
  projection: props.options?.projection,
270
333
  container: mapRef,
271
334
  style: getStyle(props.options?.style, props.darkStyle),
272
- fitBoundsOptions: { padding: props.viewport?.padding }
335
+ fitBoundsOptions: { padding: props.viewport?.padding },
336
+ // Required for capture()/toDataURL() to read back anything from the canvas at all — forced
337
+ // regardless of `options` since getting this wrong doesn't degrade capture, it silently
338
+ // breaks it (an all-transparent/blank image, no error).
339
+ ...props.offscreen ? { preserveDrawingBuffer: true } : {}
273
340
  });
274
341
  map.debug = props.debug;
275
342
  map.debugEvents = props.debugEvents;
@@ -283,7 +350,7 @@ var MapGL = (props) => {
283
350
  if (typeof prop === "function") {
284
351
  map.on(event, (evt) => {
285
352
  setTimeout(() => {
286
- if (evt.clickOnLayer) return;
353
+ if (event === "click" && evt.clickOnLayer) return;
287
354
  prop(evt);
288
355
  props.debugEvents && debug(`Map '${event}' event:`, evt);
289
356
  }, 0);
@@ -292,7 +359,7 @@ var MapGL = (props) => {
292
359
  Object.keys(prop).forEach((layerId) => {
293
360
  map.on(event, layerId, (evt) => {
294
361
  setTimeout(() => {
295
- if (evt.clickOnLayer) return;
362
+ if (event === "click" && evt.clickOnLayer) return;
296
363
  prop[layerId](evt);
297
364
  props.debugEvents && debug(`Map '${event}' event on '${layerId}':`, evt);
298
365
  }, 0);
@@ -301,9 +368,30 @@ var MapGL = (props) => {
301
368
  }
302
369
  }
303
370
  });
371
+ if (props.onTilesLoaded) {
372
+ const timeout = props.tilesLoadedTimeout ?? 1e4;
373
+ const fadeMargin = props.tilesLoadedFadeMargin ?? 400;
374
+ map.on("idle", () => {
375
+ const generation = ++tilesLoadedGeneration;
376
+ settleAfterIdle(map, { timeout, fadeMargin }).then(() => {
377
+ if (generation !== tilesLoadedGeneration) return;
378
+ debug("Tiles settled");
379
+ props.onTilesLoaded();
380
+ });
381
+ });
382
+ }
304
383
  map.once("load", () => {
305
384
  setMapLoaded(map);
306
385
  debug("Map loaded");
386
+ if (props.offscreen) {
387
+ if (props.offscreen.disableRasterFade !== false) disableRasterFade(map);
388
+ props.onCapturerReady?.(
389
+ createCapturer(map, {
390
+ timeout: props.tilesLoadedTimeout ?? 1e4,
391
+ fadeMargin: props.tilesLoadedFadeMargin ?? 400
392
+ })
393
+ );
394
+ }
307
395
  ["mousedown", "touchstart", "wheel"].forEach(
308
396
  (event) => map.on(
309
397
  event,
@@ -382,12 +470,12 @@ var MapGL = (props) => {
382
470
  props.onViewportChange(viewport);
383
471
  });
384
472
  });
385
- });
386
- createEffect2(
473
+ };
474
+ createEffect(
387
475
  on(
388
476
  () => props.viewport,
389
477
  (vp) => {
390
- if (props.id !== vp?.id) return;
478
+ if (!map || props.id !== vp?.id) return;
391
479
  if (expectingOwnEcho) {
392
480
  expectingOwnEcho = false;
393
481
  return;
@@ -412,7 +500,7 @@ var MapGL = (props) => {
412
500
  { defer: true }
413
501
  )
414
502
  );
415
- createEffect2(() => {
503
+ createEffect(() => {
416
504
  const loadedMap = mapLoaded();
417
505
  if (typeof loadedMap?.setConfigProperty !== "function") {
418
506
  if (loadedMap && props.config && Object.keys(props.config).length) {
@@ -430,13 +518,13 @@ var MapGL = (props) => {
430
518
  debug(`Set Config (${id}:${key}) to:`, value);
431
519
  }
432
520
  });
433
- createEffect2(() => {
521
+ createEffect(() => {
434
522
  const proj = props.options?.projection;
435
523
  if (!map || !proj) return;
436
524
  map.setProjection(proj);
437
525
  debug("Set Projection to:", proj);
438
526
  });
439
- createEffect2(() => {
527
+ createEffect(() => {
440
528
  const cur = props.cursorStyle;
441
529
  if (!map || !cur) return;
442
530
  map.getCanvas().style.cursor = cur;
@@ -451,7 +539,7 @@ var MapGL = (props) => {
451
539
  });
452
540
  return list;
453
541
  };
454
- createEffect2((prev) => {
542
+ createEffect((prev) => {
455
543
  const style = getStyle(props.options?.style, props.darkStyle);
456
544
  if (map?.isStyleLoaded() && prev !== style) {
457
545
  const oldStyle = map.getStyle();
@@ -484,7 +572,7 @@ var MapGL = (props) => {
484
572
  "showPadding",
485
573
  "showOverdrawInspector"
486
574
  ].forEach((item) => {
487
- createEffect2(() => {
575
+ createEffect(() => {
488
576
  const prop = props[item];
489
577
  if (!map || !prop) return;
490
578
  map[item] = prop;
@@ -494,6 +582,7 @@ var MapGL = (props) => {
494
582
  onCleanup(() => {
495
583
  resizeObserver?.disconnect();
496
584
  mutationObserver?.disconnect();
585
+ tilesLoadedGeneration++;
497
586
  map?.remove();
498
587
  debug("Map removed");
499
588
  });
@@ -502,7 +591,14 @@ var MapGL = (props) => {
502
591
  id={props.id}
503
592
  class={props?.class}
504
593
  classList={props?.classList}
505
- style={{ width: "100%", height: "100%", ...props.style }}
594
+ style={props.offscreen ? {
595
+ position: "fixed",
596
+ left: "-99999px",
597
+ top: "-99999px",
598
+ width: `${props.offscreen.width}px`,
599
+ height: `${props.offscreen.height}px`,
600
+ ...props.style
601
+ } : { width: "100%", height: "100%", ...props.style }}
506
602
  >
507
603
  {mapLoaded() && <MapProvider
508
604
  map={mapLoaded()}
@@ -520,13 +616,13 @@ var MapGL = (props) => {
520
616
  // src/components/Source/index.tsx
521
617
  import {
522
618
  onCleanup as onCleanup2,
523
- createEffect as createEffect3,
524
- createContext as createContext2,
525
- useContext as useContext2,
619
+ createEffect as createEffect2,
620
+ createContext,
621
+ useContext,
526
622
  createUniqueId
527
623
  } from "solid-js";
528
- var SourceContext = createContext2();
529
- var useSourceId = () => useContext2(SourceContext);
624
+ var SourceContext = createContext();
625
+ var useSourceId = () => useContext(SourceContext);
530
626
  var Source = (props) => {
531
627
  const [ctx] = useMapContext();
532
628
  props.id ??= createUniqueId();
@@ -558,14 +654,14 @@ var Source = (props) => {
558
654
  debug("Add Source:", props.id);
559
655
  switch (anySource().type) {
560
656
  case "geojson":
561
- createEffect3(() => {
657
+ createEffect2(() => {
562
658
  const data = anySource().data;
563
659
  ctx.map.getSource(props.id).setData(data || {});
564
660
  debug("Update GeoJSON Data:", props.id);
565
661
  });
566
662
  break;
567
663
  case "image":
568
- createEffect3(() => {
664
+ createEffect2(() => {
569
665
  const url = anySource().url;
570
666
  const coords = anySource().coordinates;
571
667
  ctx.map.getSource(props.id).updateImage(url, coords);
@@ -573,7 +669,7 @@ var Source = (props) => {
573
669
  });
574
670
  break;
575
671
  case "vector":
576
- createEffect3(() => {
672
+ createEffect2(() => {
577
673
  const url = anySource().url;
578
674
  const tiles = anySource().tiles;
579
675
  const source = ctx.map.getSource(props.id);
@@ -582,7 +678,7 @@ var Source = (props) => {
582
678
  });
583
679
  break;
584
680
  case "raster":
585
- createEffect3(() => {
681
+ createEffect2(() => {
586
682
  const src = lookup(anySource().url);
587
683
  const source = ctx.map.getSource(props.id);
588
684
  src.url ? source.setUrl(src.url) : source.setTiles(src.tiles);
@@ -605,12 +701,12 @@ var Source = (props) => {
605
701
  // src/components/Layer/index.tsx
606
702
  import {
607
703
  onCleanup as onCleanup3,
608
- createEffect as createEffect4,
704
+ createEffect as createEffect3,
609
705
  createMemo,
610
706
  createUniqueId as createUniqueId2
611
707
  } from "solid-js";
612
708
 
613
- // src/styles.ts
709
+ // src/components/Layer/styles.ts
614
710
  var baseStyle = [
615
711
  "id",
616
712
  "type",
@@ -674,7 +770,7 @@ var layoutStyles = [
674
770
  "text-writing-mode"
675
771
  ];
676
772
 
677
- // src/colors.ts
773
+ // src/components/Layer/colors.ts
678
774
  var probe;
679
775
  var toRgbString = (cssColor) => {
680
776
  probe ??= document.body.appendChild(document.createElement("div"));
@@ -707,8 +803,37 @@ var resolveColor = (value) => {
707
803
  }
708
804
  return value;
709
805
  };
806
+ var toRgbaComponents = (value) => {
807
+ const rgb = toRgbString(resolveColor(value)) ?? value;
808
+ const match = rgb.match(/rgba?\(([^)]+)\)/i);
809
+ if (!match) return [0, 0, 0, 1];
810
+ const [r, g, b, a = 1] = match[1].split(",").map(Number);
811
+ return [r, g, b, a];
812
+ };
710
813
 
711
814
  // src/components/Layer/index.tsx
815
+ var PULSE_DEFAULTS = {
816
+ property: "icon-halo-width",
817
+ from: 0,
818
+ to: 4,
819
+ duration: 1500,
820
+ waveform: "out"
821
+ };
822
+ var PULSE_RAMP_FRACTION = 0.75;
823
+ var easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
824
+ var pulseShape = (elapsed, duration, waveform) => {
825
+ const phase = elapsed % duration / duration;
826
+ if (waveform === "in-out") return (1 - Math.cos(phase * Math.PI * 2)) / 2;
827
+ const ramp = Math.min(phase / PULSE_RAMP_FRACTION, 1);
828
+ return easeOutQuad(ramp);
829
+ };
830
+ var lerpColor = (from, to, t) => {
831
+ const r = Math.round(from[0] + (to[0] - from[0]) * t);
832
+ const g = Math.round(from[1] + (to[1] - from[1]) * t);
833
+ const b = Math.round(from[2] + (to[2] - from[2]) * t);
834
+ const a = from[3] + (to[3] - from[3]) * t;
835
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
836
+ };
712
837
  var diff = (newProps = {}, prevProps = {}) => {
713
838
  const keys = /* @__PURE__ */ new Set([...Object.keys(newProps), ...Object.keys(prevProps)]);
714
839
  return [...keys].reduce((acc, key) => {
@@ -720,9 +845,16 @@ var diff = (newProps = {}, prevProps = {}) => {
720
845
  }, []);
721
846
  };
722
847
  var newKey = (key, type) => (key.startsWith(type) || key.startsWith("icon") || key.startsWith("text") ? "" : type + "-") + key.replace(/[A-Z]/g, (s) => "-" + s.toLowerCase());
723
- var resolveColor2 = (key, value) => key.endsWith("color") && typeof value === "string" ? resolveColor(value) : value;
848
+ var misplacedTopLevelProps = ["slot", "filter", "visible", "beforeId", "beforeType"];
849
+ var resolveColor2 = (key, value) => {
850
+ if (!key.endsWith("color")) return value;
851
+ if (Array.isArray(value)) return value.map((v) => resolveColor2(key, v));
852
+ return typeof value === "string" ? resolveColor(value) : value;
853
+ };
724
854
  var constantRef = /^@(.+)$/;
725
855
  var resolveConstant = (key, value, constants, debug) => {
856
+ if (Array.isArray(value))
857
+ return value.map((v) => resolveConstant(key, v, constants, debug));
726
858
  if (typeof value !== "string") return value;
727
859
  const match = value.match(constantRef);
728
860
  if (!match) return value;
@@ -743,6 +875,10 @@ var updateStyle = (oldStyle, constants = {}, debug = () => {
743
875
  Object.entries(oldStyle).forEach(([key, value]) => {
744
876
  if (baseStyle.includes(key)) style[key] = value;
745
877
  else {
878
+ if (misplacedTopLevelProps.includes(key))
879
+ debug(
880
+ `"${key}" is a <Layer> prop, not a style property \u2014 belongs outside "style", not inside it. Treating it as a paint/layout property instead.`
881
+ );
746
882
  const nk = newKey(key, oldStyle.type);
747
883
  layoutStyles.includes(nk) ? layout[nk] = resolveConstant(nk, value, constants, debug) : paint[nk] = resolvePaint(nk, value);
748
884
  }
@@ -774,7 +910,7 @@ var Layer = (props) => {
774
910
  ...updateStyle(props.style, ctx.constants, debug),
775
911
  id: layerId,
776
912
  source: sourceId,
777
- // `slot` is Mapbox Standard-Style-only — MapLibre has no equivalent (see docs/dev-notes.md)
913
+ // `slot` is Mapbox Standard-Style-only — MapLibre has no equivalent (see .claude/dev-notes.md)
778
914
  ...ctx.isMapLibre ? {} : { slot: props.slot || "" },
779
915
  metadata: {
780
916
  smg: { beforeType: props.beforeType, beforeId: props.beforeId }
@@ -789,13 +925,13 @@ var Layer = (props) => {
789
925
  if (props[item]) {
790
926
  const event = item.slice(2).toLowerCase();
791
927
  ctx.map.on(event, layerId, (evt) => {
792
- evt.clickOnLayer = true;
928
+ if (event === "click") evt.clickOnLayer = true;
793
929
  props[item](evt);
794
930
  ctx.map.debugEvents && debug(`Layer '${event}' event on '${layerId}':`, evt);
795
931
  });
796
932
  }
797
933
  });
798
- createEffect4((prev) => {
934
+ createEffect3((prev) => {
799
935
  ctx.themeVersion;
800
936
  const style = updateStyle(props.style, ctx.constants, debug);
801
937
  if (style === prev) return;
@@ -814,7 +950,7 @@ var Layer = (props) => {
814
950
  debug("Update Layer Style:", layerId);
815
951
  return style;
816
952
  }, updateStyle(props.style, ctx.constants, debug));
817
- createEffect4((prev) => {
953
+ createEffect3((prev) => {
818
954
  if (props.visible === prev) return;
819
955
  ctx.map.setLayoutProperty(
820
956
  layerId,
@@ -825,19 +961,20 @@ var Layer = (props) => {
825
961
  debug(`Update Visibility (${layerId}):`, props.visible.toString());
826
962
  return props.visible;
827
963
  }, props.visible);
828
- createEffect4((prev) => {
964
+ createEffect3((prev) => {
829
965
  if (getBeforeId() === prev) return prev;
830
966
  ctx.map.moveLayer(layerId, getBeforeId());
831
967
  debug(`Update Layer Z-Index (${layerId}):`, getBeforeId());
832
968
  return getBeforeId();
833
969
  }, getBeforeId());
834
- createEffect4(async () => {
835
- if (!props.filter) return;
836
- !ctx.map.isStyleLoaded() && await ctx.map.once("styledata");
837
- ctx.map.setFilter(layerId, props.filter);
970
+ createEffect3((prev) => {
971
+ if (props.filter === prev) return prev;
972
+ if (prev === void 0 && !props.filter) return prev;
973
+ ctx.map.setFilter(layerId, props.filter ?? null);
838
974
  debug(`Update Filter (${layerId}):`, props.filter);
839
- });
840
- createEffect4(async () => {
975
+ return props.filter;
976
+ }, void 0);
977
+ createEffect3(async () => {
841
978
  if (!props.featureState || props.featureState.id === null) return;
842
979
  !ctx.map.isStyleLoaded() && await ctx.map.once("styledata");
843
980
  ctx.map.removeFeatureState({
@@ -853,6 +990,52 @@ var Layer = (props) => {
853
990
  props.featureState.state
854
991
  );
855
992
  });
993
+ createEffect3(() => {
994
+ if (!props.pulse) return;
995
+ const configs = props.pulse === true ? [{}] : Array.isArray(props.pulse) ? props.pulse : [props.pulse];
996
+ const resolved = configs.map((config) => {
997
+ const { property, from, to, duration, waveform } = {
998
+ ...PULSE_DEFAULTS,
999
+ ...config
1000
+ };
1001
+ const isColor = typeof from === "string" || typeof to === "string";
1002
+ const [a, b] = waveform === "in" ? [to, from] : [from, to];
1003
+ return {
1004
+ property,
1005
+ duration,
1006
+ waveform,
1007
+ isColor,
1008
+ fromNum: isColor ? 0 : a,
1009
+ toNum: isColor ? 0 : b,
1010
+ fromRgba: isColor ? toRgbaComponents(a) : null,
1011
+ toRgba: isColor ? toRgbaComponents(b) : null
1012
+ };
1013
+ });
1014
+ resolved.forEach((cfg) => {
1015
+ ctx.map.setPaintProperty(
1016
+ layerId,
1017
+ `${cfg.property}-transition`,
1018
+ { duration: 0, delay: 0 },
1019
+ { validate: false }
1020
+ );
1021
+ });
1022
+ const start = performance.now();
1023
+ let frameId;
1024
+ const loop = (now) => {
1025
+ const elapsed = now - start;
1026
+ resolved.forEach((cfg) => {
1027
+ const t = pulseShape(elapsed, cfg.duration, cfg.waveform);
1028
+ const value = cfg.isColor ? lerpColor(cfg.fromRgba, cfg.toRgba, t) : cfg.fromNum + (cfg.toNum - cfg.fromNum) * t;
1029
+ ctx.map.setPaintProperty(layerId, cfg.property, value, {
1030
+ validate: false
1031
+ });
1032
+ });
1033
+ frameId = window.requestAnimationFrame(loop);
1034
+ };
1035
+ frameId = window.requestAnimationFrame(loop);
1036
+ debug(`Start Pulse (${layerId}):`, props.pulse);
1037
+ onCleanup3(() => window.cancelAnimationFrame(frameId));
1038
+ });
856
1039
  onCleanup3(() => ctx.map?.getLayer(layerId) && ctx.map?.removeLayer(layerId));
857
1040
  return props.children;
858
1041
  };
@@ -863,11 +1046,11 @@ import {
863
1046
  createUniqueId as createUniqueId3,
864
1047
  createSignal as createSignal2,
865
1048
  onMount as onMount2,
866
- createContext as createContext3,
867
- useContext as useContext3
1049
+ createContext as createContext2,
1050
+ useContext as useContext2
868
1051
  } from "solid-js";
869
- var LayerContext = createContext3();
870
- var useScene = () => useContext3(LayerContext);
1052
+ var LayerContext = createContext2();
1053
+ var useScene = () => useContext2(LayerContext);
871
1054
  var Layer3D = (props) => {
872
1055
  const [ctx] = useMapContext();
873
1056
  const [scene, setScene] = createSignal2(null);
@@ -1007,10 +1190,10 @@ var Layer3D = (props) => {
1007
1190
  };
1008
1191
 
1009
1192
  // src/components/Atmosphere/index.tsx
1010
- import { onCleanup as onCleanup5, createEffect as createEffect5 } from "solid-js";
1193
+ import { onCleanup as onCleanup5, createEffect as createEffect4 } from "solid-js";
1011
1194
  var Atmosphere = (props) => {
1012
1195
  const [ctx] = useMapContext();
1013
- createEffect5(() => {
1196
+ createEffect4(() => {
1014
1197
  if (ctx.isMapLibre) ctx.map.setSky?.(props.style || {});
1015
1198
  else ctx.map.setFog(props.style || {});
1016
1199
  });
@@ -1026,7 +1209,7 @@ var Atmosphere = (props) => {
1026
1209
 
1027
1210
  // src/components/Terrain/index.tsx
1028
1211
  import {
1029
- createEffect as createEffect6,
1212
+ createEffect as createEffect5,
1030
1213
  createUniqueId as createUniqueId4,
1031
1214
  onCleanup as onCleanup6
1032
1215
  } from "solid-js";
@@ -1043,7 +1226,7 @@ var Terrain = (props) => {
1043
1226
  });
1044
1227
  ctx.map.sourceIdList.push(sourceId);
1045
1228
  }
1046
- createEffect6(() => {
1229
+ createEffect5(() => {
1047
1230
  ctx.map.setTerrain({
1048
1231
  exaggeration: props.exaggeration || 1,
1049
1232
  source: props?.source || sourceId
@@ -1054,13 +1237,13 @@ var Terrain = (props) => {
1054
1237
  };
1055
1238
 
1056
1239
  // src/components/Control/index.tsx
1057
- import { createSignal as createSignal3, createEffect as createEffect8, splitProps, untrack } from "solid-js";
1240
+ import { createSignal as createSignal3, createEffect as createEffect7, splitProps, untrack } from "solid-js";
1058
1241
 
1059
1242
  // src/lib/createMapControl.ts
1060
- import { createEffect as createEffect7, onCleanup as onCleanup7 } from "solid-js";
1243
+ import { createEffect as createEffect6, onCleanup as onCleanup7 } from "solid-js";
1061
1244
  var useControlPosition = (control, position) => {
1062
1245
  const [ctx] = useMapContext();
1063
- createEffect7(() => {
1246
+ createEffect6(() => {
1064
1247
  const c = control();
1065
1248
  const pos = position?.();
1066
1249
  if (!c) return;
@@ -1087,7 +1270,7 @@ var Control = (props) => {
1087
1270
  ["logo", ctx.mapLib.LogoControl],
1088
1271
  ["terrain", ctx.mapLib.TerrainControl]
1089
1272
  ]);
1090
- createEffect8(() => {
1273
+ createEffect7(() => {
1091
1274
  untrack(
1092
1275
  () => control() && ctx.map.hasControl(control()) && ctx.map.removeControl(control())
1093
1276
  );
@@ -1100,7 +1283,7 @@ var Control = (props) => {
1100
1283
  };
1101
1284
 
1102
1285
  // src/components/Marker/index.tsx
1103
- import { onCleanup as onCleanup8, createEffect as createEffect9, splitProps as splitProps2 } from "solid-js";
1286
+ import { onCleanup as onCleanup8, createEffect as createEffect8, splitProps as splitProps2 } from "solid-js";
1104
1287
  var Marker = (props) => {
1105
1288
  if (!props.lngLat) throw new Error("Marker - lngLat is required");
1106
1289
  const [update, create_popup, create_marker] = splitProps2(
@@ -1112,7 +1295,7 @@ var Marker = (props) => {
1112
1295
  let marker = null;
1113
1296
  let popup = null;
1114
1297
  const LNGLAT = update.lngLat;
1115
- createEffect9(() => {
1298
+ createEffect8(() => {
1116
1299
  if (!ctx.map) return;
1117
1300
  popup?.remove();
1118
1301
  popup = new ctx.mapLib.Popup({
@@ -1120,31 +1303,31 @@ var Marker = (props) => {
1120
1303
  focusAfterOpen: false,
1121
1304
  ...create_popup.popup
1122
1305
  }).on("open", () => create_popup.onOpen?.()).on("close", () => create_popup.onClose?.());
1123
- createEffect9(() => {
1306
+ createEffect8(() => {
1124
1307
  if (update.children === void 0) return;
1125
1308
  typeof update.children === "string" ? popup?.setHTML(update.children) : popup?.setDOMContent(update.children);
1126
1309
  marker?.setPopup(popup);
1127
1310
  });
1128
- createEffect9(
1311
+ createEffect8(
1129
1312
  () => update.showPopup !== popup?.isOpen() && marker?.togglePopup()
1130
1313
  );
1131
1314
  });
1132
- createEffect9(() => {
1315
+ createEffect8(() => {
1133
1316
  if (!ctx.map) return;
1134
1317
  marker?.remove();
1135
1318
  marker = new ctx.mapLib.Marker(create_marker.options).on("dragstart", () => create_marker.onDragStart?.()).on("dragend", () => create_marker.onDragEnd?.()).on("drag", () => create_marker.onDrag?.(marker?.getLngLat().toArray())).setPopup(popup).setLngLat(LNGLAT).addTo(ctx.map);
1136
- createEffect9(
1319
+ createEffect8(
1137
1320
  () => update.showPopup !== popup?.isOpen() && marker?.togglePopup()
1138
1321
  );
1139
1322
  });
1140
- createEffect9(() => marker?.setLngLat(update.lngLat));
1141
- createEffect9(() => marker?.setDraggable(update.draggable));
1323
+ createEffect8(() => marker?.setLngLat(update.lngLat));
1324
+ createEffect8(() => marker?.setDraggable(update.draggable));
1142
1325
  onCleanup8(() => marker?.remove());
1143
1326
  return null;
1144
1327
  };
1145
1328
 
1146
1329
  // src/components/Popup/index.tsx
1147
- import { onCleanup as onCleanup9, createEffect as createEffect10, splitProps as splitProps3 } from "solid-js";
1330
+ import { onCleanup as onCleanup9, createEffect as createEffect9, splitProps as splitProps3 } from "solid-js";
1148
1331
  var Popup = (props) => {
1149
1332
  if (!props.trackPointer && !props.lngLat)
1150
1333
  throw new Error("Popup - lngLat or trackPointer is required");
@@ -1155,7 +1338,7 @@ var Popup = (props) => {
1155
1338
  ]);
1156
1339
  const [ctx] = useMapContext();
1157
1340
  let popup;
1158
- createEffect10(() => {
1341
+ createEffect9(() => {
1159
1342
  if (!ctx.map) return;
1160
1343
  popup?.remove();
1161
1344
  popup = new ctx.mapLib.Popup({
@@ -1163,10 +1346,10 @@ var Popup = (props) => {
1163
1346
  focusAfterOpen: false,
1164
1347
  ...create.options
1165
1348
  }).on("open", () => create.onOpen?.()).on("close", () => create.onClose?.()).addTo(ctx.map);
1166
- createEffect10(
1349
+ createEffect9(
1167
1350
  () => update.trackPointer ? popup.trackPointer() : popup.setLngLat(update.lngLat)
1168
1351
  );
1169
- createEffect10(
1352
+ createEffect9(
1170
1353
  () => typeof update.children === "string" ? popup.setHTML(update.children) : popup.setDOMContent(update.children)
1171
1354
  );
1172
1355
  });
@@ -1178,27 +1361,162 @@ var Popup = (props) => {
1178
1361
  import {
1179
1362
  onCleanup as onCleanup10,
1180
1363
  createSignal as createSignal4,
1181
- createEffect as createEffect11,
1364
+ createEffect as createEffect10,
1182
1365
  untrack as untrack2
1183
1366
  } from "solid-js";
1367
+
1368
+ // src/components/Image/sdf.ts
1369
+ var INF = 1e20;
1370
+ var ALPHA_TABLE = new Float64Array(256);
1371
+ for (let i = 0; i < 256; i++) {
1372
+ const d = 0.5 - Math.pow(i / 255, 1 / 2.2);
1373
+ ALPHA_TABLE[i] = d * Math.abs(d);
1374
+ }
1375
+ ALPHA_TABLE[255] = -INF;
1376
+ function edt1d(grid, offset, stride, length2, f, v, z) {
1377
+ v[0] = 0;
1378
+ z[0] = -INF;
1379
+ z[1] = INF;
1380
+ f[0] = grid[offset];
1381
+ for (let q = 1, k = 0, s = 0; q < length2; q++) {
1382
+ f[q] = grid[offset + q * stride];
1383
+ const q2 = q * q;
1384
+ do {
1385
+ const r = v[k];
1386
+ s = (f[q] - f[r] + q2 - r * r) / (q - r) / 2;
1387
+ } while (s <= z[k] && --k > -1);
1388
+ k++;
1389
+ v[k] = q;
1390
+ z[k] = s;
1391
+ z[k + 1] = INF;
1392
+ }
1393
+ for (let q = 0, k = 0; q < length2; q++) {
1394
+ while (z[k + 1] < q) k++;
1395
+ const r = v[k];
1396
+ const qr = q - r;
1397
+ grid[offset + q * stride] = f[r] + qr * qr;
1398
+ }
1399
+ }
1400
+ function edt(data, width, height, f, v, z) {
1401
+ for (let x = 0; x < width; x++) edt1d(data, x, width, height, f, v, z);
1402
+ for (let y = 0; y < height; y++) edt1d(data, y * width, 1, width, f, v, z);
1403
+ }
1404
+ var sdfPadding = ({ radius = 8 } = {}) => Math.ceil(radius);
1405
+ function toSDF(imageData, { radius = 8, cutoff = 0.25 } = {}) {
1406
+ const { width, height } = imageData;
1407
+ const len = width * height;
1408
+ const gridOuter = new Float64Array(len).fill(INF);
1409
+ const gridInner = new Float64Array(len);
1410
+ const size = Math.max(width, height);
1411
+ const f = new Float64Array(size);
1412
+ const v = new Uint16Array(size);
1413
+ const z = new Float64Array(size + 1);
1414
+ const src = imageData.data;
1415
+ for (let i = 0, j = 3; i < len; i++, j += 4) {
1416
+ const a = src[j];
1417
+ if (a === 0) continue;
1418
+ const t = ALPHA_TABLE[a];
1419
+ gridOuter[i] = Math.max(0, t);
1420
+ gridInner[i] = Math.max(0, -t);
1421
+ }
1422
+ edt(gridOuter, width, height, f, v, z);
1423
+ edt(gridInner, width, height, f, v, z);
1424
+ const scale = 255 / radius;
1425
+ const base = 255 * (1 - cutoff);
1426
+ const out = new Uint8ClampedArray(len * 4);
1427
+ for (let i = 0, j = 0; i < len; i++, j += 4) {
1428
+ const d = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]);
1429
+ const value = Math.max(0, Math.min(255, Math.round(base - scale * d)));
1430
+ out[j] = out[j + 1] = out[j + 2] = 255;
1431
+ out[j + 3] = value;
1432
+ }
1433
+ return { width, height, data: out };
1434
+ }
1435
+
1436
+ // src/components/Image/shapes.ts
1184
1437
  var PATTERN = {
1185
1438
  diagonal_l: { size: 20, path: "M20 0 0 20M-10 10 10-10M10 30 30 10" },
1186
1439
  diagonal_r: { size: 20, path: "M0 0 20 20M30 10 10-10M10 30-10 10" },
1187
1440
  horizontal: { size: 14, path: "M7 0V20" },
1188
1441
  vertical: { size: 14, path: "M0 7H20" },
1442
+ // A single centered "+" per tile, tiling into a repeating grid of crosses — not to be
1443
+ // confused with `SYMBOL.cross` (a single bold "+" icon) below.
1189
1444
  cross: { size: 18, path: "M9 0V18M0 9H18" },
1190
1445
  hash: { size: 30, path: "M15 0 30 15 15 30 0 15Z" },
1191
1446
  chevron_h: { size: 20, path: "M-5 5 0 10 10 0 20 10 25 5M0 30 10 20 20 30" },
1192
1447
  chevron_v: { size: 20, path: "M5-5 10 0 0 10 10 20 5 25M25 15 20 10 25 5" },
1193
1448
  square: { size: 20, path: "M8 8H12V12H8Z", fill: true },
1194
1449
  hex: { size: 50, path: "M0 0V50L50 25ZM50 0V50L0 25ZM25 0V50" },
1450
+ // Two arcs swept almost all the way around (`A2.5 2.5 0 1 1 ...` to a point a hair past the
1451
+ // start) rather than a `<circle>`, since this path is later stroked/filled via `Path2D`,
1452
+ // which has no dedicated circle primitive. A single dot per tile — not to be confused with
1453
+ // `SYMBOL.circle` (a single full-bleed circle icon) below.
1195
1454
  circle: {
1196
1455
  size: 25,
1197
1456
  path: "M8 10A2.5 2.5 0 118.01 10M18 15A2.5 2.5 0 1018.01 15",
1198
1457
  fill: true
1199
- }
1458
+ },
1459
+ // `horizontal` and `vertical` combined into one crossed-line tile.
1460
+ grid: { size: 20, path: "M10 0V20M0 10H20" },
1461
+ // Two rows of offset (running-bond) bricks per tile, so the offset continues correctly
1462
+ // when tiles stack — row two's joints fall at the quarter-points, not centered, to match
1463
+ // a real brick course rather than just checkerboarding.
1464
+ brick: {
1465
+ size: 40,
1466
+ path: "M0 0H40M0 20H40M0 40H40M0 0V20M20 0V20M40 0V20M10 20V40M30 20V40"
1467
+ },
1468
+ // Two wavy horizontal lines per tile (quadratic-bezier "hump" repeated via the `T` shorthand),
1469
+ // for the wave/water fill texture common in geologic and land-cover maps.
1470
+ wave: { size: 20, path: "M0 5Q5 0 10 5T20 5M0 15Q5 10 10 15T20 15" }
1200
1471
  };
1201
1472
  var patternList = Object.keys(PATTERN);
1473
+ var svg = (path) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" stroke-linecap="round" stroke-linejoin="round">
1474
+ <path d="${path}" fill="#000" stroke="#000" stroke-width="3"/>
1475
+ </svg>`;
1476
+ var SYMBOL = {
1477
+ square: svg("M3,3 L21,3 L21,21 L3,21 Z"),
1478
+ // Two semicircular arcs sharing one center/radius, traced in the same direction both times
1479
+ // (the standard "circle via two arcs" trick). The requested `1,1` radius is far smaller than
1480
+ // half the 16-unit chord between the two points, so per the SVG spec it's scaled up to the
1481
+ // minimum radius that can still connect them (8, i.e. exactly a semicircle each) — the circle
1482
+ // ends up centered on the chord's midpoint (12,11), not on the literal "1".
1483
+ circle: svg("M 12 4 A 1 1 0 0 0 12 20 A 1 1 0 0 0 12 4 Z"),
1484
+ triangle: svg("M 12 2 L 22 19 H 2 Z"),
1485
+ diamond: svg("M 12 1 L 19 12 L 12 23 L 5 12 Z"),
1486
+ pentagon: svg("M12,3 L21,9 L17,19 L7,19 L3,9 Z"),
1487
+ hexagon: svg("M12,3 L20,7.5 L20,16.5 L12,21 L4,16.5 L4,7.5 Z"),
1488
+ // A square with corners cut at 2/7 of its side, the standard construction for a regular
1489
+ // octagon (stop-sign proportions) without resorting to trig.
1490
+ octagon: svg("M 8 3 Q 12 3.3 16 3 Q 18.3 5.7 21 8 Q 20.7 12 21 16 Q 18.3 18.3 16 21 Q 12 20.7 8 21 Q 5.8 18.2 3 16 Q 3.3 12 3 8 Q 5.7 5.7 8 3 Z"),
1491
+ cross: svg("M 10.5 3 h 3 v 7.5 h 7.5 v 3 h -7.5 v 7.5 h -3 v -7.5 h -7.5 v -3 h 7.5 Z"),
1492
+ // The same 12-point "+" as `cross` above, rotated 45° about the center.
1493
+ x: svg("M 18 4 L 20 6 L 14 12 L 20 18 L 18 20 L 12 14 L 6 20 L 4 18 L 10 12 L 4 6 L 6 4 L 12 10 Z"),
1494
+ // A regular 5-pointed star: outer vertices every 72° alternating with inner vertices every
1495
+ // 72° offset by 36° (inner radius ~40% of the outer radius, the usual ratio for a balanced star).
1496
+ star: svg("M12,3 L14,9 L20.5,9 L15.5,13 L17.5,19.5 L12,15.5 L6.5,19.5 L8.5,13 L3.5,9 L10,9 Z"),
1497
+ // Two circular lobes (centers 8.5,8 / 15.5,8, radius 4.5 apart enough to overlap) joined by
1498
+ // straight tangent lines down to a bottom point. The notch between the lobes is the two
1499
+ // circles' actual intersection point (computed, not eyeballed), so both arcs land on it
1500
+ // exactly with no seam.
1501
+ heart: svg("M 12 21 L 3 11 A 4.5 4.5 0 1 1 11 8 L 13 8 A 4.5 4.5 0 1 1 21 11 Z"),
1502
+ // A map-pin/teardrop: a circle (center 12,10, r6) with two lines tangent to it converging on a
1503
+ // point below, so the straight edges meet the round top without a kink — the tangent points
1504
+ // and angles come from `acos(r / distance-to-point)`, not eyeballing.
1505
+ pin: svg("M 12 21 L 7 11 A 6 6 0 0 1 12 2 A 6 6 0 0 1 17 11 Z"),
1506
+ // The same tangent-circle construction as `pin`, just with the circle bigger relative to how
1507
+ // far below it the point sits (r6/d11 there vs r7.5/d10.5 here) — a shorter, wider point on a
1508
+ // rounder body: "a circle with one pointy edge" rather than a full elongated marker.
1509
+ drop: svg("M 4 21 V 12 A 7.5 7.5 0 0 1 12 4 A 7.5 7.5 0 0 1 13 21 Z"),
1510
+ // A thick chevron/bracket built as a filled ribbon, not a stroked open line: the outer and
1511
+ // inner edges of a constant-width "V", offset perpendicular to each arm and mitered where the
1512
+ // arms meet (both at the outer point and the inner notch). Has to be a closed silhouette like
1513
+ // every other symbol here, since `sdf` only encodes a filled alpha shape, not a stroke.
1514
+ chevron: svg("M 8 4 L 14 12 L 8 20 H 5 L 11 12 L 5 4 Z")
1515
+ };
1516
+ var symbolList = Object.keys(SYMBOL);
1517
+ var wrapSymbolPath = (d) => svg(d);
1518
+
1519
+ // src/components/Image/index.tsx
1202
1520
  var MGL_Image = (props) => {
1203
1521
  const [ctx] = useMapContext();
1204
1522
  const [size, setSize] = createSignal4({ width: 0, height: 0 });
@@ -1217,13 +1535,14 @@ var MGL_Image = (props) => {
1217
1535
  ctx.map?.hasImage(props.id) && ctx.map?.removeImage(props.id);
1218
1536
  debug("Remove Image:", props.id);
1219
1537
  });
1220
- createEffect11(() => {
1538
+ createEffect10(() => {
1221
1539
  if (!props.id) throw new Error("Image - ID is required");
1222
- if (!props.source && !props.pattern)
1223
- throw new Error("Image - Image or Pattern is required");
1540
+ if (!props.source && !props.pattern && !props.symbol)
1541
+ throw new Error("Image - Image, Pattern or Symbol is required");
1224
1542
  const pixelRatio = Math.max(1, Math.round(window.devicePixelRatio || 2));
1225
- const ops = props.pattern ? { pixelRatio, ...props.options } : props.options;
1226
- _loadImage(props.source || _createPattern(props.pattern, pixelRatio), (data, autoPixelRatio) => {
1543
+ const ops = props.pattern ? { pixelRatio, ...props.options } : props.sdf ? { ...props.options, sdf: true } : props.options;
1544
+ const symbolSource = props.symbol && (SYMBOL[props.symbol] || (props.symbol.trimStart().startsWith("<svg") ? props.symbol : wrapSymbolPath(props.symbol)));
1545
+ _loadImage(props.source || symbolSource || _createPattern(props.pattern, pixelRatio), (data, autoPixelRatio) => {
1227
1546
  const finalOps = autoPixelRatio ? { ...ops, pixelRatio: autoPixelRatio } : ops;
1228
1547
  const { width, height } = data;
1229
1548
  if (ctx.map && !ctx.map.hasImage(props.id))
@@ -1241,6 +1560,7 @@ var MGL_Image = (props) => {
1241
1560
  });
1242
1561
  });
1243
1562
  const _loadImage = (image, callback) => {
1563
+ const sdfOpts = props.sdf ? typeof props.sdf === "object" ? props.sdf : {} : null;
1244
1564
  if (typeof image == "string" && image?.trimStart().startsWith("<svg")) {
1245
1565
  image = new DOMParser().parseFromString(image, "image/svg+xml").childNodes[0];
1246
1566
  }
@@ -1263,24 +1583,43 @@ var MGL_Image = (props) => {
1263
1583
  const img = new Image();
1264
1584
  img.crossOrigin = "Anonymous";
1265
1585
  img.onload = () => {
1266
- const floorScale = Math.max(1, 50 / Math.min(img.width, img.height));
1586
+ const minAxis = Math.min(img.width, img.height);
1587
+ const floorScale = minAxis > 0 ? Math.max(1, 50 / minAxis) : 1;
1267
1588
  const pixelRatio = props.options?.pixelRatio ?? Math.max(1, Math.round(window.devicePixelRatio || 1));
1268
1589
  const scale = floorScale * pixelRatio;
1590
+ const padding = sdfOpts ? sdfPadding(sdfOpts) * scale : 0;
1591
+ const drawWidth = Math.round(img.width * scale);
1592
+ const drawHeight = Math.round(img.height * scale);
1269
1593
  const canvas = document.createElement("canvas");
1270
- canvas.width = Math.round(img.width * scale);
1271
- canvas.height = Math.round(img.height * scale);
1594
+ canvas.width = drawWidth + padding * 2;
1595
+ canvas.height = drawHeight + padding * 2;
1272
1596
  const ctx2 = canvas.getContext("2d");
1273
1597
  ctx2.imageSmoothingEnabled = true;
1274
- ctx2.drawImage(img, 0, 0, canvas.width, canvas.height);
1598
+ ctx2.drawImage(img, padding, padding, drawWidth, drawHeight);
1599
+ const imageData2 = ctx2.getImageData(0, 0, canvas.width, canvas.height);
1275
1600
  return callback(
1276
- ctx2.getImageData(0, 0, canvas.width, canvas.height),
1277
- pixelRatio
1601
+ sdfOpts ? toSDF(imageData2, sdfOpts) : imageData2,
1602
+ scale
1278
1603
  );
1279
1604
  };
1280
1605
  img.onerror = (event) => console.error(`[MapGL] Image "${props.id}" failed to load:`, event);
1281
1606
  img.src = image.trimStart().startsWith("<svg") ? "data:image/svg+xml;charset=utf-8," + encodeURIComponent(image) : image;
1282
1607
  } else {
1283
- return callback(imageData);
1608
+ if (!sdfOpts) return callback(imageData);
1609
+ const { width, height } = imageData;
1610
+ const padding = sdfPadding(sdfOpts);
1611
+ const canvas = document.createElement("canvas");
1612
+ canvas.width = width + padding * 2;
1613
+ canvas.height = height + padding * 2;
1614
+ const c = canvas.getContext("2d");
1615
+ c.imageSmoothingEnabled = true;
1616
+ if ("data" in imageData) {
1617
+ c.putImageData(imageData, padding, padding);
1618
+ } else {
1619
+ c.drawImage(imageData, padding, padding, width, height);
1620
+ }
1621
+ const pixels = c.getImageData(0, 0, canvas.width, canvas.height);
1622
+ return callback(toSDF(pixels, sdfOpts));
1284
1623
  }
1285
1624
  });
1286
1625
  };
@@ -1301,7 +1640,7 @@ var MGL_Image = (props) => {
1301
1640
  this.ctx.fillStyle = pattern.background || "transparent";
1302
1641
  this.ctx.fillRect(0, 0, p.size, p.size);
1303
1642
  this.ctx.strokeStyle = this.ctx.fillStyle = pattern.color || "black";
1304
- this.ctx.lineWidth = pattern.lineWith || 1;
1643
+ this.ctx.lineWidth = pattern.lineWidth || 1;
1305
1644
  this.ctx.stroke(new Path2D(p.path));
1306
1645
  if (p.fill) this.ctx.fill(new Path2D(p.path));
1307
1646
  this.data = this.ctx.getImageData(0, 0, this.width, this.height).data;
@@ -1313,7 +1652,7 @@ var MGL_Image = (props) => {
1313
1652
  };
1314
1653
 
1315
1654
  // src/components/Camera/index.tsx
1316
- import { createEffect as createEffect12, onCleanup as onCleanup11, createSignal as createSignal5 } from "solid-js";
1655
+ import { createEffect as createEffect11, onCleanup as onCleanup11, createSignal as createSignal5 } from "solid-js";
1317
1656
  var lerp = (a, b, t) => a.map((_, idx) => (1 - t) * a[idx] + t * b[idx]);
1318
1657
  var slerp = (a, b, t) => {
1319
1658
  const dotProduct = a.map((_, idx) => a[idx] * b[idx]).reduce((m, n) => m + n);
@@ -1360,7 +1699,7 @@ var Camera = (props) => {
1360
1699
  isReverse = !isReverse;
1361
1700
  animationTime = isReverse ? animationTime - 1e-3 : animationTime + 1e-3;
1362
1701
  };
1363
- createEffect12(() => {
1702
+ createEffect11(() => {
1364
1703
  props.translate && window.requestAnimationFrame(frame);
1365
1704
  });
1366
1705
  const options = { duration: 1e3, easing: (n) => n };
@@ -1395,7 +1734,7 @@ var Camera = (props) => {
1395
1734
  const onEnd = () => props.rotateGlobe ? rotateGlobe() : rotateViewport();
1396
1735
  ctx.map.on("moveend", onEnd).on("dragend", onEnd);
1397
1736
  let originalCenter;
1398
- createEffect12(() => {
1737
+ createEffect11(() => {
1399
1738
  if (props.rotateGlobe == void 0) return;
1400
1739
  if (props.rotateGlobe) {
1401
1740
  originalCenter = ctx.map.getCenter();
@@ -1404,7 +1743,7 @@ var Camera = (props) => {
1404
1743
  props.resetWhenStopped ? ctx.map.stop().easeTo({ center: originalCenter }) : ctx.map.stop();
1405
1744
  }
1406
1745
  });
1407
- createEffect12(() => {
1746
+ createEffect11(() => {
1408
1747
  if (props.rotateViewport == void 0) return;
1409
1748
  props.rotateViewport ? rotateViewport() : props.resetWhenStopped ? ctx.map.stop().resetNorthPitch() : ctx.map.stop();
1410
1749
  });
@@ -1413,10 +1752,10 @@ var Camera = (props) => {
1413
1752
  };
1414
1753
 
1415
1754
  // src/components/Light/index.tsx
1416
- import { onCleanup as onCleanup12, createEffect as createEffect13 } from "solid-js";
1755
+ import { onCleanup as onCleanup12, createEffect as createEffect12 } from "solid-js";
1417
1756
  var Light = (props) => {
1418
1757
  const [ctx] = useMapContext();
1419
- createEffect13(() => ctx.map.setLight(props.style || {}));
1758
+ createEffect12(() => ctx.map.setLight(props.style || {}));
1420
1759
  onCleanup12(() => ctx.map?.setLight(null));
1421
1760
  return null;
1422
1761
  };
@@ -1452,11 +1791,105 @@ MultiPointMode.toDisplayFeatures = function(state, geojson, display) {
1452
1791
  };
1453
1792
  var multi_point_default = MultiPointMode;
1454
1793
 
1794
+ // src/components/Draw/modes/geo.ts
1795
+ var EARTH_RADIUS_KM = 6371.0088;
1796
+ var KM_PER_MILE = 1.609344;
1797
+ var EARTH_RADIUS_M = 6378137;
1798
+ function toRadians(deg) {
1799
+ return deg * Math.PI / 180;
1800
+ }
1801
+ function toDegrees(rad) {
1802
+ return rad * 180 / Math.PI;
1803
+ }
1804
+ function distance(from, to) {
1805
+ const dLat = toRadians(to[1] - from[1]);
1806
+ const dLng = toRadians(to[0] - from[0]);
1807
+ const lat1 = toRadians(from[1]);
1808
+ const lat2 = toRadians(to[1]);
1809
+ const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLng / 2) ** 2;
1810
+ return EARTH_RADIUS_KM * 2 * Math.asin(Math.sqrt(a));
1811
+ }
1812
+ function length(coordinates, units = "kilometers") {
1813
+ let total = 0;
1814
+ for (let i = 1; i < coordinates.length; i++) {
1815
+ total += distance(coordinates[i - 1], coordinates[i]);
1816
+ }
1817
+ return units === "miles" ? total / KM_PER_MILE : total;
1818
+ }
1819
+ function midpoint(from, to) {
1820
+ const lat1 = toRadians(from[1]);
1821
+ const lon1 = toRadians(from[0]);
1822
+ const lat2 = toRadians(to[1]);
1823
+ const dLon = toRadians(to[0] - from[0]);
1824
+ const bx = Math.cos(lat2) * Math.cos(dLon);
1825
+ const by = Math.cos(lat2) * Math.sin(dLon);
1826
+ const lat3 = Math.atan2(
1827
+ Math.sin(lat1) + Math.sin(lat2),
1828
+ Math.sqrt((Math.cos(lat1) + bx) ** 2 + by ** 2)
1829
+ );
1830
+ const lon3 = lon1 + Math.atan2(by, Math.cos(lat1) + bx);
1831
+ return [toDegrees(lon3), toDegrees(lat3)];
1832
+ }
1833
+ function ringArea(ring) {
1834
+ const n = ring.length;
1835
+ if (n <= 2) return 0;
1836
+ let total = 0;
1837
+ for (let i = 0; i < n; i++) {
1838
+ let lower;
1839
+ let middle;
1840
+ let upper;
1841
+ if (i === n - 2) {
1842
+ lower = n - 2;
1843
+ middle = n - 1;
1844
+ upper = 0;
1845
+ } else if (i === n - 1) {
1846
+ lower = n - 1;
1847
+ middle = 0;
1848
+ upper = 1;
1849
+ } else {
1850
+ lower = i;
1851
+ middle = i + 1;
1852
+ upper = i + 2;
1853
+ }
1854
+ total += (toRadians(ring[upper][0]) - toRadians(ring[lower][0])) * Math.sin(toRadians(ring[middle][1]));
1855
+ }
1856
+ return total * EARTH_RADIUS_M * EARTH_RADIUS_M / 2;
1857
+ }
1858
+ function area(rings) {
1859
+ return rings.reduce((total, ring, i) => {
1860
+ const ringTotal = Math.abs(ringArea(ring));
1861
+ return i === 0 ? total + ringTotal : total - ringTotal;
1862
+ }, 0);
1863
+ }
1864
+ function centerOfMass(rings) {
1865
+ const ring = rings[0];
1866
+ let meanX = 0;
1867
+ let meanY = 0;
1868
+ for (const [x, y] of ring) {
1869
+ meanX += x;
1870
+ meanY += y;
1871
+ }
1872
+ meanX /= ring.length;
1873
+ meanY /= ring.length;
1874
+ let xSum = 0;
1875
+ let ySum = 0;
1876
+ let areaSum = 0;
1877
+ for (let i = 0; i < ring.length - 1; i++) {
1878
+ const x1 = ring[i][0] - meanX;
1879
+ const y1 = ring[i][1] - meanY;
1880
+ const x2 = ring[i + 1][0] - meanX;
1881
+ const y2 = ring[i + 1][1] - meanY;
1882
+ const cross = x1 * y2 - x2 * y1;
1883
+ xSum += (x1 + x2) * cross;
1884
+ ySum += (y1 + y2) * cross;
1885
+ areaSum += cross;
1886
+ }
1887
+ if (areaSum === 0) return [meanX, meanY];
1888
+ const factor = 1 / (3 * areaSum);
1889
+ return [meanX + xSum * factor, meanY + ySum * factor];
1890
+ }
1891
+
1455
1892
  // src/components/Draw/modes/measurements.ts
1456
- import length from "@turf/length";
1457
- import area from "@turf/area";
1458
- import midpoint from "@turf/midpoint";
1459
- import { centerOfMass } from "@turf/center-of-mass";
1460
1893
  var getCoords = (coords) => {
1461
1894
  return new Intl.NumberFormat(void 0, {
1462
1895
  maximumFractionDigits: 5
@@ -1466,17 +1899,8 @@ var getCoords = (coords) => {
1466
1899
  };
1467
1900
  var getLength = (coordinates, meta) => {
1468
1901
  const lengthValue = length(
1469
- {
1470
- type: "Feature",
1471
- properties: {},
1472
- geometry: {
1473
- type: "LineString",
1474
- coordinates
1475
- }
1476
- },
1477
- {
1478
- units: window.navigator.language === "en-US" ? "miles" : "kilometers"
1479
- }
1902
+ coordinates,
1903
+ window.navigator.language === "en-US" ? "miles" : "kilometers"
1480
1904
  );
1481
1905
  const label = window.navigator.language === "en-US" ? new Intl.NumberFormat(void 0, {
1482
1906
  style: "unit",
@@ -1496,19 +1920,12 @@ var getLength = (coordinates, meta) => {
1496
1920
  },
1497
1921
  geometry: {
1498
1922
  type: "Point",
1499
- coordinates: midpoint(coordinates[0], coordinates[1]).geometry.coordinates
1923
+ coordinates: midpoint(coordinates[0], coordinates[1])
1500
1924
  }
1501
1925
  };
1502
1926
  };
1503
1927
  var getArea = (coordinates, meta) => {
1504
- const areaValue = area({
1505
- type: "Feature",
1506
- properties: {},
1507
- geometry: {
1508
- type: "Polygon",
1509
- coordinates
1510
- }
1511
- });
1928
+ const areaValue = area(coordinates);
1512
1929
  const areaLabel = window.navigator.language === "en-US" ? new Intl.NumberFormat(void 0, {
1513
1930
  style: "unit",
1514
1931
  maximumFractionDigits: 2,
@@ -1527,10 +1944,10 @@ var getArea = (coordinates, meta) => {
1527
1944
  value: areaLabel,
1528
1945
  ...meta
1529
1946
  },
1530
- geometry: centerOfMass({
1531
- type: "Polygon",
1532
- coordinates
1533
- }).geometry
1947
+ geometry: {
1948
+ type: "Point",
1949
+ coordinates: centerOfMass(coordinates)
1950
+ }
1534
1951
  };
1535
1952
  };
1536
1953
 
@@ -1624,7 +2041,6 @@ var polygon_default = (lib) => {
1624
2041
  };
1625
2042
 
1626
2043
  // src/components/Draw/modes/radius.ts
1627
- import length2 from "@turf/length";
1628
2044
  function createVertex(parentId, coordinates, path, selected) {
1629
2045
  return {
1630
2046
  type: "Feature",
@@ -1707,7 +2123,7 @@ var RadiusMode = (lib) => ({
1707
2123
  state.line.removeCoordinate("0");
1708
2124
  if (state.line.isValid()) {
1709
2125
  const lineGeoJson = state.line.toGeoJSON();
1710
- const radius = (length2(lineGeoJson) * 1e3).toFixed(1);
2126
+ const radius = (length(lineGeoJson.geometry.coordinates) * 1e3).toFixed(1);
1711
2127
  const center = lineGeoJson.geometry.coordinates[0];
1712
2128
  this.deleteFeature([state.line.id], { silent: true });
1713
2129
  const point = this.newFeature({
@@ -1747,7 +2163,7 @@ var RadiusMode = (lib) => ({
1747
2163
  display(
1748
2164
  createGeoJSONCircle(
1749
2165
  geojson.geometry.coordinates[0],
1750
- length2(geojson),
2166
+ length(geojson.geometry.coordinates),
1751
2167
  state.line.id
1752
2168
  )
1753
2169
  );
@@ -2373,7 +2789,7 @@ var Draw = (props) => {
2373
2789
  };
2374
2790
 
2375
2791
  // src/components/DeckOverlay/index.tsx
2376
- import { createEffect as createEffect14 } from "solid-js";
2792
+ import { createEffect as createEffect13 } from "solid-js";
2377
2793
  var DeckOverlay = (props) => {
2378
2794
  const [ctx] = useMapContext();
2379
2795
  let overlay;
@@ -2382,7 +2798,7 @@ var DeckOverlay = (props) => {
2382
2798
  return overlay;
2383
2799
  };
2384
2800
  useControlPosition(control);
2385
- createEffect14(() => overlay?.setProps(props.props || {}));
2801
+ createEffect13(() => overlay?.setProps(props.props || {}));
2386
2802
  return null;
2387
2803
  };
2388
2804
  export {
@@ -2401,8 +2817,13 @@ export {
2401
2817
  Source,
2402
2818
  Terrain,
2403
2819
  MapGL as default,
2820
+ disableRasterFade,
2404
2821
  patternList,
2822
+ settleAfterIdle,
2823
+ symbolList,
2824
+ toSDF,
2405
2825
  useMapContext,
2406
2826
  useScene,
2407
- useSourceId
2827
+ useSourceId,
2828
+ waitForIdleAndSettle
2408
2829
  };