@guardian/interactive-component-library 0.1.0-alpha.57 → 0.1.0-alpha.59

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.
@@ -327,9 +327,9 @@
327
327
  labelType === LabelType.hanging && !hideLabels && hangingLabelConfig.map((config, i) => renderLabel(config, i))
328
328
  ] });
329
329
  }
330
- const svg$8 = "_svg_ihy3w_6";
331
- const previous = "_previous_ihy3w_12";
332
- const next = "_next_ihy3w_16";
330
+ const svg$8 = "_svg_8bh9o_6";
331
+ const previous = "_previous_8bh9o_12";
332
+ const next = "_next_8bh9o_16";
333
333
  const defaultStyles$u = {
334
334
  svg: svg$8,
335
335
  previous,
@@ -493,25 +493,27 @@
493
493
  circle: circle$1,
494
494
  pulse
495
495
  };
496
- const CircleIcon = ({ color: color2, pulse: pulse2 = false, styles: styles2 }) => {
496
+ const CircleIcon = ({ color: color2, pulse: pulse2 = false, diameter = 11, styles: styles2 }) => {
497
497
  styles2 = mergeStyles(defaultStyles$r, styles2);
498
+ let radius = diameter / 2;
499
+ let padding = 2;
498
500
  return /* @__PURE__ */ jsxRuntime.jsx(
499
501
  "svg",
500
502
  {
501
503
  style: styles2.svg,
502
504
  fill: "none",
503
- height: "11",
504
- viewBox: "0 0 11 11",
505
- width: "11",
505
+ height: diameter + padding,
506
+ viewBox: `0 0 ${diameter + padding} ${diameter + padding}`,
507
+ width: diameter + padding,
506
508
  xmlns: "http://www.w3.org/2000/svg",
507
509
  children: /* @__PURE__ */ jsxRuntime.jsx(
508
- "rect",
510
+ "circle",
509
511
  {
510
512
  className: [styles2.circle, pulse2 && styles2.pulse].join(" "),
511
513
  style: { fill: color2 },
512
- height: "11",
513
- rx: "5.5",
514
- width: "11"
514
+ r: radius,
515
+ cx: radius + padding / 2,
516
+ cy: radius + padding / 2
515
517
  }
516
518
  )
517
519
  }
@@ -1139,6 +1141,19 @@
1139
1141
  }
1140
1142
  ) });
1141
1143
  }
1144
+ const style = {
1145
+ "aspect-ratio-box": "_aspect-ratio-box_1e5oy_1"
1146
+ };
1147
+ function AspectRatioBox({ heightAsProportionOfWidth, children }) {
1148
+ return /* @__PURE__ */ jsxRuntime.jsx(
1149
+ "div",
1150
+ {
1151
+ className: style["aspect-ratio-box"],
1152
+ style: { "--aspect-ratio": heightAsProportionOfWidth },
1153
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", width: "100%", height: "100%" }, children })
1154
+ }
1155
+ );
1156
+ }
1142
1157
  const sortAscending = (accessor) => {
1143
1158
  return (a, b) => {
1144
1159
  const valueA = a[accessor];
@@ -2732,7 +2747,7 @@
2732
2747
  };
2733
2748
  return feature;
2734
2749
  }
2735
- const MapContext = preact.createContext();
2750
+ const MapContext$1 = preact.createContext();
2736
2751
  function SVGMapProvider({
2737
2752
  id: id2,
2738
2753
  mapRef,
@@ -2811,20 +2826,20 @@
2811
2826
  findFeatureAtPoint
2812
2827
  };
2813
2828
  mapRef.current = context;
2814
- return /* @__PURE__ */ jsxRuntime.jsx(MapContext.Provider, { value: context, children });
2829
+ return /* @__PURE__ */ jsxRuntime.jsx(MapContext$1.Provider, { value: context, children });
2815
2830
  }
2816
2831
  const path = "_path_1cwd5_9";
2817
2832
  const defaultStyles$1 = {
2818
2833
  path
2819
2834
  };
2820
2835
  function CompositionBorders({ styles: styles2 }) {
2821
- const context = hooks.useContext(MapContext);
2836
+ const context = hooks.useContext(MapContext$1);
2822
2837
  const { projection } = context;
2823
2838
  styles2 = mergeStyles(defaultStyles$1, styles2);
2824
2839
  return /* @__PURE__ */ jsxRuntime.jsx("path", { className: styles2.path, d: projection.getCompositionBorders() });
2825
2840
  }
2826
2841
  function SVGRenderer({ children }) {
2827
- const { id: id2, config, size, selectedFeature, padding } = hooks.useContext(MapContext);
2842
+ const { id: id2, config, size, selectedFeature, padding } = hooks.useContext(MapContext$1);
2828
2843
  return /* @__PURE__ */ jsxRuntime.jsx(
2829
2844
  "svg",
2830
2845
  {
@@ -3262,7 +3277,7 @@
3262
3277
  zIndex = 0,
3263
3278
  styles: styles2
3264
3279
  }) {
3265
- const context = hooks.useContext(MapContext);
3280
+ const context = hooks.useContext(MapContext$1);
3266
3281
  const [searchIndex, setSearchIndex] = hooks.useState();
3267
3282
  hooks.useEffect(() => {
3268
3283
  if (!context.path) return;
@@ -3320,7 +3335,7 @@
3320
3335
  }) });
3321
3336
  }
3322
3337
  function Line({ id: id2, features, stroke = null, strokeWidth = 1, styles: styles2 }) {
3323
- const context = hooks.useContext(MapContext);
3338
+ const context = hooks.useContext(MapContext$1);
3324
3339
  const draw = (ctx, path2) => {
3325
3340
  for (const feature of features) {
3326
3341
  ctx.beginPath();
@@ -3361,7 +3376,7 @@
3361
3376
  }) });
3362
3377
  }
3363
3378
  function Prerendered({ url }) {
3364
- const context = hooks.useContext(MapContext);
3379
+ const context = hooks.useContext(MapContext$1);
3365
3380
  const { width, height } = context.contentSize;
3366
3381
  return /* @__PURE__ */ jsxRuntime.jsx("image", { width, height, href: url });
3367
3382
  }
@@ -3375,7 +3390,7 @@
3375
3390
  zIndex = 0,
3376
3391
  styles: styles2
3377
3392
  }) {
3378
- const context = hooks.useContext(MapContext);
3393
+ const context = hooks.useContext(MapContext$1);
3379
3394
  hooks.useEffect(() => {
3380
3395
  function findFeatureAtPoint(point) {
3381
3396
  for (const [index, feature] of features.entries()) {
@@ -3515,10 +3530,10 @@
3515
3530
  );
3516
3531
  const containerSize = useContainerSize(containerRef);
3517
3532
  const containerStyle = hooks.useMemo(() => {
3518
- const style = {};
3519
- if (width > 0) style["width"] = width;
3520
- if (height > 0) style["height"] = height;
3521
- return style;
3533
+ const style2 = {};
3534
+ if (width > 0) style2["width"] = width;
3535
+ if (height > 0) style2["height"] = height;
3536
+ return style2;
3522
3537
  }, [width, height]);
3523
3538
  const renderSVG = containerSize && !config.drawToCanvas;
3524
3539
  const childrenArray = Array.isArray(children) ? children : [children];
@@ -3593,6 +3608,254 @@
3593
3608
  }
3594
3609
  return size;
3595
3610
  }
3611
+ function bboxFeature(bounds) {
3612
+ const minLon = bounds[0][0];
3613
+ const minLat = bounds[0][1];
3614
+ const maxLon = bounds[1][0];
3615
+ const maxLat = bounds[1][1];
3616
+ const feature = {
3617
+ type: "Feature",
3618
+ properties: {},
3619
+ geometry: {
3620
+ coordinates: [
3621
+ [
3622
+ [minLon, maxLat],
3623
+ [maxLon, maxLat],
3624
+ [maxLon, minLat],
3625
+ [minLon, minLat],
3626
+ [minLon, maxLat]
3627
+ ]
3628
+ ],
3629
+ type: "Polygon"
3630
+ }
3631
+ };
3632
+ return feature;
3633
+ }
3634
+ const BASE_RESOLUTION = 156543.03392;
3635
+ function zoomLevelToZoomScale(zoomLevel, initialResolution) {
3636
+ const resolution = BASE_RESOLUTION / Math.pow(2, zoomLevel);
3637
+ const zoomScale = initialResolution / resolution;
3638
+ return zoomScale;
3639
+ }
3640
+ function zoomLevelForResolution(currentResolution) {
3641
+ const zoomLevel = Math.log2(BASE_RESOLUTION / currentResolution);
3642
+ return zoomLevel;
3643
+ }
3644
+ function haversineDistance(lat1, lon1, lat2, lon2) {
3645
+ const R2 = 6371e3;
3646
+ const toRadians = (degrees2) => degrees2 * Math.PI / 180;
3647
+ const dLat = toRadians(lat2 - lat1);
3648
+ const dLon = toRadians(lon2 - lon1);
3649
+ const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
3650
+ const c2 = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
3651
+ return R2 * c2;
3652
+ }
3653
+ function resolutionForExtent(extent, viewportSize) {
3654
+ const [lonMin, latMin, lonMax, latMax] = extent;
3655
+ const latMid = (latMin + latMax) / 2;
3656
+ const distance = haversineDistance(latMid, lonMin, latMid, lonMax);
3657
+ const resolution = distance / viewportSize[0];
3658
+ return resolution;
3659
+ }
3660
+ const Projection = {
3661
+ geoIdentity: d3Geo.geoIdentity(),
3662
+ geoMercator: d3Geo.geoMercator(),
3663
+ geoAlbersUS: d3Geo.geoAlbersUsa().scale(1070).translate([487.5, 305]),
3664
+ geoAlbersUKComposite: geoAlbersUk(),
3665
+ geoAlbersEngland: d3Geo.geoAlbers().center([0, 52.7]).rotate([1.1743, 0]).parallels([50, 54])
3666
+ };
3667
+ function validateGeometries(geometries) {
3668
+ if (!Array.isArray(geometries)) {
3669
+ throw new Error("geometries must be an array");
3670
+ }
3671
+ geometries.forEach((geometry) => {
3672
+ var _a;
3673
+ if (!geometry.type) {
3674
+ throw new Error("geometry must have a type");
3675
+ }
3676
+ if (!((_a = geometry.coordinates) == null ? void 0 : _a.length)) {
3677
+ throw new Error("geometry must have coordinates");
3678
+ }
3679
+ });
3680
+ }
3681
+ function generateDebugUrl(feature, convertToGeoJSON = true) {
3682
+ const featureGeoJSON = convertToGeoJSON ? feature.getGeoJSON() : feature;
3683
+ const featureCollection = {
3684
+ type: "FeatureCollection",
3685
+ features: [featureGeoJSON]
3686
+ };
3687
+ const jsonString = encodeURIComponent(JSON.stringify(featureCollection));
3688
+ return `https://geojson.io/#data=data:application/json,${jsonString}`;
3689
+ }
3690
+ class View {
3691
+ constructor({
3692
+ projection = Projection.geoIdentity,
3693
+ extent,
3694
+ minZoom = 1,
3695
+ maxZoom = 10,
3696
+ padding = { top: 0, right: 0, bottom: 0, left: 0 }
3697
+ }, debug = false) {
3698
+ this.debug = debug;
3699
+ projection.revision = 0;
3700
+ this.projection = projection;
3701
+ this.extent = extent;
3702
+ this.minZoom = minZoom;
3703
+ this.maxZoom = maxZoom;
3704
+ this._transform = d3Zoom.zoomIdentity;
3705
+ this._padding = padding;
3706
+ this._viewPortSize = [0, 0];
3707
+ this.pixelRatio = window.devicePixelRatio;
3708
+ }
3709
+ set viewPortSize(size) {
3710
+ const previousSize = this._viewPortSize;
3711
+ this._viewPortSize = size;
3712
+ if (previousSize !== size) {
3713
+ if (this.extent) {
3714
+ this.fitExtent(this.extent);
3715
+ }
3716
+ }
3717
+ }
3718
+ get viewPortSize() {
3719
+ return this._viewPortSize;
3720
+ }
3721
+ set transform(transform) {
3722
+ this._transform = transform;
3723
+ }
3724
+ get transform() {
3725
+ return new d3Zoom.ZoomTransform(
3726
+ this._transform.k,
3727
+ this._transform.x * this.pixelRatio,
3728
+ this._transform.y * this.pixelRatio
3729
+ );
3730
+ }
3731
+ // map size in pixels (i.e. scaled by device pixel ratio)
3732
+ get mapSize() {
3733
+ return scaleSize(this.viewPortSize, this.pixelRatio);
3734
+ }
3735
+ get padding() {
3736
+ return this._padding;
3737
+ }
3738
+ // padding in pixels (i.e. scaled by device pixel ratio)
3739
+ get scaledPadding() {
3740
+ const scaledPadding = { ...this._padding };
3741
+ return scalePadding(scaledPadding, this.pixelRatio);
3742
+ }
3743
+ get baseResolution() {
3744
+ const baseExtent = this.getVisibleExtent(d3Zoom.zoomIdentity, this.projection);
3745
+ const baseResolution = resolutionForExtent(baseExtent, this.viewPortSize);
3746
+ return baseResolution;
3747
+ }
3748
+ // calculates the upper and lower zoom scales
3749
+ get scaleExtent() {
3750
+ const maxScale = zoomLevelToZoomScale(this.maxZoom, this.baseResolution);
3751
+ return [1, maxScale];
3752
+ }
3753
+ setProjection(projection) {
3754
+ this.projection = projection;
3755
+ this.fitObject(bboxFeature(this.extent));
3756
+ }
3757
+ // only set the raw projection when it has already been configured with projection.fitExtent()
3758
+ setRawProjection(projection) {
3759
+ this.projection = projection;
3760
+ }
3761
+ fitExtent(extent) {
3762
+ const extentFeature = bboxFeature(extent);
3763
+ this.fitObject(extentFeature);
3764
+ if (this.debug) {
3765
+ console.log("Fit extent", extent, generateDebugUrl(extentFeature, false));
3766
+ }
3767
+ }
3768
+ fitObject(geoJSON) {
3769
+ this.projection.fitExtent(this.getMapExtent(), geoJSON);
3770
+ ++this.projection.revision;
3771
+ }
3772
+ // returns bounds relative to the viewport
3773
+ boundsForExtent(extent) {
3774
+ const SW = this.projection([extent[0], extent[1]]);
3775
+ const NE = this.projection([extent[2], extent[3]]);
3776
+ const minX = SW[0] / this.pixelRatio;
3777
+ const minY = NE[1] / this.pixelRatio;
3778
+ const maxX = NE[0] / this.pixelRatio;
3779
+ const maxY = SW[1] / this.pixelRatio;
3780
+ const width = maxX - minX;
3781
+ const height = maxY - minY;
3782
+ return [
3783
+ [minX, minY],
3784
+ [width, height]
3785
+ ];
3786
+ }
3787
+ invert(point) {
3788
+ const { projection, pixelRatio, transform } = this.getState();
3789
+ const scaledPoint = [point[0] * pixelRatio, point[1] * pixelRatio];
3790
+ const untransformedPoint = transform.invert(scaledPoint);
3791
+ const mapCoordinate = projection.invert(untransformedPoint);
3792
+ return mapCoordinate;
3793
+ }
3794
+ // bounds is defined as [[minX, minY], [maxX, maxY]]
3795
+ invertBounds(bounds) {
3796
+ const topLeft = bounds[0];
3797
+ const topRight = [bounds[1][0], bounds[0][1]];
3798
+ const bottomRight = [bounds[1][0], bounds[1][1]];
3799
+ const bottomLeft = [bounds[0][0], bounds[1][1]];
3800
+ const points = [topLeft, topRight, bottomRight, bottomLeft, topLeft];
3801
+ return points.map((d2) => this.invert(d2));
3802
+ }
3803
+ // map resolution (meters per pixel)
3804
+ getResolution() {
3805
+ return resolutionForExtent(
3806
+ this.getVisibleExtent(this.transform, this.projection),
3807
+ this.viewPortSize
3808
+ );
3809
+ }
3810
+ // map zoom level (0 = the entire world)
3811
+ getZoomLevel() {
3812
+ return zoomLevelForResolution(this.getResolution());
3813
+ }
3814
+ //
3815
+ /**
3816
+ * Function that returns the extent of the view in screen coordinates
3817
+ * The extent is defined as [[minX, minY], [maxX, maxY]]
3818
+ * @function getMapExtent
3819
+ * @returns {[[number, number], [number, number]]}
3820
+ */
3821
+ getMapExtent() {
3822
+ const mapSizeInPixels = this.mapSize;
3823
+ const paddingInPixels = this.scaledPadding;
3824
+ return [
3825
+ [paddingInPixels.left, paddingInPixels.top],
3826
+ sizeMinusPadding(mapSizeInPixels, {
3827
+ ...paddingInPixels,
3828
+ left: 0,
3829
+ top: 0
3830
+ })
3831
+ ];
3832
+ }
3833
+ // visible extent in map coordinates
3834
+ getVisibleExtent(transform, projection) {
3835
+ if (this.projection === Projection.geoIdentity) {
3836
+ const [width2, height2] = this.mapSize;
3837
+ return [0, 0, width2, height2];
3838
+ }
3839
+ const [width, height] = this.mapSize;
3840
+ const southWest = projection.invert(transform.invert([0, height]));
3841
+ const northEast = projection.invert(transform.invert([width, 0]));
3842
+ return [southWest[0], southWest[1], northEast[0], northEast[1]];
3843
+ }
3844
+ getState() {
3845
+ const transform = this.transform;
3846
+ const projection = this.projection;
3847
+ return {
3848
+ transform,
3849
+ projection,
3850
+ zoomLevel: transform.k,
3851
+ pixelRatio: this.pixelRatio,
3852
+ padding: this.padding,
3853
+ viewPortSize: this.viewPortSize,
3854
+ sizeInPixels: scaleSize(this.viewPortSize, this.pixelRatio),
3855
+ visibleExtent: this.getVisibleExtent(transform, projection)
3856
+ };
3857
+ }
3858
+ }
3596
3859
  function arrayEquals(arr1, arr2) {
3597
3860
  const len1 = arr1.length;
3598
3861
  if (len1 !== arr2.length) {
@@ -4085,11 +4348,11 @@
4085
4348
  this.map = map;
4086
4349
  this._element = document.createElement("div");
4087
4350
  this._element.className = "gv-layer-container";
4088
- const style = this._element.style;
4089
- style.position = "absolute";
4090
- style.width = "100%";
4091
- style.height = "100%";
4092
- style.zIndex = "0";
4351
+ const style2 = this._element.style;
4352
+ style2.position = "absolute";
4353
+ style2.width = "100%";
4354
+ style2.height = "100%";
4355
+ style2.zIndex = "0";
4093
4356
  const container2 = map.viewPort;
4094
4357
  container2.insertBefore(this._element, container2.firstChild || null);
4095
4358
  }
@@ -5472,18 +5735,19 @@
5472
5735
  d3Selection.selection.prototype.interrupt = selection_interrupt;
5473
5736
  d3Selection.selection.prototype.transition = selection_transition;
5474
5737
  let Map$2 = class Map {
5475
- constructor(options) {
5476
- this.options = options;
5477
- this.view = options.view;
5478
- this.target = options.target;
5738
+ constructor(config) {
5739
+ if (config.debug) {
5740
+ console.log("Map config", config);
5741
+ }
5742
+ this.options = config;
5743
+ this.view = new View(config.view, config.debug);
5744
+ this.target = config.target;
5479
5745
  this.layers = [];
5480
5746
  this.dispatcher = new Dispatcher(this);
5481
5747
  this._viewport = document.createElement("div");
5482
5748
  this._viewport.className = "gv-map";
5483
5749
  this._viewport.style.position = "relative";
5484
5750
  this._viewport.style.overflow = "hidden";
5485
- this._viewport.style.top = 0;
5486
- this._viewport.style.left = 0;
5487
5751
  this._viewport.style.width = "100%";
5488
5752
  this._viewport.style.height = "100%";
5489
5753
  this.target.appendChild(this._viewport);
@@ -5493,7 +5757,7 @@
5493
5757
  });
5494
5758
  this._resizeObserver.observe(this.target);
5495
5759
  this._viewport.addEventListener("touchmove", (event) => {
5496
- if (event.targetTouches.length < 2 && this.collaborativeGesturesEnabled) {
5760
+ if (event.targetTouches.length < 2 && this._collaborativeGesturesEnabled) {
5497
5761
  this._filterEventCallback(true);
5498
5762
  }
5499
5763
  });
@@ -5516,6 +5780,10 @@
5516
5780
  return this._isTransitioning;
5517
5781
  }
5518
5782
  /** PUBLIC METHODS */
5783
+ collaborativeGesturesEnabled(enabled) {
5784
+ if (enabled === void 0) return this._collaborativeGesturesEnabled;
5785
+ this._collaborativeGesturesEnabled = enabled;
5786
+ }
5519
5787
  onFilterEvent(callback) {
5520
5788
  this._filterEventCallback = callback;
5521
5789
  }
@@ -5591,6 +5859,18 @@
5591
5859
  );
5592
5860
  d3Selection.select(this._viewport).transition().duration(500).call(this._zoomBehaviour.transform, newTransform, focalPoint);
5593
5861
  }
5862
+ /** @param {import("./layers").Layer[]} layers */
5863
+ hasLayers(layers) {
5864
+ if (layers.length !== this.layers.length) {
5865
+ return false;
5866
+ }
5867
+ for (let i = 0; i < layers.length; i++) {
5868
+ if (layers[i] !== this.layers[i]) {
5869
+ return false;
5870
+ }
5871
+ }
5872
+ return true;
5873
+ }
5594
5874
  async resetZoom(options) {
5595
5875
  return this.zoomTo(1, options);
5596
5876
  }
@@ -5687,201 +5967,13 @@
5687
5967
  _renderFrame() {
5688
5968
  const frameState = {
5689
5969
  size: this.size,
5690
- viewState: this.view.getState()
5970
+ viewState: this.view.getState(),
5971
+ debug: this.options.debug || false
5691
5972
  };
5692
5973
  this._renderer.renderFrame(frameState);
5693
5974
  this._animationFrameRequestID = null;
5694
5975
  }
5695
5976
  };
5696
- function bboxFeature(bounds) {
5697
- const minLon = bounds[0][0];
5698
- const minLat = bounds[0][1];
5699
- const maxLon = bounds[1][0];
5700
- const maxLat = bounds[1][1];
5701
- const feature = {
5702
- type: "Feature",
5703
- properties: {},
5704
- geometry: {
5705
- coordinates: [
5706
- [
5707
- [minLon, maxLat],
5708
- [maxLon, maxLat],
5709
- [maxLon, minLat],
5710
- [minLon, minLat],
5711
- [minLon, maxLat]
5712
- ]
5713
- ],
5714
- type: "Polygon"
5715
- }
5716
- };
5717
- return feature;
5718
- }
5719
- const BASE_RESOLUTION = 156543.03392;
5720
- function zoomLevelToZoomScale(zoomLevel, initialResolution) {
5721
- const resolution = BASE_RESOLUTION / Math.pow(2, zoomLevel);
5722
- const zoomScale = initialResolution / resolution;
5723
- return zoomScale;
5724
- }
5725
- function zoomLevelForResolution(currentResolution) {
5726
- const zoomLevel = Math.log2(BASE_RESOLUTION / currentResolution);
5727
- return zoomLevel;
5728
- }
5729
- function haversineDistance(lat1, lon1, lat2, lon2) {
5730
- const R2 = 6371e3;
5731
- const toRadians = (degrees2) => degrees2 * Math.PI / 180;
5732
- const dLat = toRadians(lat2 - lat1);
5733
- const dLon = toRadians(lon2 - lon1);
5734
- const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
5735
- const c2 = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
5736
- return R2 * c2;
5737
- }
5738
- function resolutionForExtent(extent, viewportSize) {
5739
- const [lonMin, latMin, lonMax, latMax] = extent;
5740
- const latMid = (latMin + latMax) / 2;
5741
- const distance = haversineDistance(latMid, lonMin, latMid, lonMax);
5742
- const resolution = distance / viewportSize[0];
5743
- return resolution;
5744
- }
5745
- class View {
5746
- constructor({ projection, extent, minZoom, maxZoom, padding }) {
5747
- projection.revision = 0;
5748
- this.projection = projection;
5749
- this.extent = extent;
5750
- this.minZoom = minZoom;
5751
- this.maxZoom = maxZoom;
5752
- this._transform = d3Zoom.zoomIdentity;
5753
- this._padding = padding;
5754
- this._viewPortSize = [0, 0];
5755
- this.pixelRatio = window.devicePixelRatio;
5756
- }
5757
- set viewPortSize(size) {
5758
- const previousSize = this._viewPortSize;
5759
- this._viewPortSize = size;
5760
- if (previousSize !== size) {
5761
- this.fitObject(bboxFeature(this.extent));
5762
- const initialExtent = this.getVisibleExtent(d3Zoom.zoomIdentity, this.projection);
5763
- this.initialResolution = resolutionForExtent(initialExtent, size);
5764
- }
5765
- }
5766
- get viewPortSize() {
5767
- return this._viewPortSize;
5768
- }
5769
- set transform(transform) {
5770
- this._transform = transform;
5771
- }
5772
- get transform() {
5773
- return new d3Zoom.ZoomTransform(
5774
- this._transform.k,
5775
- this._transform.x * this.pixelRatio,
5776
- this._transform.y * this.pixelRatio
5777
- );
5778
- }
5779
- // map size in pixels (i.e. scaled by device pixel ratio)
5780
- get mapSize() {
5781
- return scaleSize(this.viewPortSize, this.pixelRatio);
5782
- }
5783
- get padding() {
5784
- return this._padding;
5785
- }
5786
- // padding in pixels (i.e. scaled by device pixel ratio)
5787
- get scaledPadding() {
5788
- const scaledPadding = { ...this._padding };
5789
- return scalePadding(scaledPadding, this.pixelRatio);
5790
- }
5791
- // calculates the upper and lower zoom scales
5792
- get scaleExtent() {
5793
- const maxScale = zoomLevelToZoomScale(this.maxZoom, this.initialResolution);
5794
- return [1, maxScale];
5795
- }
5796
- setProjection(projection) {
5797
- this.projection = projection;
5798
- this.fitObject(bboxFeature(this.extent));
5799
- }
5800
- // only set the raw projection when it has already been configured with projection.fitExtent()
5801
- setRawProjection(projection) {
5802
- this.projection = projection;
5803
- }
5804
- fitObject(geoJSON) {
5805
- this.projection.fitExtent(this.getMapExtent(), geoJSON);
5806
- ++this.projection.revision;
5807
- }
5808
- // returns bounds relative to the viewport
5809
- boundsForExtent(extent) {
5810
- const SW = this.projection([extent[0], extent[1]]);
5811
- const NE = this.projection([extent[2], extent[3]]);
5812
- const minX = SW[0] / this.pixelRatio;
5813
- const minY = NE[1] / this.pixelRatio;
5814
- const maxX = NE[0] / this.pixelRatio;
5815
- const maxY = SW[1] / this.pixelRatio;
5816
- const width = maxX - minX;
5817
- const height = maxY - minY;
5818
- return [
5819
- [minX, minY],
5820
- [width, height]
5821
- ];
5822
- }
5823
- invert(point) {
5824
- const { projection, pixelRatio, transform } = this.getState();
5825
- const scaledPoint = [point[0] * pixelRatio, point[1] * pixelRatio];
5826
- const untransformedPoint = transform.invert(scaledPoint);
5827
- const mapCoordinate = projection.invert(untransformedPoint);
5828
- return mapCoordinate;
5829
- }
5830
- // bounds is defined as [[minX, minY], [maxX, maxY]]
5831
- invertBounds(bounds) {
5832
- const topLeft = bounds[0];
5833
- const topRight = [bounds[1][0], bounds[0][1]];
5834
- const bottomRight = [bounds[1][0], bounds[1][1]];
5835
- const bottomLeft = [bounds[0][0], bounds[1][1]];
5836
- const points = [topLeft, topRight, bottomRight, bottomLeft, topLeft];
5837
- return points.map((d2) => this.invert(d2));
5838
- }
5839
- // map resolution (meters per pixel)
5840
- getResolution() {
5841
- return resolutionForExtent(
5842
- this.getVisibleExtent(this.transform, this.projection),
5843
- this.viewPortSize
5844
- );
5845
- }
5846
- // map zoom level (0 = the entire world)
5847
- getZoomLevel() {
5848
- return zoomLevelForResolution(this.getResolution());
5849
- }
5850
- // get extent for drawn map
5851
- getMapExtent() {
5852
- const mapSizeInPixels = this.mapSize;
5853
- const paddingInPixels = this.scaledPadding;
5854
- return [
5855
- [paddingInPixels.left, paddingInPixels.top],
5856
- sizeMinusPadding(mapSizeInPixels, {
5857
- ...paddingInPixels,
5858
- left: 0,
5859
- top: 0
5860
- })
5861
- ];
5862
- }
5863
- // visible extent in map coordinates
5864
- getVisibleExtent(transform, projection) {
5865
- const [width, height] = this.mapSize;
5866
- const southWest = projection.invert(transform.invert([0, height]));
5867
- const northEast = projection.invert(transform.invert([width, 0]));
5868
- return [southWest[0], southWest[1], northEast[0], northEast[1]];
5869
- }
5870
- getState() {
5871
- const transform = this.transform;
5872
- const projection = this.projection;
5873
- return {
5874
- transform,
5875
- projection,
5876
- zoomLevel: transform.k,
5877
- pixelRatio: this.pixelRatio,
5878
- padding: this.padding,
5879
- viewPortSize: this.viewPortSize,
5880
- sizeInPixels: scaleSize(this.viewPortSize, this.pixelRatio),
5881
- visibleExtent: this.getVisibleExtent(transform, projection)
5882
- };
5883
- }
5884
- }
5885
5977
  const mapContainer = "_mapContainer_1ogf3_9";
5886
5978
  const helpTextContainer = "_helpTextContainer_1ogf3_15";
5887
5979
  const helpText = "_helpText_1ogf3_15";
@@ -5894,18 +5986,33 @@
5894
5986
  desktopHelpText,
5895
5987
  mobileHelpText: mobileHelpText$1
5896
5988
  };
5989
+ const MapContext = preact.createContext(null);
5990
+ function MapProvider({ map, children }) {
5991
+ const registeredLayers = [];
5992
+ const registerLayer = (layer) => {
5993
+ registeredLayers.push(layer);
5994
+ };
5995
+ hooks.useEffect(() => {
5996
+ if (map && !map.hasLayers(registeredLayers)) {
5997
+ map.setLayers(registeredLayers);
5998
+ }
5999
+ }, [map, children]);
6000
+ return /* @__PURE__ */ jsxRuntime.jsx(MapContext.Provider, { value: { registerLayer }, children });
6001
+ }
5897
6002
  const mobileHelpText = "Use two fingers to zoom";
5898
6003
  const Map$1 = compat.forwardRef(
5899
6004
  ({ config, inModalState = false, onLoad, children }, ref) => {
5900
- const { layers } = children;
5901
6005
  const targetRef = hooks.useRef();
5902
- const [map, setMap] = hooks.useState();
6006
+ const [map, setMap] = hooks.useState(
6007
+ /** @type {_Map | null} */
6008
+ null
6009
+ );
5903
6010
  const [zoomHelpText, setZoomHelpText] = hooks.useState("");
5904
6011
  const [showHelpText, setShowHelpText] = hooks.useState(false);
5905
6012
  hooks.useEffect(() => {
5906
6013
  var _a;
5907
6014
  const map2 = new Map$2({
5908
- view: new View(config.view),
6015
+ ...config,
5909
6016
  target: targetRef.current
5910
6017
  });
5911
6018
  map2.collaborativeGesturesEnabled = true;
@@ -5921,7 +6028,7 @@
5921
6028
  map2.destroy();
5922
6029
  setMap(null);
5923
6030
  };
5924
- }, [config.view]);
6031
+ }, [config]);
5925
6032
  hooks.useEffect(() => {
5926
6033
  if (!map) return;
5927
6034
  let timeoutID;
@@ -5951,27 +6058,25 @@
5951
6058
  }
5952
6059
  };
5953
6060
  }, [map, ref, onLoad]);
5954
- hooks.useEffect(() => {
5955
- if (map && layers !== map.layers) {
5956
- map.setLayers(layers);
5957
- }
5958
- }, [map, layers]);
5959
6061
  hooks.useEffect(() => {
5960
6062
  if (!map) return;
5961
6063
  map.collaborativeGesturesEnabled = !inModalState;
5962
6064
  }, [map, inModalState]);
5963
- return /* @__PURE__ */ jsxRuntime.jsx("figure", { ref: targetRef, className: styles$3.mapContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
5964
- "div",
5965
- {
5966
- className: styles$3.helpTextContainer,
5967
- style: { opacity: showHelpText ? 1 : 0 },
5968
- "aria-hidden": true,
5969
- children: [
5970
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: [styles$3.helpText, styles$3.desktopHelpText].join(" "), children: zoomHelpText }),
5971
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: [styles$3.helpText, styles$3.mobileHelpText].join(" "), children: mobileHelpText })
5972
- ]
5973
- }
5974
- ) });
6065
+ return /* @__PURE__ */ jsxRuntime.jsxs("figure", { ref: targetRef, className: styles$3.mapContainer, children: [
6066
+ /* @__PURE__ */ jsxRuntime.jsxs(
6067
+ "div",
6068
+ {
6069
+ className: styles$3.helpTextContainer,
6070
+ style: { opacity: showHelpText ? 1 : 0 },
6071
+ "aria-hidden": true,
6072
+ children: [
6073
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: [styles$3.helpText, styles$3.desktopHelpText].join(" "), children: zoomHelpText }),
6074
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: [styles$3.helpText, styles$3.mobileHelpText].join(" "), children: mobileHelpText })
6075
+ ]
6076
+ }
6077
+ ),
6078
+ /* @__PURE__ */ jsxRuntime.jsx(MapProvider, { map, children })
6079
+ ] });
5975
6080
  }
5976
6081
  );
5977
6082
  function IconMinus() {
@@ -6075,17 +6180,12 @@
6075
6180
  )
6076
6181
  ] });
6077
6182
  }
6078
- const Projection = {
6079
- geoAlbersUKComposite: geoAlbersUk(),
6080
- geoAlbersEngland: d3Geo.geoAlbers().center([0, 52.7]).rotate([1.1743, 0]).parallels([50, 54]),
6081
- geoMercator: d3Geo.geoMercator()
6082
- };
6083
6183
  class FeatureRenderer {
6084
6184
  constructor() {
6085
6185
  this.drawingFunction = d3Geo.geoPath();
6086
6186
  }
6087
- setStyle(style) {
6088
- this.style = style;
6187
+ setStyle(style2) {
6188
+ this.style = style2;
6089
6189
  }
6090
6190
  render(frameState, feature, context) {
6091
6191
  if (!this.style) {
@@ -6096,6 +6196,17 @@
6096
6196
  this.drawingFunction.context(context);
6097
6197
  context.beginPath();
6098
6198
  const geometries = feature.getProjectedGeometries(projection);
6199
+ if (frameState.debug) {
6200
+ try {
6201
+ validateGeometries(geometries);
6202
+ } catch {
6203
+ console.error(
6204
+ `Invalid geometry. Feature skipped during rendering. Click here to inspect geometry: ${generateDebugUrl(feature)}
6205
+ `,
6206
+ feature
6207
+ );
6208
+ }
6209
+ }
6099
6210
  for (const geometry of geometries) {
6100
6211
  this.drawingFunction(geometry);
6101
6212
  }
@@ -6122,12 +6233,12 @@
6122
6233
  this.featureRenderer = new FeatureRenderer();
6123
6234
  this._element = document.createElement("div");
6124
6235
  this._element.className = "gv-text-layer";
6125
- const style = this._element.style;
6126
- style.position = "absolute";
6127
- style.width = "100%";
6128
- style.height = "100%";
6129
- style.pointerEvents = "none";
6130
- style.overflow = "hidden";
6236
+ const style2 = this._element.style;
6237
+ style2.position = "absolute";
6238
+ style2.width = "100%";
6239
+ style2.height = "100%";
6240
+ style2.pointerEvents = "none";
6241
+ style2.overflow = "hidden";
6131
6242
  }
6132
6243
  renderFrame(frameState, targetElement) {
6133
6244
  if (this.layer.opacity === 0) return targetElement;
@@ -6182,20 +6293,20 @@
6182
6293
  return textElement;
6183
6294
  }
6184
6295
  styleTextElement(element, textStyle, position) {
6185
- const style = element.style;
6186
- style.position = "absolute";
6187
- style.transform = `translate(-50%, -50%)`;
6188
- style.left = position.left;
6189
- style.top = position.top;
6190
- style.textAlign = "center";
6191
- style.whiteSpace = "nowrap";
6192
- style.fontFamily = textStyle.fontFamily;
6193
- style.fontSize = textStyle.fontSize;
6194
- style.fontWeight = textStyle.fontWeight;
6195
- style.lineHeight = textStyle.lineHeight;
6196
- style.color = textStyle.color;
6197
- style.textShadow = textStyle.textShadow;
6198
- style.padding = `${textPadding.top}px ${textPadding.right}px ${textPadding.bottom}px ${textPadding.left}px`;
6296
+ const style2 = element.style;
6297
+ style2.position = "absolute";
6298
+ style2.transform = `translate(-50%, -50%)`;
6299
+ style2.left = position.left;
6300
+ style2.top = position.top;
6301
+ style2.textAlign = "center";
6302
+ style2.whiteSpace = "nowrap";
6303
+ style2.fontFamily = textStyle.fontFamily;
6304
+ style2.fontSize = textStyle.fontSize;
6305
+ style2.fontWeight = textStyle.fontWeight;
6306
+ style2.lineHeight = textStyle.lineHeight;
6307
+ style2.color = textStyle.color;
6308
+ style2.textShadow = textStyle.textShadow;
6309
+ style2.padding = `${textPadding.top}px ${textPadding.right}px ${textPadding.bottom}px ${textPadding.left}px`;
6199
6310
  }
6200
6311
  getElementBBox(element, position) {
6201
6312
  if (!element.parentElement) {
@@ -6214,21 +6325,21 @@
6214
6325
  }
6215
6326
  getCollisionBoxElement(bbox) {
6216
6327
  const element = document.createElement("div");
6217
- const style = element.style;
6218
- style.position = "absolute";
6219
- style.left = `${bbox.minX}px`;
6220
- style.top = `${bbox.minY}px`;
6221
- style.width = `${bbox.maxX - bbox.minX}px`;
6222
- style.height = `${bbox.maxY - bbox.minY}px`;
6223
- style.border = "2px solid black";
6328
+ const style2 = element.style;
6329
+ style2.position = "absolute";
6330
+ style2.left = `${bbox.minX}px`;
6331
+ style2.top = `${bbox.minY}px`;
6332
+ style2.width = `${bbox.maxX - bbox.minX}px`;
6333
+ style2.height = `${bbox.maxY - bbox.minY}px`;
6334
+ style2.border = "2px solid black";
6224
6335
  return element;
6225
6336
  }
6226
6337
  }
6227
6338
  class Style {
6228
- constructor(options) {
6229
- this.stroke = options == null ? void 0 : options.stroke;
6230
- this.fill = options == null ? void 0 : options.fill;
6231
- this.text = options == null ? void 0 : options.text;
6339
+ constructor(properties) {
6340
+ this.stroke = properties == null ? void 0 : properties.stroke;
6341
+ this.fill = properties == null ? void 0 : properties.fill;
6342
+ this.text = properties == null ? void 0 : properties.text;
6232
6343
  }
6233
6344
  clone() {
6234
6345
  return new Style({
@@ -6458,20 +6569,60 @@
6458
6569
  }
6459
6570
  }
6460
6571
  class TextLayer {
6572
+ /** @param {TextLayerComponentProps} props */
6573
+ static Component({
6574
+ features,
6575
+ style: style2,
6576
+ minZoom,
6577
+ opacity,
6578
+ declutter,
6579
+ drawCollisionBoxes
6580
+ }) {
6581
+ const { registerLayer } = hooks.useContext(MapContext);
6582
+ const layer = hooks.useMemo(
6583
+ () => TextLayer.with(features, {
6584
+ style: style2,
6585
+ minZoom,
6586
+ opacity,
6587
+ declutter,
6588
+ drawCollisionBoxes
6589
+ }),
6590
+ // eslint-disable-next-line react-hooks/exhaustive-deps
6591
+ [features, minZoom, opacity, declutter, drawCollisionBoxes]
6592
+ );
6593
+ registerLayer(layer);
6594
+ hooks.useEffect(() => {
6595
+ layer.style = style2;
6596
+ }, [style2]);
6597
+ return null;
6598
+ }
6599
+ /**
6600
+ * @param {import("../Feature").Feature[]} features
6601
+ * @param {TextLayerOptions} options
6602
+ */
6461
6603
  static with(features, options) {
6462
6604
  const source = new VectorSource({ features });
6463
6605
  return new TextLayer({ source, ...options });
6464
6606
  }
6607
+ /**
6608
+ * @param {Object} params
6609
+ * @param {VectorSource} params.source
6610
+ * @param {Style} [params.style=undefined]
6611
+ * @param {number} [params.minZoom=0]
6612
+ * @param {number} [params.opacity=1]
6613
+ * @param {boolean} [params.declutter=true]
6614
+ * @param {boolean} [params.drawCollisionBoxes=false]
6615
+ */
6465
6616
  constructor({
6466
6617
  source,
6467
- style,
6618
+ style: style2,
6468
6619
  minZoom = 0,
6469
6620
  opacity = 1,
6470
6621
  declutter = true,
6471
6622
  drawCollisionBoxes = false
6472
6623
  }) {
6473
6624
  this.source = source;
6474
- this._style = style;
6625
+ this._style = style2;
6475
6626
  this.minZoom = minZoom;
6476
6627
  this.opacity = opacity;
6477
6628
  this.declutter = declutter;
@@ -6489,8 +6640,8 @@
6489
6640
  });
6490
6641
  return defaultStyle;
6491
6642
  }
6492
- set style(style) {
6493
- this._style = style;
6643
+ set style(style2) {
6644
+ this._style = style2;
6494
6645
  this.dispatcher.dispatch(MapEvent.CHANGE);
6495
6646
  }
6496
6647
  getExtent() {
@@ -6505,10 +6656,10 @@
6505
6656
  return extent;
6506
6657
  }
6507
6658
  getStyleFunction() {
6508
- const style = this.style;
6509
- if (typeof style === "function") return style;
6659
+ const style2 = this.style;
6660
+ if (typeof style2 === "function") return style2;
6510
6661
  return () => {
6511
- return style;
6662
+ return style2;
6512
6663
  };
6513
6664
  }
6514
6665
  renderFrame(frameState, targetElement) {
@@ -6576,27 +6727,58 @@
6576
6727
  createContainer() {
6577
6728
  const container2 = document.createElement("div");
6578
6729
  container2.className = "gv-map-layer";
6579
- let style = container2.style;
6580
- style.position = "absolute";
6581
- style.width = "100%";
6582
- style.height = "100%";
6730
+ let style2 = container2.style;
6731
+ style2.position = "absolute";
6732
+ style2.width = "100%";
6733
+ style2.height = "100%";
6583
6734
  const canvas = document.createElement("canvas");
6584
- style = canvas.style;
6585
- style.position = "absolute";
6586
- style.width = "100%";
6587
- style.height = "100%";
6735
+ style2 = canvas.style;
6736
+ style2.position = "absolute";
6737
+ style2.width = "100%";
6738
+ style2.height = "100%";
6588
6739
  container2.appendChild(canvas);
6589
6740
  return container2;
6590
6741
  }
6591
6742
  }
6592
6743
  class VectorLayer {
6744
+ /** @param {VectorLayerComponentProps} props */
6745
+ static Component({ features, style: style2, minZoom, opacity, hitDetectionEnabled }) {
6746
+ const { registerLayer } = hooks.useContext(MapContext);
6747
+ const layer = hooks.useMemo(
6748
+ () => VectorLayer.with(features, {
6749
+ style: style2,
6750
+ minZoom,
6751
+ opacity,
6752
+ hitDetectionEnabled
6753
+ }),
6754
+ // eslint-disable-next-line react-hooks/exhaustive-deps
6755
+ [features, minZoom, opacity, hitDetectionEnabled]
6756
+ );
6757
+ registerLayer(layer);
6758
+ hooks.useEffect(() => {
6759
+ layer.style = style2;
6760
+ }, [style2]);
6761
+ return null;
6762
+ }
6763
+ /**
6764
+ * @param {import("../Feature").Feature[]} features
6765
+ * @param {VectorLayerOptions} options
6766
+ */
6593
6767
  static with(features, options) {
6594
6768
  const source = new VectorSource({ features });
6595
6769
  return new VectorLayer({ source, ...options });
6596
6770
  }
6771
+ /**
6772
+ * @param {Object} params
6773
+ * @param {VectorSource} params.source
6774
+ * @param {Style | (() => Style)} [params.style=undefined]
6775
+ * @param {number} [params.minZoom=0]
6776
+ * @param {number} [params.opacity=1]
6777
+ * @param {boolean} [params.hitDetectionEnabled=false]
6778
+ */
6597
6779
  constructor({
6598
6780
  source,
6599
- style,
6781
+ style: style2,
6600
6782
  minZoom = 0,
6601
6783
  opacity = 1,
6602
6784
  hitDetectionEnabled = true
@@ -6604,7 +6786,7 @@
6604
6786
  this.dispatcher = new Dispatcher(this);
6605
6787
  this.renderer = new VectorLayerRenderer(this);
6606
6788
  this.source = source;
6607
- this._style = style;
6789
+ this._style = style2;
6608
6790
  this.minZoom = minZoom;
6609
6791
  this.opacity = opacity;
6610
6792
  this.hitDetectionEnabled = hitDetectionEnabled;
@@ -6635,15 +6817,15 @@
6635
6817
  });
6636
6818
  return defaultStyle;
6637
6819
  }
6638
- set style(style) {
6639
- this._style = style;
6820
+ set style(style2) {
6821
+ this._style = style2;
6640
6822
  this.dispatcher.dispatch(MapEvent.CHANGE);
6641
6823
  }
6642
6824
  getStyleFunction() {
6643
- const style = this.style;
6644
- if (typeof style === "function") return style;
6825
+ const style2 = this.style;
6826
+ if (typeof style2 === "function") return style2;
6645
6827
  return () => {
6646
- return style;
6828
+ return style2;
6647
6829
  };
6648
6830
  }
6649
6831
  getExtent() {
@@ -6751,7 +6933,7 @@
6751
6933
  break;
6752
6934
  }
6753
6935
  }
6754
- feature.setGeometries(geometries);
6936
+ feature.setGeometry(geometries);
6755
6937
  features.push(feature);
6756
6938
  }
6757
6939
  return features;
@@ -6815,23 +6997,32 @@
6815
6997
  };
6816
6998
  }
6817
6999
  class Feature {
6818
- constructor({ id: id2, geometries, properties, style }) {
7000
+ /**
7001
+ * Represents a feature on the map
7002
+ * @constructor
7003
+ * @param {Object} props - The properties for the feature.
7004
+ * @property {string} id - The unique identifier of the feature
7005
+ * @property {Array} geometries - The geometries of the feature
7006
+ * @property {Object} properties - The properties of the feature
7007
+ * @property {import("./styles").Style | import("./styles").StyleFunction} style - The style of the feature
7008
+ */
7009
+ constructor({ id: id2, geometries, properties, style: style2 }) {
6819
7010
  this.id = id2;
6820
7011
  this.geometries = geometries;
6821
7012
  this.properties = properties;
6822
- this.style = style;
7013
+ this.style = style2;
6823
7014
  this.uid = createUid();
6824
7015
  }
6825
7016
  getExtent() {
6826
7017
  if (this._extent) return this._extent;
6827
- const extent = this.geometries.reduce((combinedExtent, geometry) => {
6828
- if (!combinedExtent) return geometry.extent;
6829
- return combineExtents(geometry.extent, combinedExtent);
7018
+ const extent = this.geometries.reduce((combinedExtent, geometries) => {
7019
+ if (!combinedExtent) return geometries.extent;
7020
+ return combineExtents(geometries.extent, combinedExtent);
6830
7021
  }, null);
6831
7022
  this._extent = extent;
6832
7023
  return extent;
6833
7024
  }
6834
- setGeometries(geometries) {
7025
+ setgeometries(geometries) {
6835
7026
  this.geometries = geometries;
6836
7027
  this._extent = void 0;
6837
7028
  }
@@ -6841,19 +7032,19 @@
6841
7032
  );
6842
7033
  }
6843
7034
  getStyleFunction() {
6844
- const style = this.style;
6845
- if (!style) return null;
6846
- if (typeof style === "function") return style;
7035
+ const style2 = this.style;
7036
+ if (!style2) return null;
7037
+ if (typeof style2 === "function") return style2;
6847
7038
  return () => {
6848
- return style;
7039
+ return style2;
6849
7040
  };
6850
7041
  }
6851
7042
  containsCoordinate(coordinate) {
6852
7043
  if (!containsCoordinate(this.getExtent(), coordinate)) {
6853
7044
  return false;
6854
7045
  }
6855
- for (const geometry of this.geometries) {
6856
- if (d3Geo.geoContains(geometry.getGeoJSON(), coordinate)) {
7046
+ for (const geometries of this.geometries) {
7047
+ if (d3Geo.geoContains(geometries.getGeoJSON(), coordinate)) {
6857
7048
  return true;
6858
7049
  }
6859
7050
  }
@@ -6867,14 +7058,83 @@
6867
7058
  style: this.style
6868
7059
  });
6869
7060
  }
7061
+ /**
7062
+ * Returns the geometries as a GeoJSON object
7063
+ * @returns {Object} The GeoJSON representation of the geometries
7064
+ */
7065
+ getGeoJSON() {
7066
+ const geometries = this.geometries.map((d2) => d2.getGeoJSON());
7067
+ if (geometries.length === 1) return geometries[0];
7068
+ return {
7069
+ type: "Feature",
7070
+ geometry: this._getGeometryGeoJSON(),
7071
+ properties: this.properties
7072
+ };
7073
+ }
7074
+ _getGeometryGeoJSON() {
7075
+ const geometries = this.geometries.map((d2) => d2.getGeoJSON());
7076
+ if (geometries.length === 0) throw new Error("Feature has no geometries");
7077
+ if (geometries.length === 1) return geometries[0];
7078
+ if (geometries[0].type === "Polygon") {
7079
+ return {
7080
+ type: "MultiPolygon",
7081
+ coordinates: geometries.map((d2) => d2.coordinates)
7082
+ };
7083
+ } else if (geometries[0].type === "LineString") {
7084
+ return {
7085
+ type: "MultiLineString",
7086
+ coordinates: geometries.map((d2) => d2.coordinates)
7087
+ };
7088
+ } else if (geometries[0].type === "Point") {
7089
+ return {
7090
+ type: "MultiPoint",
7091
+ coordinates: geometries.map((d2) => d2.coordinates)
7092
+ };
7093
+ }
7094
+ throw new Error("Could not determine geometry type");
7095
+ }
6870
7096
  }
6871
- class LineString {
6872
- constructor({ type = "LineString", extent, coordinates }) {
7097
+ class Geometry {
7098
+ /**
7099
+ * Represents vector geometry
7100
+ * @constructor
7101
+ * @param {Object} options
7102
+ * @param {string} options.type - The type of geometry (e.g., 'Point', 'LineString', 'Polygon')
7103
+ * @param {Array} options.extent - The extent of the geometry (e.g., [xmin, ymin, xmax, ymax])
7104
+ * @param {Array} options.coordinates - The coordinates of the geometry (e.g., [[x1, y1], [x2, y2], ...])
7105
+ */
7106
+ constructor({ type, extent, coordinates }) {
6873
7107
  this.type = type;
6874
7108
  this.extent = extent;
6875
7109
  this.coordinates = coordinates;
6876
7110
  this.getProjected = memoise(this._getProjected).bind(this);
6877
7111
  }
7112
+ /**
7113
+ * Returns the geometry as a GeoJSON object
7114
+ * @function
7115
+ * @param {import("../projection").Projection} projection - The projection to use for the geometry
7116
+ * @returns {Object} A GeoJSON representation of the projected geometry
7117
+ * @private
7118
+ */
7119
+ // eslint-disable-next-line no-unused-vars
7120
+ _getProjected(projection) {
7121
+ throw new Error("Not implemented");
7122
+ }
7123
+ /**
7124
+ * Returns the geometry as a GeoJSON object
7125
+ * @returns {Object} The GeoJSON representation of the geometry
7126
+ */
7127
+ getGeoJSON() {
7128
+ return {
7129
+ type: this.type,
7130
+ coordinates: this.coordinates
7131
+ };
7132
+ }
7133
+ }
7134
+ class LineString extends Geometry {
7135
+ constructor({ type = "LineString", extent, coordinates }) {
7136
+ super({ type, extent, coordinates });
7137
+ }
6878
7138
  _getProjected(projection) {
6879
7139
  const projected = [];
6880
7140
  for (const point of this.coordinates) {
@@ -6886,23 +7146,26 @@
6886
7146
  };
6887
7147
  }
6888
7148
  }
6889
- class Polygon {
7149
+ class Polygon extends Geometry {
6890
7150
  constructor({ type = "Polygon", extent, coordinates }) {
6891
- this.type = type;
6892
- this.extent = extent;
6893
- this.coordinates = coordinates;
6894
- this.getProjected = memoise(this._getProjected).bind(this);
7151
+ super({ type, extent, coordinates });
6895
7152
  }
6896
- // eslint-disable-next-line no-unused-vars
6897
- _getProjected(projection, _revision) {
7153
+ _getProjected(projection) {
6898
7154
  const projected = [];
6899
7155
  const rings = this.coordinates;
6900
7156
  for (const ring of rings) {
6901
7157
  const projectedRing = [];
6902
7158
  for (const point of ring) {
6903
- projectedRing.push(projection(point));
7159
+ const projectedPoint = projection(point);
7160
+ if (projectedPoint) {
7161
+ projectedRing.push(projectedPoint);
7162
+ } else {
7163
+ break;
7164
+ }
7165
+ }
7166
+ if (projectedRing.length > 0) {
7167
+ projected.push(projectedRing);
6904
7168
  }
6905
- projected.push(projectedRing);
6906
7169
  }
6907
7170
  return {
6908
7171
  type: this.type,
@@ -6918,12 +7181,6 @@
6918
7181
  setCoordinates(coordinates) {
6919
7182
  this.coordinates = coordinates;
6920
7183
  }
6921
- getGeoJSON() {
6922
- return {
6923
- type: this.type,
6924
- coordinates: this.coordinates
6925
- };
6926
- }
6927
7184
  clone() {
6928
7185
  return new Polygon({
6929
7186
  extent: this.extent,
@@ -6931,12 +7188,10 @@
6931
7188
  });
6932
7189
  }
6933
7190
  }
6934
- class Point {
7191
+ class Point extends Geometry {
6935
7192
  constructor({ type = "Point", coordinates }) {
6936
- this.type = type;
7193
+ super({ type, extent: null, coordinates });
6937
7194
  this.extent = [...coordinates, ...coordinates];
6938
- this.coordinates = coordinates;
6939
- this.getProjected = memoise(this._getProjected).bind(this);
6940
7195
  }
6941
7196
  _getProjected(projection) {
6942
7197
  return {
@@ -7458,6 +7713,7 @@
7458
7713
  }
7459
7714
  exports2.AdSlot = AdSlot;
7460
7715
  exports2.ArrowButton = ArrowButton;
7716
+ exports2.AspectRatioBox = AspectRatioBox;
7461
7717
  exports2.Button = Button;
7462
7718
  exports2.ChangeBar = ChangeBar;
7463
7719
  exports2.Chevron = Chevron;