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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6180,174 +6180,103 @@
6180
6180
  )
6181
6181
  ] });
6182
6182
  }
6183
- class FeatureRenderer {
6184
- constructor() {
6185
- this.drawingFunction = d3Geo.geoPath();
6186
- }
6187
- setStyle(style2) {
6183
+ class Feature {
6184
+ /**
6185
+ * Represents a feature on the map
6186
+ * @constructor
6187
+ * @param {Object} props - The properties for the feature.
6188
+ * @property {string} id - The unique identifier of the feature
6189
+ * @property {Array} geometries - The geometries of the feature
6190
+ * @property {Object} properties - The properties of the feature
6191
+ * @property {import("./styles").Style | import("./styles").StyleFunction} style - The style of the feature
6192
+ */
6193
+ constructor({ id: id2, geometries, properties, style: style2 }) {
6194
+ this.id = id2;
6195
+ this.geometries = geometries;
6196
+ this.properties = properties;
6188
6197
  this.style = style2;
6198
+ this.uid = createUid();
6189
6199
  }
6190
- render(frameState, feature, context) {
6191
- if (!this.style) {
6192
- return;
6193
- }
6194
- const { projection, transform, pixelRatio } = frameState.viewState;
6195
- const { stroke, fill } = this.style;
6196
- this.drawingFunction.context(context);
6197
- context.beginPath();
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
- }
6210
- for (const geometry of geometries) {
6211
- this.drawingFunction(geometry);
6212
- }
6213
- if (fill) {
6214
- context.fillStyle = fill.getRgba();
6215
- context.fill();
6216
- }
6217
- if (stroke) {
6218
- context.lineWidth = stroke.width * pixelRatio / transform.k;
6219
- context.strokeStyle = stroke.getRgba();
6220
- context.stroke();
6221
- }
6222
- }
6223
- }
6224
- const textPadding = {
6225
- top: 20,
6226
- right: 20,
6227
- bottom: 20,
6228
- left: 20
6229
- };
6230
- class TextLayerRenderer {
6231
- constructor(layer) {
6232
- this.layer = layer;
6233
- this.featureRenderer = new FeatureRenderer();
6234
- this._element = document.createElement("div");
6235
- this._element.className = "gv-text-layer";
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";
6200
+ getExtent() {
6201
+ if (this._extent) return this._extent;
6202
+ const extent = this.geometries.reduce((combinedExtent, geometries) => {
6203
+ if (!combinedExtent) return geometries.extent;
6204
+ return combineExtents(geometries.extent, combinedExtent);
6205
+ }, null);
6206
+ this._extent = extent;
6207
+ return extent;
6242
6208
  }
6243
- renderFrame(frameState, targetElement) {
6244
- if (this.layer.opacity === 0) return targetElement;
6245
- const { declutterTree } = frameState;
6246
- const { projection, viewPortSize, sizeInPixels, visibleExtent, transform } = frameState.viewState;
6247
- this._element.style.opacity = this.layer.opacity;
6248
- const source = this.layer.source;
6249
- const features = source.getFeaturesInExtent(visibleExtent);
6250
- const textElements = [];
6251
- for (const feature of features) {
6252
- const geometries = feature.getProjectedGeometries(projection);
6253
- const point = geometries.find((d2) => d2.type === "Point");
6254
- if (!point) {
6255
- throw new Error(
6256
- `Expected Point geometry for feature in TextLayer: ${feature}`
6257
- );
6258
- }
6259
- const styleFunction2 = feature.getStyleFunction() || this.layer.getStyleFunction();
6260
- const featureStyle = styleFunction2(feature);
6261
- const textElement = this.getTextElementWithID(feature.uid);
6262
- textElement.innerText = featureStyle.text.content;
6263
- const [relativeX, relativeY] = transform.apply(point.coordinates).map((d2, i) => d2 / sizeInPixels[i]);
6264
- const position = {
6265
- left: `${relativeX * 100}%`,
6266
- top: `${relativeY * 100}%`
6267
- };
6268
- this.styleTextElement(textElement, featureStyle.text, position);
6269
- const bbox = this.getElementBBox(textElement, {
6270
- x: relativeX * viewPortSize[0],
6271
- y: relativeY * viewPortSize[1]
6272
- });
6273
- if (declutterTree.collides(bbox)) {
6274
- continue;
6275
- }
6276
- declutterTree.insert(bbox);
6277
- if (this.layer.drawCollisionBoxes) {
6278
- const collisionBoxDebugElement = this.getCollisionBoxElement(bbox);
6279
- textElements.push(collisionBoxDebugElement);
6280
- }
6281
- textElements.push(textElement);
6282
- }
6283
- replaceChildren(this._element, textElements);
6284
- return this._element;
6209
+ setgeometries(geometries) {
6210
+ this.geometries = geometries;
6211
+ this._extent = void 0;
6285
6212
  }
6286
- getTextElementWithID(id2) {
6287
- const elementId = `text-feature-${id2}`;
6288
- let textElement = this._element.querySelector(`#${elementId}`);
6289
- if (!textElement) {
6290
- textElement = document.createElement("div");
6291
- textElement.id = elementId;
6292
- }
6293
- return textElement;
6213
+ getProjectedGeometries(projection) {
6214
+ return this.geometries.map(
6215
+ (d2) => d2.getProjected(projection, projection.revision)
6216
+ );
6294
6217
  }
6295
- styleTextElement(element, textStyle, position) {
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`;
6218
+ getStyleFunction() {
6219
+ const style2 = this.style;
6220
+ if (!style2) return null;
6221
+ if (typeof style2 === "function") return style2;
6222
+ return () => {
6223
+ return style2;
6224
+ };
6310
6225
  }
6311
- getElementBBox(element, position) {
6312
- if (!element.parentElement) {
6313
- document.body.appendChild(element);
6226
+ containsCoordinate(coordinate) {
6227
+ if (!containsCoordinate(this.getExtent(), coordinate)) {
6228
+ return false;
6314
6229
  }
6315
- const { width, height } = element.getBoundingClientRect();
6316
- if (element.parentElement !== this._element) {
6317
- element.remove();
6230
+ for (const geometries of this.geometries) {
6231
+ if (d3Geo.geoContains(geometries.getGeoJSON(), coordinate)) {
6232
+ return true;
6233
+ }
6318
6234
  }
6319
- return {
6320
- minX: Math.floor(position.x) - width / 2,
6321
- minY: Math.floor(position.y) - height / 2,
6322
- maxX: Math.ceil(position.x + width / 2),
6323
- maxY: Math.ceil(position.y + height / 2)
6324
- };
6325
- }
6326
- getCollisionBoxElement(bbox) {
6327
- const element = document.createElement("div");
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";
6335
- return element;
6336
- }
6337
- }
6338
- class Style {
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;
6235
+ return false;
6343
6236
  }
6344
6237
  clone() {
6345
- return new Style({
6346
- stroke: this.stroke,
6347
- fill: this.fill,
6348
- text: this.text
6238
+ return new Feature({
6239
+ id: this.id,
6240
+ geometries: this.geometries.map((d2) => d2.clone()),
6241
+ properties: this.properties,
6242
+ style: this.style
6349
6243
  });
6350
6244
  }
6245
+ /**
6246
+ * Returns the geometries as a GeoJSON object
6247
+ * @returns {Object} The GeoJSON representation of the geometries
6248
+ */
6249
+ getGeoJSON() {
6250
+ const geometries = this.geometries.map((d2) => d2.getGeoJSON());
6251
+ if (geometries.length === 1) return geometries[0];
6252
+ return {
6253
+ type: "Feature",
6254
+ geometry: this._getGeometryGeoJSON(),
6255
+ properties: this.properties
6256
+ };
6257
+ }
6258
+ _getGeometryGeoJSON() {
6259
+ const geometries = this.geometries.map((d2) => d2.getGeoJSON());
6260
+ if (geometries.length === 0) throw new Error("Feature has no geometries");
6261
+ if (geometries.length === 1) return geometries[0];
6262
+ if (geometries[0].type === "Polygon") {
6263
+ return {
6264
+ type: "MultiPolygon",
6265
+ coordinates: geometries.map((d2) => d2.coordinates)
6266
+ };
6267
+ } else if (geometries[0].type === "LineString") {
6268
+ return {
6269
+ type: "MultiLineString",
6270
+ coordinates: geometries.map((d2) => d2.coordinates)
6271
+ };
6272
+ } else if (geometries[0].type === "Point") {
6273
+ return {
6274
+ type: "MultiPoint",
6275
+ coordinates: geometries.map((d2) => d2.coordinates)
6276
+ };
6277
+ }
6278
+ throw new Error("Could not determine geometry type");
6279
+ }
6351
6280
  }
6352
6281
  function memoise(fn) {
6353
6282
  let called = false;
@@ -6365,928 +6294,1031 @@
6365
6294
  return lastResult;
6366
6295
  };
6367
6296
  }
6368
- function toRgba(color2, opacity = 1) {
6369
- color2 = color2.replace(/\s+/g, "").toLowerCase();
6370
- if (color2.startsWith("#")) {
6371
- color2 = color2.replace(/^#/, "");
6372
- if (color2.length === 3) {
6373
- color2 = color2.split("").map((char) => char + char).join("");
6374
- }
6375
- let r = parseInt(color2.substring(0, 2), 16);
6376
- let g = parseInt(color2.substring(2, 4), 16);
6377
- let b = parseInt(color2.substring(4, 6), 16);
6378
- return `rgba(${r}, ${g}, ${b}, ${opacity})`;
6379
- }
6380
- let rgbaMatch = color2.match(/^rgba?\((\d+),(\d+),(\d+)(?:,(\d+(\.\d+)?))?\)$/);
6381
- if (rgbaMatch) {
6382
- let r = parseInt(rgbaMatch[1], 10);
6383
- let g = parseInt(rgbaMatch[2], 10);
6384
- let b = parseInt(rgbaMatch[3], 10);
6385
- let a = rgbaMatch[4] !== void 0 ? parseFloat(rgbaMatch[4]) : opacity;
6386
- return `rgba(${r}, ${g}, ${b}, ${a})`;
6387
- }
6388
- throw new Error("Unsupported color format");
6389
- }
6390
- class Stroke {
6391
- constructor(options) {
6392
- this.color = (options == null ? void 0 : options.color) || "#121212";
6393
- this.width = (options == null ? void 0 : options.width) || 0.5;
6394
- this.opacity = (options == null ? void 0 : options.opacity) || 1;
6395
- this._getRgba = memoise(toRgba);
6396
- }
6397
- getRgba() {
6398
- return this._getRgba(this.color, this.opacity);
6399
- }
6400
- }
6401
- class Fill {
6402
- constructor(options) {
6403
- this.color = (options == null ? void 0 : options.color) || "#CCC";
6404
- this.opacity = (options == null ? void 0 : options.opacity) || 1;
6405
- this._getRgba = memoise(toRgba);
6297
+ class Geometry {
6298
+ /**
6299
+ * Represents vector geometry
6300
+ * @constructor
6301
+ * @param {Object} options
6302
+ * @param {string} options.type - The type of geometry (e.g., 'Point', 'LineString', 'Polygon')
6303
+ * @param {Array} options.extent - The extent of the geometry (e.g., [xmin, ymin, xmax, ymax])
6304
+ * @param {Array} options.coordinates - The coordinates of the geometry (e.g., [[x1, y1], [x2, y2], ...])
6305
+ */
6306
+ constructor({ type, extent, coordinates }) {
6307
+ this.type = type;
6308
+ this.extent = extent;
6309
+ this.coordinates = coordinates;
6310
+ this.getProjected = memoise(this._getProjected).bind(this);
6406
6311
  }
6407
- getRgba() {
6408
- return this._getRgba(this.color, this.opacity);
6312
+ /**
6313
+ * Returns the geometry as a GeoJSON object
6314
+ * @function
6315
+ * @param {import("../projection").Projection} projection - The projection to use for the geometry
6316
+ * @returns {Object} A GeoJSON representation of the projected geometry
6317
+ * @private
6318
+ */
6319
+ // eslint-disable-next-line no-unused-vars
6320
+ _getProjected(projection) {
6321
+ throw new Error("Not implemented");
6409
6322
  }
6410
- }
6411
- class Text {
6412
- constructor(options) {
6413
- this.content = options == null ? void 0 : options.content;
6414
- this.fontFamily = (options == null ? void 0 : options.fontFamily) || "var(--text-sans)";
6415
- this.fontSize = (options == null ? void 0 : options.fontSize) || "17px";
6416
- this.fontWeight = (options == null ? void 0 : options.fontWeight) || "400";
6417
- this.lineHeight = (options == null ? void 0 : options.lineHeight) || 1.3;
6418
- this.color = (options == null ? void 0 : options.color) || "#121212";
6419
- this.textShadow = (options == null ? void 0 : options.textShadow) || "1px 1px 0px #f6f6f6, -1px -1px 0px #f6f6f6, -1px 1px 0px #f6f6f6, 1px -1px #f6f6f6";
6323
+ /**
6324
+ * Returns the geometry as a GeoJSON object
6325
+ * @returns {Object} The GeoJSON representation of the geometry
6326
+ */
6327
+ getGeoJSON() {
6328
+ return {
6329
+ type: this.type,
6330
+ coordinates: this.coordinates
6331
+ };
6420
6332
  }
6421
6333
  }
6422
- class TinyQueue {
6423
- constructor(data = [], compare = defaultCompare) {
6424
- this.data = data;
6425
- this.length = this.data.length;
6426
- this.compare = compare;
6427
- if (this.length > 0) {
6428
- for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
6429
- }
6430
- }
6431
- push(item) {
6432
- this.data.push(item);
6433
- this.length++;
6434
- this._up(this.length - 1);
6334
+ class LineString extends Geometry {
6335
+ constructor({ type = "LineString", extent, coordinates }) {
6336
+ super({ type, extent, coordinates });
6435
6337
  }
6436
- pop() {
6437
- if (this.length === 0) return void 0;
6438
- const top = this.data[0];
6439
- const bottom = this.data.pop();
6440
- this.length--;
6441
- if (this.length > 0) {
6442
- this.data[0] = bottom;
6443
- this._down(0);
6338
+ _getProjected(projection) {
6339
+ const projected = [];
6340
+ for (const point of this.coordinates) {
6341
+ projected.push(projection(point));
6444
6342
  }
6445
- return top;
6446
- }
6447
- peek() {
6448
- return this.data[0];
6343
+ return {
6344
+ type: this.type,
6345
+ coordinates: projected
6346
+ };
6449
6347
  }
6450
- _up(pos) {
6451
- const { data, compare } = this;
6452
- const item = data[pos];
6453
- while (pos > 0) {
6454
- const parent = pos - 1 >> 1;
6455
- const current = data[parent];
6456
- if (compare(item, current) >= 0) break;
6457
- data[pos] = current;
6458
- pos = parent;
6459
- }
6460
- data[pos] = item;
6348
+ }
6349
+ class Polygon extends Geometry {
6350
+ constructor({ type = "Polygon", extent, coordinates }) {
6351
+ super({ type, extent, coordinates });
6461
6352
  }
6462
- _down(pos) {
6463
- const { data, compare } = this;
6464
- const halfLength = this.length >> 1;
6465
- const item = data[pos];
6466
- while (pos < halfLength) {
6467
- let left = (pos << 1) + 1;
6468
- let best = data[left];
6469
- const right = left + 1;
6470
- if (right < this.length && compare(data[right], best) < 0) {
6471
- left = right;
6472
- best = data[right];
6353
+ _getProjected(projection) {
6354
+ const projected = [];
6355
+ const rings = this.coordinates;
6356
+ for (const ring of rings) {
6357
+ const projectedRing = [];
6358
+ for (const point of ring) {
6359
+ const projectedPoint = projection(point);
6360
+ if (projectedPoint) {
6361
+ projectedRing.push(projectedPoint);
6362
+ } else {
6363
+ break;
6364
+ }
6473
6365
  }
6474
- if (compare(best, item) >= 0) break;
6475
- data[pos] = best;
6476
- pos = left;
6477
- }
6478
- data[pos] = item;
6479
- }
6480
- }
6481
- function defaultCompare(a, b) {
6482
- return a < b ? -1 : a > b ? 1 : 0;
6483
- }
6484
- function knn(tree, x, y, n2, predicate, maxDistance) {
6485
- let node = tree.data;
6486
- const result = [];
6487
- const toBBox = tree.toBBox;
6488
- const queue = new TinyQueue(void 0, compareDist);
6489
- while (node) {
6490
- for (let i = 0; i < node.children.length; i++) {
6491
- const child = node.children[i];
6492
- const dist = boxDist(x, y, node.leaf ? toBBox(child) : child);
6493
- {
6494
- queue.push({
6495
- node: child,
6496
- isItem: node.leaf,
6497
- dist
6498
- });
6366
+ if (projectedRing.length > 0) {
6367
+ projected.push(projectedRing);
6499
6368
  }
6500
6369
  }
6501
- while (queue.length && queue.peek().isItem) {
6502
- const candidate = queue.pop().node;
6503
- if (!predicate || predicate(candidate))
6504
- result.push(candidate);
6505
- if (result.length === n2) return result;
6506
- }
6507
- node = queue.pop();
6508
- if (node) node = node.node;
6370
+ return {
6371
+ type: this.type,
6372
+ coordinates: projected
6373
+ };
6509
6374
  }
6510
- return result;
6511
- }
6512
- function compareDist(a, b) {
6513
- return a.dist - b.dist;
6514
- }
6515
- function boxDist(x, y, box) {
6516
- const dx = axisDist(x, box.minX, box.maxX), dy = axisDist(y, box.minY, box.maxY);
6517
- return dx * dx + dy * dy;
6518
- }
6519
- function axisDist(k, min, max) {
6520
- return k < min ? min - k : k <= max ? 0 : k - max;
6521
- }
6522
- class VectorSource {
6523
- constructor({ features }) {
6524
- this.dispatcher = new Dispatcher(this);
6525
- this._featuresRtree = new RBush();
6526
- this.setFeatures(features);
6375
+ getOuterRing() {
6376
+ return this.coordinates[0];
6527
6377
  }
6528
- tearDown() {
6529
- this.dispatcher = null;
6378
+ setOuterRing(coordinates) {
6379
+ this.coordinates[0] = coordinates;
6530
6380
  }
6531
- getFeatures() {
6532
- return this._features;
6381
+ setCoordinates(coordinates) {
6382
+ this.coordinates = coordinates;
6533
6383
  }
6534
- getFeaturesAtCoordinate(coordinate) {
6535
- const [lon, lat] = coordinate;
6536
- const features = knn(
6537
- this._featuresRtree,
6538
- lon,
6539
- lat,
6540
- 10,
6541
- (d2) => d2.feature.containsCoordinate(coordinate)
6542
- ).map((d2) => {
6543
- const midX = d2.minX + (d2.minX + d2.maxX) / 2;
6544
- const midY = d2.minY + (d2.minY + d2.maxY) / 2;
6545
- d2.distance = Math.hypot(midX - lon, midY - lat);
6546
- return d2;
6384
+ clone() {
6385
+ return new Polygon({
6386
+ extent: this.extent,
6387
+ coordinates: JSON.parse(JSON.stringify(this.coordinates))
6547
6388
  });
6548
- features.sort((a, b) => a.distance - b.distance);
6549
- return features.map((d2) => d2.feature);
6550
6389
  }
6551
- getFeaturesInExtent(extent) {
6552
- const [minX, minY, maxX, maxY] = extent;
6553
- return this._featuresRtree.search({ minX, minY, maxX, maxY }).map((d2) => d2.feature);
6390
+ }
6391
+ class Point extends Geometry {
6392
+ constructor({ type = "Point", coordinates }) {
6393
+ super({ type, extent: null, coordinates });
6394
+ this.extent = [...coordinates, ...coordinates];
6554
6395
  }
6555
- setFeatures(features) {
6556
- this._featuresRtree.clear();
6557
- for (const feature of features) {
6558
- const [minX, minY, maxX, maxY] = feature.getExtent();
6559
- this._featuresRtree.insert({
6560
- minX: Math.floor(minX),
6561
- minY: Math.floor(minY),
6562
- maxX: Math.ceil(maxX),
6563
- maxY: Math.ceil(maxY),
6564
- feature
6565
- });
6566
- }
6567
- this._features = features;
6568
- this.dispatcher.dispatch(MapEvent.CHANGE);
6396
+ _getProjected(projection) {
6397
+ return {
6398
+ type: this.type,
6399
+ coordinates: projection(this.coordinates)
6400
+ };
6569
6401
  }
6570
6402
  }
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]);
6403
+ class GeoJSON {
6404
+ readFeaturesFromObject(object) {
6405
+ const geoJSONObject = object;
6406
+ let features = null;
6407
+ if (geoJSONObject["type"] === "FeatureCollection") {
6408
+ const geoJSONFeatureCollection = object;
6409
+ features = [];
6410
+ const geoJSONFeatures = geoJSONFeatureCollection["features"];
6411
+ for (let i = 0, ii = geoJSONFeatures.length; i < ii; ++i) {
6412
+ const featureObject = this.readFeatureFromObject(geoJSONFeatures[i]);
6413
+ if (!featureObject) {
6414
+ continue;
6415
+ }
6416
+ features.push(featureObject);
6417
+ }
6418
+ } else if (geoJSONObject["type"] === "Feature") {
6419
+ features = [this.readFeatureFromObject(geoJSONObject)];
6420
+ } else if (Array.isArray(geoJSONObject)) {
6421
+ features = [];
6422
+ for (let i = 0, ii = geoJSONObject.length; i < ii; ++i) {
6423
+ const featureObject = this.readFeatureFromObject(geoJSONObject[i]);
6424
+ if (!featureObject) {
6425
+ continue;
6426
+ }
6427
+ features.push(featureObject);
6428
+ }
6429
+ } else {
6430
+ try {
6431
+ const geometries = this.readGeometriesFromObject(geoJSONObject);
6432
+ const feature = new Feature({ geometries });
6433
+ features = [feature];
6434
+ } catch {
6435
+ console.warn("Unable to interpret GeoJSON:", geoJSONObject);
6436
+ return;
6437
+ }
6438
+ }
6439
+ return features.flat();
6440
+ }
6441
+ readFeatureFromObject(geoJSONObject) {
6442
+ const geometries = this.readGeometriesFromObject(geoJSONObject["geometry"]);
6443
+ if (geometries.length > 0) {
6444
+ return new Feature({
6445
+ id: geoJSONObject["id"],
6446
+ geometries,
6447
+ properties: geoJSONObject["properties"]
6448
+ });
6449
+ }
6597
6450
  return null;
6598
6451
  }
6452
+ readGeometriesFromObject(geometry) {
6453
+ const geometries = [];
6454
+ if (geometry.type === "Polygon") {
6455
+ const polygon = this.readPolygonForCoordinates(geometry.coordinates);
6456
+ geometries.push(polygon);
6457
+ } else if (geometry.type === "MultiPolygon") {
6458
+ for (const polygonCoordinates of geometry.coordinates) {
6459
+ const polygon = this.readPolygonForCoordinates(polygonCoordinates);
6460
+ geometries.push(polygon);
6461
+ }
6462
+ } else if (geometry.type === "LineString") {
6463
+ const lineString = this.readLineStringForCoordinates(geometry.coordinates);
6464
+ geometries.push(lineString);
6465
+ } else if (geometry.type === "MultiLineString") {
6466
+ for (const lineStringCoordinates of geometry.coordinates) {
6467
+ const lineString = this.readLineStringForCoordinates(
6468
+ lineStringCoordinates
6469
+ );
6470
+ geometries.push(lineString);
6471
+ }
6472
+ } else if (geometry.type === "Point") {
6473
+ const point = this.readPointForCoordinates(geometry.coordinates);
6474
+ geometries.push(point);
6475
+ }
6476
+ return geometries;
6477
+ }
6478
+ readPolygonForCoordinates(coordinates) {
6479
+ const outerRing = coordinates[0];
6480
+ const extent = extentForCoordinates(outerRing);
6481
+ return new Polygon({ extent, coordinates });
6482
+ }
6483
+ readLineStringForCoordinates(coordinates) {
6484
+ const extent = extentForCoordinates(coordinates);
6485
+ return new LineString({ extent, coordinates });
6486
+ }
6487
+ readPointForCoordinates(coordinates) {
6488
+ return new Point({ coordinates });
6489
+ }
6490
+ }
6491
+ class FeatureCollection {
6599
6492
  /**
6600
- * @param {import("../Feature").Feature[]} features
6601
- * @param {TextLayerOptions} options
6493
+ * Create a feature collection from GeoJSON features.
6494
+ * @param {Object[]} geoJSON - The GeoJSON object
6495
+ * @returns {FeatureCollection} The feature collection
6602
6496
  */
6603
- static with(features, options) {
6604
- const source = new VectorSource({ features });
6605
- return new TextLayer({ source, ...options });
6497
+ static fromGeoJSON(geoJSON) {
6498
+ const features = new GeoJSON().readFeaturesFromObject(geoJSON);
6499
+ return new FeatureCollection(features);
6606
6500
  }
6607
6501
  /**
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]
6502
+ * Create a feature collection.
6503
+ * @constructor
6504
+ * @param {import("./Feature").Feature[]} features - The features to put in the collection
6615
6505
  */
6616
- constructor({
6617
- source,
6618
- style: style2,
6619
- minZoom = 0,
6620
- opacity = 1,
6621
- declutter = true,
6622
- drawCollisionBoxes = false
6623
- }) {
6624
- this.source = source;
6625
- this._style = style2;
6626
- this.minZoom = minZoom;
6627
- this.opacity = opacity;
6628
- this.declutter = declutter;
6629
- this.drawCollisionBoxes = drawCollisionBoxes;
6630
- this.renderer = new TextLayerRenderer(this);
6631
- this.dispatcher = new Dispatcher(this);
6632
- }
6633
- tearDown() {
6634
- this.dispatcher = null;
6635
- }
6636
- get style() {
6637
- if (this._style) return this._style;
6638
- const defaultStyle = new Style({
6639
- text: new Text()
6640
- });
6641
- return defaultStyle;
6642
- }
6643
- set style(style2) {
6644
- this._style = style2;
6645
- this.dispatcher.dispatch(MapEvent.CHANGE);
6506
+ constructor(features) {
6507
+ this.features = features;
6646
6508
  }
6647
- getExtent() {
6648
- if (this._extent) return this._extent;
6649
- const features = this.source.getFeatures();
6650
- const extent = features.reduce((combinedExtent, feature) => {
6651
- const featureExtent = feature.getExtent();
6652
- if (!combinedExtent) return featureExtent;
6653
- return combineExtents(featureExtent, combinedExtent);
6654
- }, null);
6655
- this._extent = extent;
6656
- return extent;
6509
+ }
6510
+ class FeatureRenderer {
6511
+ constructor() {
6512
+ this.drawingFunction = d3Geo.geoPath();
6657
6513
  }
6658
- getStyleFunction() {
6659
- const style2 = this.style;
6660
- if (typeof style2 === "function") return style2;
6661
- return () => {
6662
- return style2;
6663
- };
6514
+ setStyle(style2) {
6515
+ this.style = style2;
6664
6516
  }
6665
- renderFrame(frameState, targetElement) {
6666
- return this.renderer.renderFrame(frameState, targetElement);
6517
+ render(frameState, feature, context) {
6518
+ if (!this.style) {
6519
+ return;
6520
+ }
6521
+ const { projection, transform, pixelRatio } = frameState.viewState;
6522
+ const { stroke, fill } = this.style;
6523
+ this.drawingFunction.context(context);
6524
+ context.beginPath();
6525
+ const geometries = feature.getProjectedGeometries(projection);
6526
+ if (frameState.debug) {
6527
+ try {
6528
+ validateGeometries(geometries);
6529
+ } catch {
6530
+ console.error(
6531
+ `Invalid geometry. Feature skipped during rendering. Click here to inspect geometry: ${generateDebugUrl(feature)}
6532
+ `,
6533
+ feature
6534
+ );
6535
+ }
6536
+ }
6537
+ for (const geometry of geometries) {
6538
+ this.drawingFunction(geometry);
6539
+ }
6540
+ if (fill) {
6541
+ context.fillStyle = fill.getRgba();
6542
+ context.fill();
6543
+ }
6544
+ if (stroke) {
6545
+ context.lineWidth = stroke.width * pixelRatio / transform.k;
6546
+ context.strokeStyle = stroke.getRgba();
6547
+ context.stroke();
6548
+ }
6667
6549
  }
6668
6550
  }
6669
- class VectorLayerRenderer {
6551
+ const textPadding = {
6552
+ top: 20,
6553
+ right: 20,
6554
+ bottom: 20,
6555
+ left: 20
6556
+ };
6557
+ class TextLayerRenderer {
6670
6558
  constructor(layer) {
6671
6559
  this.layer = layer;
6672
6560
  this.featureRenderer = new FeatureRenderer();
6561
+ this._element = document.createElement("div");
6562
+ this._element.className = "gv-text-layer";
6563
+ const style2 = this._element.style;
6564
+ style2.position = "absolute";
6565
+ style2.width = "100%";
6566
+ style2.height = "100%";
6567
+ style2.pointerEvents = "none";
6568
+ style2.overflow = "hidden";
6673
6569
  }
6674
6570
  renderFrame(frameState, targetElement) {
6675
6571
  if (this.layer.opacity === 0) return targetElement;
6676
- const { projection, sizeInPixels, visibleExtent, transform } = frameState.viewState;
6677
- const container2 = this.getOrCreateContainer(targetElement, sizeInPixels);
6678
- const context = container2.firstElementChild.getContext("2d");
6679
- context.save();
6680
- context.translate(transform.x, transform.y);
6681
- context.scale(transform.k, transform.k);
6682
- context.lineJoin = "round";
6683
- context.lineCap = "round";
6684
- context.globalAlpha = this.layer.opacity;
6572
+ const { declutterTree } = frameState;
6573
+ const { projection, viewPortSize, sizeInPixels, visibleExtent, transform } = frameState.viewState;
6574
+ this._element.style.opacity = this.layer.opacity;
6685
6575
  const source = this.layer.source;
6686
6576
  const features = source.getFeaturesInExtent(visibleExtent);
6577
+ const textElements = [];
6687
6578
  for (const feature of features) {
6579
+ const geometries = feature.getProjectedGeometries(projection);
6580
+ const point = geometries.find((d2) => d2.type === "Point");
6581
+ if (!point) {
6582
+ throw new Error(
6583
+ `Expected Point geometry for feature in TextLayer: ${feature}`
6584
+ );
6585
+ }
6688
6586
  const styleFunction2 = feature.getStyleFunction() || this.layer.getStyleFunction();
6689
6587
  const featureStyle = styleFunction2(feature);
6690
- if ((featureStyle == null ? void 0 : featureStyle.stroke) || (featureStyle == null ? void 0 : featureStyle.fill)) {
6691
- context.save();
6692
- this.featureRenderer.setStyle(featureStyle);
6693
- this.featureRenderer.render(frameState, feature, context);
6694
- context.restore();
6695
- }
6696
- }
6697
- if (Object.prototype.hasOwnProperty.call(projection, "getCompositionBorders")) {
6698
- context.beginPath();
6699
- context.lineWidth = 1 / transform.k;
6700
- context.strokeStyle = "#999";
6701
- projection.drawCompositionBorders(context);
6702
- context.stroke();
6588
+ const textElement = this.getTextElementWithID(feature.uid);
6589
+ textElement.innerText = featureStyle.text.content;
6590
+ const [relativeX, relativeY] = transform.apply(point.coordinates).map((d2, i) => d2 / sizeInPixels[i]);
6591
+ const position = {
6592
+ left: `${relativeX * 100}%`,
6593
+ top: `${relativeY * 100}%`
6594
+ };
6595
+ this.styleTextElement(textElement, featureStyle.text, position);
6596
+ const bbox = this.getElementBBox(textElement, {
6597
+ x: relativeX * viewPortSize[0],
6598
+ y: relativeY * viewPortSize[1]
6599
+ });
6600
+ if (declutterTree.collides(bbox)) {
6601
+ continue;
6602
+ }
6603
+ declutterTree.insert(bbox);
6604
+ if (this.layer.drawCollisionBoxes) {
6605
+ const collisionBoxDebugElement = this.getCollisionBoxElement(bbox);
6606
+ textElements.push(collisionBoxDebugElement);
6607
+ }
6608
+ textElements.push(textElement);
6703
6609
  }
6704
- context.restore();
6705
- return container2;
6610
+ replaceChildren(this._element, textElements);
6611
+ return this._element;
6706
6612
  }
6707
- getOrCreateContainer(targetElement, sizeInPixels) {
6708
- let container2 = null;
6709
- let containerReused = false;
6710
- let canvas = targetElement && targetElement.firstElementChild;
6711
- if (canvas instanceof HTMLCanvasElement) {
6712
- container2 = targetElement;
6713
- containerReused = true;
6714
- } else if (this._container) {
6715
- container2 = this._container;
6716
- } else {
6717
- container2 = this.createContainer();
6718
- }
6719
- if (!containerReused) {
6720
- const canvas2 = container2.firstElementChild;
6721
- canvas2.width = sizeInPixels[0];
6722
- canvas2.height = sizeInPixels[1];
6613
+ getTextElementWithID(id2) {
6614
+ const elementId = `text-feature-${id2}`;
6615
+ let textElement = this._element.querySelector(`#${elementId}`);
6616
+ if (!textElement) {
6617
+ textElement = document.createElement("div");
6618
+ textElement.id = elementId;
6723
6619
  }
6724
- this._container = container2;
6725
- return container2;
6620
+ return textElement;
6726
6621
  }
6727
- createContainer() {
6728
- const container2 = document.createElement("div");
6729
- container2.className = "gv-map-layer";
6730
- let style2 = container2.style;
6731
- style2.position = "absolute";
6732
- style2.width = "100%";
6733
- style2.height = "100%";
6734
- const canvas = document.createElement("canvas");
6735
- style2 = canvas.style;
6622
+ styleTextElement(element, textStyle, position) {
6623
+ const style2 = element.style;
6736
6624
  style2.position = "absolute";
6737
- style2.width = "100%";
6738
- style2.height = "100%";
6739
- container2.appendChild(canvas);
6740
- return container2;
6741
- }
6742
- }
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
- */
6767
- static with(features, options) {
6768
- const source = new VectorSource({ features });
6769
- return new VectorLayer({ source, ...options });
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
- */
6779
- constructor({
6780
- source,
6781
- style: style2,
6782
- minZoom = 0,
6783
- opacity = 1,
6784
- hitDetectionEnabled = true
6785
- }) {
6786
- this.dispatcher = new Dispatcher(this);
6787
- this.renderer = new VectorLayerRenderer(this);
6788
- this.source = source;
6789
- this._style = style2;
6790
- this.minZoom = minZoom;
6791
- this.opacity = opacity;
6792
- this.hitDetectionEnabled = hitDetectionEnabled;
6793
- }
6794
- get source() {
6795
- return this._source;
6625
+ style2.transform = `translate(-50%, -50%)`;
6626
+ style2.left = position.left;
6627
+ style2.top = position.top;
6628
+ style2.textAlign = "center";
6629
+ style2.whiteSpace = "nowrap";
6630
+ style2.fontFamily = textStyle.fontFamily;
6631
+ style2.fontSize = textStyle.fontSize;
6632
+ style2.fontWeight = textStyle.fontWeight;
6633
+ style2.lineHeight = textStyle.lineHeight;
6634
+ style2.color = textStyle.color;
6635
+ style2.textShadow = textStyle.textShadow;
6636
+ style2.padding = `${textPadding.top}px ${textPadding.right}px ${textPadding.bottom}px ${textPadding.left}px`;
6796
6637
  }
6797
- set source(source) {
6798
- if (this._source && source !== this._source) {
6799
- this._source.tearDown();
6638
+ getElementBBox(element, position) {
6639
+ if (!element.parentElement) {
6640
+ document.body.appendChild(element);
6800
6641
  }
6801
- this._source = source;
6802
- source.on(MapEvent.CHANGE, () => {
6803
- this._extent = null;
6804
- this.dispatcher.dispatch(MapEvent.CHANGE);
6805
- });
6642
+ const { width, height } = element.getBoundingClientRect();
6643
+ if (element.parentElement !== this._element) {
6644
+ element.remove();
6645
+ }
6646
+ return {
6647
+ minX: Math.floor(position.x) - width / 2,
6648
+ minY: Math.floor(position.y) - height / 2,
6649
+ maxX: Math.ceil(position.x + width / 2),
6650
+ maxY: Math.ceil(position.y + height / 2)
6651
+ };
6806
6652
  }
6807
- setRawProjection(projection) {
6808
- this.projection = projection;
6653
+ getCollisionBoxElement(bbox) {
6654
+ const element = document.createElement("div");
6655
+ const style2 = element.style;
6656
+ style2.position = "absolute";
6657
+ style2.left = `${bbox.minX}px`;
6658
+ style2.top = `${bbox.minY}px`;
6659
+ style2.width = `${bbox.maxX - bbox.minX}px`;
6660
+ style2.height = `${bbox.maxY - bbox.minY}px`;
6661
+ style2.border = "2px solid black";
6662
+ return element;
6809
6663
  }
6810
- tearDown() {
6811
- this.dispatcher = null;
6664
+ }
6665
+ class Style {
6666
+ constructor(properties) {
6667
+ this.stroke = properties == null ? void 0 : properties.stroke;
6668
+ this.fill = properties == null ? void 0 : properties.fill;
6669
+ this.text = properties == null ? void 0 : properties.text;
6812
6670
  }
6813
- get style() {
6814
- if (this._style) return this._style;
6815
- const defaultStyle = new Style({
6816
- stroke: new Stroke()
6671
+ clone() {
6672
+ return new Style({
6673
+ stroke: this.stroke,
6674
+ fill: this.fill,
6675
+ text: this.text
6817
6676
  });
6818
- return defaultStyle;
6819
6677
  }
6820
- set style(style2) {
6821
- this._style = style2;
6822
- this.dispatcher.dispatch(MapEvent.CHANGE);
6678
+ }
6679
+ function toRgba(color2, opacity = 1) {
6680
+ color2 = color2.replace(/\s+/g, "").toLowerCase();
6681
+ if (color2.startsWith("#")) {
6682
+ color2 = color2.replace(/^#/, "");
6683
+ if (color2.length === 3) {
6684
+ color2 = color2.split("").map((char) => char + char).join("");
6685
+ }
6686
+ let r = parseInt(color2.substring(0, 2), 16);
6687
+ let g = parseInt(color2.substring(2, 4), 16);
6688
+ let b = parseInt(color2.substring(4, 6), 16);
6689
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
6823
6690
  }
6824
- getStyleFunction() {
6825
- const style2 = this.style;
6826
- if (typeof style2 === "function") return style2;
6827
- return () => {
6828
- return style2;
6829
- };
6691
+ let rgbaMatch = color2.match(/^rgba?\((\d+),(\d+),(\d+)(?:,(\d+(\.\d+)?))?\)$/);
6692
+ if (rgbaMatch) {
6693
+ let r = parseInt(rgbaMatch[1], 10);
6694
+ let g = parseInt(rgbaMatch[2], 10);
6695
+ let b = parseInt(rgbaMatch[3], 10);
6696
+ let a = rgbaMatch[4] !== void 0 ? parseFloat(rgbaMatch[4]) : opacity;
6697
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
6830
6698
  }
6831
- getExtent() {
6832
- if (this._extent) return this._extent;
6833
- const features = this.source.getFeatures();
6834
- const extent = features.reduce((combinedExtent, feature) => {
6835
- const featureExtent = feature.getExtent();
6836
- if (!combinedExtent) return featureExtent;
6837
- return combineExtents(featureExtent, combinedExtent);
6838
- }, null);
6839
- this._extent = extent;
6840
- return extent;
6699
+ throw new Error("Unsupported color format");
6700
+ }
6701
+ class Stroke {
6702
+ constructor(options) {
6703
+ this.color = (options == null ? void 0 : options.color) || "#121212";
6704
+ this.width = (options == null ? void 0 : options.width) || 0.5;
6705
+ this.opacity = (options == null ? void 0 : options.opacity) || 1;
6706
+ this._getRgba = memoise(toRgba);
6841
6707
  }
6842
- findFeatures(coordinate) {
6843
- if (!this.hitDetectionEnabled) return;
6844
- return this.source.getFeaturesAtCoordinate(coordinate);
6708
+ getRgba() {
6709
+ return this._getRgba(this.color, this.opacity);
6845
6710
  }
6846
- renderFrame(frameState, targetElement) {
6847
- return this.renderer.renderFrame(frameState, targetElement);
6711
+ }
6712
+ class Fill {
6713
+ constructor(options) {
6714
+ this.color = (options == null ? void 0 : options.color) || "#CCC";
6715
+ this.opacity = (options == null ? void 0 : options.opacity) || 1;
6716
+ this._getRgba = memoise(toRgba);
6717
+ }
6718
+ getRgba() {
6719
+ return this._getRgba(this.color, this.opacity);
6848
6720
  }
6849
6721
  }
6850
- function interpolateFeatures(currentFeatures, newFeatures, { interpolate: interpolate2, separate, combine }) {
6851
- if (currentFeatures.length !== newFeatures.length) {
6852
- throw new Error(
6853
- "interpolateFeatures expects an equal number of features for start and end"
6854
- );
6722
+ class Text {
6723
+ constructor(options) {
6724
+ this.content = options == null ? void 0 : options.content;
6725
+ this.fontFamily = (options == null ? void 0 : options.fontFamily) || "var(--text-sans)";
6726
+ this.fontSize = (options == null ? void 0 : options.fontSize) || "17px";
6727
+ this.fontWeight = (options == null ? void 0 : options.fontWeight) || "400";
6728
+ this.lineHeight = (options == null ? void 0 : options.lineHeight) || 1.3;
6729
+ this.color = (options == null ? void 0 : options.color) || "#121212";
6730
+ this.textShadow = (options == null ? void 0 : options.textShadow) || "1px 1px 0px #f6f6f6, -1px -1px 0px #f6f6f6, -1px 1px 0px #f6f6f6, 1px -1px #f6f6f6";
6855
6731
  }
6856
- const featureInterpolators = [];
6857
- for (let i = 0; i < currentFeatures.length; i++) {
6858
- const geometryInterpolators = [];
6859
- const currentGeometries = currentFeatures[i].geometries;
6860
- const newGeometries = newFeatures[i].geometries;
6861
- if (newGeometries.length === currentGeometries.length) {
6862
- for (let e = 0; e < currentGeometries.length; e++) {
6863
- const currentGeometry = currentGeometries[e];
6864
- const newGeometry = newGeometries[e];
6865
- if (currentGeometry.type !== "Polygon" || newGeometry.type !== "Polygon") {
6866
- throw new Error("interpolateFeatures expects only Polygon geometry");
6867
- }
6868
- const shapeInterpolator = interpolate2(
6869
- currentGeometries[e].getOuterRing(),
6870
- newGeometries[e].getOuterRing(),
6871
- { string: false }
6872
- );
6873
- geometryInterpolators.push({
6874
- type: "default",
6875
- interpolator: shapeInterpolator
6876
- });
6877
- }
6878
- } else if (currentGeometries.length === 1 && newGeometries.length > 1) {
6879
- const separationInterpolator = separate(
6880
- currentGeometries[0].getOuterRing(),
6881
- newGeometries.map((geometry) => geometry.getOuterRing()),
6882
- { string: false, single: true }
6883
- );
6884
- geometryInterpolators.push({
6885
- type: "separate",
6886
- interpolator: separationInterpolator
6887
- });
6888
- } else if (currentGeometries.length > 1 && newGeometries.length === 1) {
6889
- const combinationInterpolator = combine(
6890
- currentGeometries.map((geometry) => geometry.getOuterRing()),
6891
- newGeometries[0].getOuterRing(),
6892
- { string: false, single: true }
6893
- );
6894
- geometryInterpolators.push({
6895
- type: "combine",
6896
- interpolator: combinationInterpolator
6897
- });
6898
- } else {
6899
- throw new Error(
6900
- `Encountered an unexpected number of geometries: ${currentGeometries.length} and ${newGeometries.length}`
6901
- );
6732
+ }
6733
+ class TinyQueue {
6734
+ constructor(data = [], compare = defaultCompare) {
6735
+ this.data = data;
6736
+ this.length = this.data.length;
6737
+ this.compare = compare;
6738
+ if (this.length > 0) {
6739
+ for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
6902
6740
  }
6903
- featureInterpolators.push(geometryInterpolators);
6904
6741
  }
6905
- return (t) => {
6906
- if (t >= 1) {
6907
- return newFeatures;
6742
+ push(item) {
6743
+ this.data.push(item);
6744
+ this.length++;
6745
+ this._up(this.length - 1);
6746
+ }
6747
+ pop() {
6748
+ if (this.length === 0) return void 0;
6749
+ const top = this.data[0];
6750
+ const bottom = this.data.pop();
6751
+ this.length--;
6752
+ if (this.length > 0) {
6753
+ this.data[0] = bottom;
6754
+ this._down(0);
6908
6755
  }
6909
- const features = [];
6910
- for (let i = 0; i < featureInterpolators.length; i++) {
6911
- const feature = newFeatures[i].clone();
6912
- const geometries = [];
6913
- const geometryInterpolators = featureInterpolators[i];
6914
- for (const [
6915
- index,
6916
- { type, interpolator }
6917
- ] of geometryInterpolators.entries()) {
6918
- let geometry = feature.geometries[index].clone();
6919
- let interpolated;
6920
- switch (type) {
6921
- case "separate":
6922
- case "combine":
6923
- interpolated = interpolator(t);
6924
- interpolated.forEach((d2) => {
6925
- const polygon = geometry.clone();
6926
- polygon.setCoordinates([d2]);
6927
- geometries.push(polygon);
6928
- });
6929
- break;
6930
- default:
6931
- geometry.setOuterRing(interpolator(t));
6932
- geometries.push(geometry);
6933
- break;
6934
- }
6756
+ return top;
6757
+ }
6758
+ peek() {
6759
+ return this.data[0];
6760
+ }
6761
+ _up(pos) {
6762
+ const { data, compare } = this;
6763
+ const item = data[pos];
6764
+ while (pos > 0) {
6765
+ const parent = pos - 1 >> 1;
6766
+ const current = data[parent];
6767
+ if (compare(item, current) >= 0) break;
6768
+ data[pos] = current;
6769
+ pos = parent;
6770
+ }
6771
+ data[pos] = item;
6772
+ }
6773
+ _down(pos) {
6774
+ const { data, compare } = this;
6775
+ const halfLength = this.length >> 1;
6776
+ const item = data[pos];
6777
+ while (pos < halfLength) {
6778
+ let left = (pos << 1) + 1;
6779
+ let best = data[left];
6780
+ const right = left + 1;
6781
+ if (right < this.length && compare(data[right], best) < 0) {
6782
+ left = right;
6783
+ best = data[right];
6935
6784
  }
6936
- feature.setGeometry(geometries);
6937
- features.push(feature);
6785
+ if (compare(best, item) >= 0) break;
6786
+ data[pos] = best;
6787
+ pos = left;
6938
6788
  }
6939
- return features;
6940
- };
6789
+ data[pos] = item;
6790
+ }
6941
6791
  }
6942
- function interpolateStyles(firstStyle, secondStyle, interpolateColors, interpolateNumbers) {
6943
- const fillInterpolator = interpolateFill(
6944
- firstStyle.fill,
6945
- secondStyle.fill,
6946
- interpolateColors,
6947
- interpolateNumbers
6948
- );
6949
- const strokeInterpolator = interpolateStroke(
6950
- firstStyle.stroke,
6951
- secondStyle.stroke,
6952
- interpolateColors,
6953
- interpolateNumbers
6954
- );
6955
- return (t) => {
6956
- return new Style({
6957
- fill: fillInterpolator(t),
6958
- stroke: strokeInterpolator(t)
6959
- });
6960
- };
6792
+ function defaultCompare(a, b) {
6793
+ return a < b ? -1 : a > b ? 1 : 0;
6961
6794
  }
6962
- function interpolateFill(fillA, fillB, interpolateColors, interpolateNumbers) {
6963
- const colorInterpolator = interpolateColors(
6964
- (fillA == null ? void 0 : fillA.color) ?? "transparent",
6965
- (fillB == null ? void 0 : fillB.color) ?? "transparent"
6966
- );
6967
- const opacityInterpolator = interpolateNumbers(
6968
- (fillA == null ? void 0 : fillA.opacity) ?? 1,
6969
- (fillB == null ? void 0 : fillB.opacity) ?? 1
6970
- );
6971
- return (t) => {
6972
- return new Fill({
6973
- color: colorInterpolator(t),
6974
- opacity: opacityInterpolator(t)
6975
- });
6976
- };
6795
+ function knn(tree, x, y, n2, predicate, maxDistance) {
6796
+ let node = tree.data;
6797
+ const result = [];
6798
+ const toBBox = tree.toBBox;
6799
+ const queue = new TinyQueue(void 0, compareDist);
6800
+ while (node) {
6801
+ for (let i = 0; i < node.children.length; i++) {
6802
+ const child = node.children[i];
6803
+ const dist = boxDist(x, y, node.leaf ? toBBox(child) : child);
6804
+ {
6805
+ queue.push({
6806
+ node: child,
6807
+ isItem: node.leaf,
6808
+ dist
6809
+ });
6810
+ }
6811
+ }
6812
+ while (queue.length && queue.peek().isItem) {
6813
+ const candidate = queue.pop().node;
6814
+ if (!predicate || predicate(candidate))
6815
+ result.push(candidate);
6816
+ if (result.length === n2) return result;
6817
+ }
6818
+ node = queue.pop();
6819
+ if (node) node = node.node;
6820
+ }
6821
+ return result;
6977
6822
  }
6978
- function interpolateStroke(strokeA, strokeB, interpolateColors, interpolateNumbers) {
6979
- const colorInterpolator = interpolateColors(
6980
- (strokeA == null ? void 0 : strokeA.color) ?? "transparent",
6981
- (strokeB == null ? void 0 : strokeB.color) ?? "transparent"
6982
- );
6983
- const opacityInterpolator = interpolateNumbers(
6984
- (strokeA == null ? void 0 : strokeA.opacity) ?? 1,
6985
- (strokeB == null ? void 0 : strokeB.opacity) ?? 1
6986
- );
6987
- const widthInterpolator = interpolateNumbers(
6988
- (strokeA == null ? void 0 : strokeA.width) ?? 1,
6989
- (strokeB == null ? void 0 : strokeB.width) ?? 1
6990
- );
6991
- return (t) => {
6992
- return new Stroke({
6993
- color: colorInterpolator(t),
6994
- opacity: opacityInterpolator(t),
6995
- width: widthInterpolator(t)
6823
+ function compareDist(a, b) {
6824
+ return a.dist - b.dist;
6825
+ }
6826
+ function boxDist(x, y, box) {
6827
+ const dx = axisDist(x, box.minX, box.maxX), dy = axisDist(y, box.minY, box.maxY);
6828
+ return dx * dx + dy * dy;
6829
+ }
6830
+ function axisDist(k, min, max) {
6831
+ return k < min ? min - k : k <= max ? 0 : k - max;
6832
+ }
6833
+ class VectorSource {
6834
+ constructor({ features }) {
6835
+ this.dispatcher = new Dispatcher(this);
6836
+ this._featuresRtree = new RBush();
6837
+ this.setFeatures(features);
6838
+ }
6839
+ tearDown() {
6840
+ this.dispatcher = null;
6841
+ }
6842
+ getFeatures() {
6843
+ return this._features;
6844
+ }
6845
+ getFeaturesAtCoordinate(coordinate) {
6846
+ const [lon, lat] = coordinate;
6847
+ const features = knn(
6848
+ this._featuresRtree,
6849
+ lon,
6850
+ lat,
6851
+ 10,
6852
+ (d2) => d2.feature.containsCoordinate(coordinate)
6853
+ ).map((d2) => {
6854
+ const midX = d2.minX + (d2.minX + d2.maxX) / 2;
6855
+ const midY = d2.minY + (d2.minY + d2.maxY) / 2;
6856
+ d2.distance = Math.hypot(midX - lon, midY - lat);
6857
+ return d2;
6996
6858
  });
6997
- };
6859
+ features.sort((a, b) => a.distance - b.distance);
6860
+ return features.map((d2) => d2.feature);
6861
+ }
6862
+ getFeaturesInExtent(extent) {
6863
+ const [minX, minY, maxX, maxY] = extent;
6864
+ return this._featuresRtree.search({ minX, minY, maxX, maxY }).map((d2) => d2.feature);
6865
+ }
6866
+ setFeatures(features) {
6867
+ this._featuresRtree.clear();
6868
+ for (const feature of features) {
6869
+ const [minX, minY, maxX, maxY] = feature.getExtent();
6870
+ this._featuresRtree.insert({
6871
+ minX: Math.floor(minX),
6872
+ minY: Math.floor(minY),
6873
+ maxX: Math.ceil(maxX),
6874
+ maxY: Math.ceil(maxY),
6875
+ feature
6876
+ });
6877
+ }
6878
+ this._features = features;
6879
+ this.dispatcher.dispatch(MapEvent.CHANGE);
6880
+ }
6998
6881
  }
6999
- class Feature {
6882
+ class TextLayer {
6883
+ /** @param {TextLayerComponentProps} props */
6884
+ static Component({
6885
+ features,
6886
+ style: style2,
6887
+ minZoom,
6888
+ opacity,
6889
+ declutter,
6890
+ drawCollisionBoxes
6891
+ }) {
6892
+ const { registerLayer } = hooks.useContext(MapContext);
6893
+ const layer = hooks.useMemo(
6894
+ () => TextLayer.with(features, {
6895
+ style: style2,
6896
+ minZoom,
6897
+ opacity,
6898
+ declutter,
6899
+ drawCollisionBoxes
6900
+ }),
6901
+ // eslint-disable-next-line react-hooks/exhaustive-deps
6902
+ [features, minZoom, opacity, declutter, drawCollisionBoxes]
6903
+ );
6904
+ registerLayer(layer);
6905
+ hooks.useEffect(() => {
6906
+ layer.style = style2;
6907
+ }, [style2]);
6908
+ return null;
6909
+ }
6910
+ /**
6911
+ * @param {import("../Feature").Feature[]} features
6912
+ * @param {TextLayerOptions} options
6913
+ */
6914
+ static with(features, options) {
6915
+ const source = new VectorSource({ features });
6916
+ return new TextLayer({ source, ...options });
6917
+ }
7000
6918
  /**
7001
- * Represents a feature on the map
7002
6919
  * @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
6920
+ * @param {Object} params
6921
+ * @param {VectorSource} params.source
6922
+ * @param {Style | (() => Style)} [params.style=undefined]
6923
+ * @param {number} [params.minZoom=0]
6924
+ * @param {number} [params.opacity=1]
6925
+ * @param {boolean} [params.declutter=true]
6926
+ * @param {boolean} [params.drawCollisionBoxes=false]
7008
6927
  */
7009
- constructor({ id: id2, geometries, properties, style: style2 }) {
7010
- this.id = id2;
7011
- this.geometries = geometries;
7012
- this.properties = properties;
7013
- this.style = style2;
7014
- this.uid = createUid();
6928
+ constructor({
6929
+ source,
6930
+ style: style2,
6931
+ minZoom = 0,
6932
+ opacity = 1,
6933
+ declutter = true,
6934
+ drawCollisionBoxes = false
6935
+ }) {
6936
+ this.source = source;
6937
+ this._style = style2;
6938
+ this.minZoom = minZoom;
6939
+ this.opacity = opacity;
6940
+ this.declutter = declutter;
6941
+ this.drawCollisionBoxes = drawCollisionBoxes;
6942
+ this.renderer = new TextLayerRenderer(this);
6943
+ this.dispatcher = new Dispatcher(this);
6944
+ }
6945
+ tearDown() {
6946
+ this.dispatcher = null;
6947
+ }
6948
+ get style() {
6949
+ if (this._style) return this._style;
6950
+ const defaultStyle = new Style({
6951
+ text: new Text()
6952
+ });
6953
+ return defaultStyle;
6954
+ }
6955
+ set style(style2) {
6956
+ this._style = style2;
6957
+ this.dispatcher.dispatch(MapEvent.CHANGE);
7015
6958
  }
7016
6959
  getExtent() {
7017
6960
  if (this._extent) return this._extent;
7018
- const extent = this.geometries.reduce((combinedExtent, geometries) => {
7019
- if (!combinedExtent) return geometries.extent;
7020
- return combineExtents(geometries.extent, combinedExtent);
6961
+ const features = this.source.getFeatures();
6962
+ const extent = features.reduce((combinedExtent, feature) => {
6963
+ const featureExtent = feature.getExtent();
6964
+ if (!combinedExtent) return featureExtent;
6965
+ return combineExtents(featureExtent, combinedExtent);
7021
6966
  }, null);
7022
6967
  this._extent = extent;
7023
6968
  return extent;
7024
6969
  }
7025
- setgeometries(geometries) {
7026
- this.geometries = geometries;
7027
- this._extent = void 0;
7028
- }
7029
- getProjectedGeometries(projection) {
7030
- return this.geometries.map(
7031
- (d2) => d2.getProjected(projection, projection.revision)
7032
- );
7033
- }
7034
6970
  getStyleFunction() {
7035
6971
  const style2 = this.style;
7036
- if (!style2) return null;
7037
6972
  if (typeof style2 === "function") return style2;
7038
6973
  return () => {
7039
6974
  return style2;
7040
6975
  };
7041
6976
  }
7042
- containsCoordinate(coordinate) {
7043
- if (!containsCoordinate(this.getExtent(), coordinate)) {
7044
- return false;
7045
- }
7046
- for (const geometries of this.geometries) {
7047
- if (d3Geo.geoContains(geometries.getGeoJSON(), coordinate)) {
7048
- return true;
6977
+ renderFrame(frameState, targetElement) {
6978
+ return this.renderer.renderFrame(frameState, targetElement);
6979
+ }
6980
+ }
6981
+ class VectorLayerRenderer {
6982
+ constructor(layer) {
6983
+ this.layer = layer;
6984
+ this.featureRenderer = new FeatureRenderer();
6985
+ }
6986
+ renderFrame(frameState, targetElement) {
6987
+ if (this.layer.opacity === 0) return targetElement;
6988
+ const { projection, sizeInPixels, visibleExtent, transform } = frameState.viewState;
6989
+ const container2 = this.getOrCreateContainer(targetElement, sizeInPixels);
6990
+ const context = container2.firstElementChild.getContext("2d");
6991
+ context.save();
6992
+ context.translate(transform.x, transform.y);
6993
+ context.scale(transform.k, transform.k);
6994
+ context.lineJoin = "round";
6995
+ context.lineCap = "round";
6996
+ context.globalAlpha = this.layer.opacity;
6997
+ const source = this.layer.source;
6998
+ const features = source.getFeaturesInExtent(visibleExtent);
6999
+ for (const feature of features) {
7000
+ const styleFunction2 = feature.getStyleFunction() || this.layer.getStyleFunction();
7001
+ const featureStyle = styleFunction2(feature);
7002
+ if ((featureStyle == null ? void 0 : featureStyle.stroke) || (featureStyle == null ? void 0 : featureStyle.fill)) {
7003
+ context.save();
7004
+ this.featureRenderer.setStyle(featureStyle);
7005
+ this.featureRenderer.render(frameState, feature, context);
7006
+ context.restore();
7049
7007
  }
7050
7008
  }
7051
- return false;
7052
- }
7053
- clone() {
7054
- return new Feature({
7055
- id: this.id,
7056
- geometries: this.geometries.map((d2) => d2.clone()),
7057
- properties: this.properties,
7058
- style: this.style
7059
- });
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
- };
7009
+ if (Object.prototype.hasOwnProperty.call(projection, "getCompositionBorders")) {
7010
+ context.beginPath();
7011
+ context.lineWidth = 1 / transform.k;
7012
+ context.strokeStyle = "#999";
7013
+ projection.drawCompositionBorders(context);
7014
+ context.stroke();
7015
+ }
7016
+ context.restore();
7017
+ return container2;
7073
7018
  }
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
- };
7019
+ getOrCreateContainer(targetElement, sizeInPixels) {
7020
+ let container2 = null;
7021
+ let containerReused = false;
7022
+ let canvas = targetElement && targetElement.firstElementChild;
7023
+ if (canvas instanceof HTMLCanvasElement) {
7024
+ container2 = targetElement;
7025
+ containerReused = true;
7026
+ } else if (this._container) {
7027
+ container2 = this._container;
7028
+ } else {
7029
+ container2 = this.createContainer();
7093
7030
  }
7094
- throw new Error("Could not determine geometry type");
7031
+ if (!containerReused) {
7032
+ const canvas2 = container2.firstElementChild;
7033
+ canvas2.width = sizeInPixels[0];
7034
+ canvas2.height = sizeInPixels[1];
7035
+ }
7036
+ this._container = container2;
7037
+ return container2;
7038
+ }
7039
+ createContainer() {
7040
+ const container2 = document.createElement("div");
7041
+ container2.className = "gv-map-layer";
7042
+ let style2 = container2.style;
7043
+ style2.position = "absolute";
7044
+ style2.width = "100%";
7045
+ style2.height = "100%";
7046
+ const canvas = document.createElement("canvas");
7047
+ style2 = canvas.style;
7048
+ style2.position = "absolute";
7049
+ style2.width = "100%";
7050
+ style2.height = "100%";
7051
+ container2.appendChild(canvas);
7052
+ return container2;
7095
7053
  }
7096
7054
  }
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 }) {
7107
- this.type = type;
7108
- this.extent = extent;
7109
- this.coordinates = coordinates;
7110
- this.getProjected = memoise(this._getProjected).bind(this);
7055
+ class VectorLayer {
7056
+ /** @param {VectorLayerComponentProps} props */
7057
+ static Component({
7058
+ features: featureCollection,
7059
+ style: style2,
7060
+ minZoom,
7061
+ opacity,
7062
+ hitDetectionEnabled = true
7063
+ }) {
7064
+ const { registerLayer } = hooks.useContext(MapContext);
7065
+ const layer = hooks.useMemo(
7066
+ () => {
7067
+ const features = featureCollection instanceof FeatureCollection ? featureCollection.features : (
7068
+ /** @type {import("../Feature").Feature[]} */
7069
+ featureCollection
7070
+ );
7071
+ return VectorLayer.with(features, {
7072
+ style: style2,
7073
+ minZoom,
7074
+ opacity,
7075
+ hitDetectionEnabled
7076
+ });
7077
+ },
7078
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7079
+ [featureCollection, minZoom, opacity, hitDetectionEnabled]
7080
+ );
7081
+ registerLayer(layer);
7082
+ hooks.useEffect(() => {
7083
+ layer.style = style2;
7084
+ }, [style2]);
7085
+ return null;
7111
7086
  }
7112
7087
  /**
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
7088
+ * @param {import("../Feature").Feature[]} features
7089
+ * @param {VectorLayerOptions} options
7118
7090
  */
7119
- // eslint-disable-next-line no-unused-vars
7120
- _getProjected(projection) {
7121
- throw new Error("Not implemented");
7091
+ static with(features, options) {
7092
+ const source = new VectorSource({ features });
7093
+ return new VectorLayer({ source, ...options });
7122
7094
  }
7123
7095
  /**
7124
- * Returns the geometry as a GeoJSON object
7125
- * @returns {Object} The GeoJSON representation of the geometry
7096
+ * @param {Object} params
7097
+ * @param {VectorSource} params.source
7098
+ * @param {Style | (() => Style)} [params.style=undefined]
7099
+ * @param {number} [params.minZoom=0]
7100
+ * @param {number} [params.opacity=1]
7101
+ * @param {boolean} [params.hitDetectionEnabled=true]
7126
7102
  */
7127
- getGeoJSON() {
7128
- return {
7129
- type: this.type,
7130
- coordinates: this.coordinates
7131
- };
7103
+ constructor({
7104
+ source,
7105
+ style: style2,
7106
+ minZoom = 0,
7107
+ opacity = 1,
7108
+ hitDetectionEnabled = true
7109
+ }) {
7110
+ this.dispatcher = new Dispatcher(this);
7111
+ this.renderer = new VectorLayerRenderer(this);
7112
+ this.source = source;
7113
+ this._style = style2;
7114
+ this.minZoom = minZoom;
7115
+ this.opacity = opacity;
7116
+ this.hitDetectionEnabled = hitDetectionEnabled;
7132
7117
  }
7133
- }
7134
- class LineString extends Geometry {
7135
- constructor({ type = "LineString", extent, coordinates }) {
7136
- super({ type, extent, coordinates });
7118
+ get source() {
7119
+ return this._source;
7137
7120
  }
7138
- _getProjected(projection) {
7139
- const projected = [];
7140
- for (const point of this.coordinates) {
7141
- projected.push(projection(point));
7121
+ set source(source) {
7122
+ if (this._source && source !== this._source) {
7123
+ this._source.tearDown();
7142
7124
  }
7143
- return {
7144
- type: this.type,
7145
- coordinates: projected
7146
- };
7125
+ this._source = source;
7126
+ source.on(MapEvent.CHANGE, () => {
7127
+ this._extent = null;
7128
+ this.dispatcher.dispatch(MapEvent.CHANGE);
7129
+ });
7147
7130
  }
7148
- }
7149
- class Polygon extends Geometry {
7150
- constructor({ type = "Polygon", extent, coordinates }) {
7151
- super({ type, extent, coordinates });
7131
+ setRawProjection(projection) {
7132
+ this.projection = projection;
7152
7133
  }
7153
- _getProjected(projection) {
7154
- const projected = [];
7155
- const rings = this.coordinates;
7156
- for (const ring of rings) {
7157
- const projectedRing = [];
7158
- for (const point of ring) {
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);
7168
- }
7169
- }
7170
- return {
7171
- type: this.type,
7172
- coordinates: projected
7134
+ tearDown() {
7135
+ this.dispatcher = null;
7136
+ }
7137
+ get style() {
7138
+ if (this._style) return this._style;
7139
+ const defaultStyle = new Style({
7140
+ stroke: new Stroke()
7141
+ });
7142
+ return defaultStyle;
7143
+ }
7144
+ set style(style2) {
7145
+ this._style = style2;
7146
+ this.dispatcher.dispatch(MapEvent.CHANGE);
7147
+ }
7148
+ getStyleFunction() {
7149
+ const style2 = this.style;
7150
+ if (typeof style2 === "function") return style2;
7151
+ return () => {
7152
+ return style2;
7173
7153
  };
7174
7154
  }
7175
- getOuterRing() {
7176
- return this.coordinates[0];
7177
- }
7178
- setOuterRing(coordinates) {
7179
- this.coordinates[0] = coordinates;
7155
+ getExtent() {
7156
+ if (this._extent) return this._extent;
7157
+ const features = this.source.getFeatures();
7158
+ const extent = features.reduce((combinedExtent, feature) => {
7159
+ const featureExtent = feature.getExtent();
7160
+ if (!combinedExtent) return featureExtent;
7161
+ return combineExtents(featureExtent, combinedExtent);
7162
+ }, null);
7163
+ this._extent = extent;
7164
+ return extent;
7180
7165
  }
7181
- setCoordinates(coordinates) {
7182
- this.coordinates = coordinates;
7166
+ findFeatures(coordinate) {
7167
+ if (!this.hitDetectionEnabled) return;
7168
+ return this.source.getFeaturesAtCoordinate(coordinate);
7183
7169
  }
7184
- clone() {
7185
- return new Polygon({
7186
- extent: this.extent,
7187
- coordinates: JSON.parse(JSON.stringify(this.coordinates))
7188
- });
7170
+ renderFrame(frameState, targetElement) {
7171
+ return this.renderer.renderFrame(frameState, targetElement);
7189
7172
  }
7190
7173
  }
7191
- class Point extends Geometry {
7192
- constructor({ type = "Point", coordinates }) {
7193
- super({ type, extent: null, coordinates });
7194
- this.extent = [...coordinates, ...coordinates];
7195
- }
7196
- _getProjected(projection) {
7197
- return {
7198
- type: this.type,
7199
- coordinates: projection(this.coordinates)
7200
- };
7174
+ function interpolateFeatures(currentFeatures, newFeatures, { interpolate: interpolate2, separate, combine }) {
7175
+ if (currentFeatures.length !== newFeatures.length) {
7176
+ throw new Error(
7177
+ "interpolateFeatures expects an equal number of features for start and end"
7178
+ );
7201
7179
  }
7202
- }
7203
- class GeoJSON {
7204
- readFeaturesFromObject(object) {
7205
- const geoJSONObject = object;
7206
- let features = null;
7207
- if (geoJSONObject["type"] === "FeatureCollection") {
7208
- const geoJSONFeatureCollection = object;
7209
- features = [];
7210
- const geoJSONFeatures = geoJSONFeatureCollection["features"];
7211
- for (let i = 0, ii = geoJSONFeatures.length; i < ii; ++i) {
7212
- const featureObject = this.readFeatureFromObject(geoJSONFeatures[i]);
7213
- if (!featureObject) {
7214
- continue;
7215
- }
7216
- features.push(featureObject);
7217
- }
7218
- } else if (geoJSONObject["type"] === "Feature") {
7219
- features = [this.readFeatureFromObject(geoJSONObject)];
7220
- } else if (Array.isArray(geoJSONObject)) {
7221
- features = [];
7222
- for (let i = 0, ii = geoJSONObject.length; i < ii; ++i) {
7223
- const featureObject = this.readFeatureFromObject(geoJSONObject[i]);
7224
- if (!featureObject) {
7225
- continue;
7180
+ const featureInterpolators = [];
7181
+ for (let i = 0; i < currentFeatures.length; i++) {
7182
+ const geometryInterpolators = [];
7183
+ const currentGeometries = currentFeatures[i].geometries;
7184
+ const newGeometries = newFeatures[i].geometries;
7185
+ if (newGeometries.length === currentGeometries.length) {
7186
+ for (let e = 0; e < currentGeometries.length; e++) {
7187
+ const currentGeometry = currentGeometries[e];
7188
+ const newGeometry = newGeometries[e];
7189
+ if (currentGeometry.type !== "Polygon" || newGeometry.type !== "Polygon") {
7190
+ throw new Error("interpolateFeatures expects only Polygon geometry");
7226
7191
  }
7227
- features.push(featureObject);
7192
+ const shapeInterpolator = interpolate2(
7193
+ currentGeometries[e].getOuterRing(),
7194
+ newGeometries[e].getOuterRing(),
7195
+ { string: false }
7196
+ );
7197
+ geometryInterpolators.push({
7198
+ type: "default",
7199
+ interpolator: shapeInterpolator
7200
+ });
7228
7201
  }
7202
+ } else if (currentGeometries.length === 1 && newGeometries.length > 1) {
7203
+ const separationInterpolator = separate(
7204
+ currentGeometries[0].getOuterRing(),
7205
+ newGeometries.map((geometry) => geometry.getOuterRing()),
7206
+ { string: false, single: true }
7207
+ );
7208
+ geometryInterpolators.push({
7209
+ type: "separate",
7210
+ interpolator: separationInterpolator
7211
+ });
7212
+ } else if (currentGeometries.length > 1 && newGeometries.length === 1) {
7213
+ const combinationInterpolator = combine(
7214
+ currentGeometries.map((geometry) => geometry.getOuterRing()),
7215
+ newGeometries[0].getOuterRing(),
7216
+ { string: false, single: true }
7217
+ );
7218
+ geometryInterpolators.push({
7219
+ type: "combine",
7220
+ interpolator: combinationInterpolator
7221
+ });
7229
7222
  } else {
7230
- try {
7231
- const geometries = this.readGeometriesFromObject(geoJSONObject);
7232
- const feature = new Feature({ geometries });
7233
- features = [feature];
7234
- } catch {
7235
- console.warn("Unable to interpret GeoJSON:", geoJSONObject);
7236
- return;
7237
- }
7223
+ throw new Error(
7224
+ `Encountered an unexpected number of geometries: ${currentGeometries.length} and ${newGeometries.length}`
7225
+ );
7238
7226
  }
7239
- return features.flat();
7227
+ featureInterpolators.push(geometryInterpolators);
7240
7228
  }
7241
- readFeatureFromObject(geoJSONObject) {
7242
- const geometries = this.readGeometriesFromObject(geoJSONObject["geometry"]);
7243
- if (geometries.length > 0) {
7244
- return new Feature({
7245
- id: geoJSONObject["id"],
7246
- geometries,
7247
- properties: geoJSONObject["properties"]
7248
- });
7229
+ return (t) => {
7230
+ if (t >= 1) {
7231
+ return newFeatures;
7249
7232
  }
7250
- return null;
7251
- }
7252
- readGeometriesFromObject(geometry) {
7253
- const geometries = [];
7254
- if (geometry.type === "Polygon") {
7255
- const polygon = this.readPolygonForCoordinates(geometry.coordinates);
7256
- geometries.push(polygon);
7257
- } else if (geometry.type === "MultiPolygon") {
7258
- for (const polygonCoordinates of geometry.coordinates) {
7259
- const polygon = this.readPolygonForCoordinates(polygonCoordinates);
7260
- geometries.push(polygon);
7261
- }
7262
- } else if (geometry.type === "LineString") {
7263
- const lineString = this.readLineStringForCoordinates(geometry.coordinates);
7264
- geometries.push(lineString);
7265
- } else if (geometry.type === "MultiLineString") {
7266
- for (const lineStringCoordinates of geometry.coordinates) {
7267
- const lineString = this.readLineStringForCoordinates(
7268
- lineStringCoordinates
7269
- );
7270
- geometries.push(lineString);
7233
+ const features = [];
7234
+ for (let i = 0; i < featureInterpolators.length; i++) {
7235
+ const feature = newFeatures[i].clone();
7236
+ const geometries = [];
7237
+ const geometryInterpolators = featureInterpolators[i];
7238
+ for (const [
7239
+ index,
7240
+ { type, interpolator }
7241
+ ] of geometryInterpolators.entries()) {
7242
+ let geometry = feature.geometries[index].clone();
7243
+ let interpolated;
7244
+ switch (type) {
7245
+ case "separate":
7246
+ case "combine":
7247
+ interpolated = interpolator(t);
7248
+ interpolated.forEach((d2) => {
7249
+ const polygon = geometry.clone();
7250
+ polygon.setCoordinates([d2]);
7251
+ geometries.push(polygon);
7252
+ });
7253
+ break;
7254
+ default:
7255
+ geometry.setOuterRing(interpolator(t));
7256
+ geometries.push(geometry);
7257
+ break;
7258
+ }
7271
7259
  }
7272
- } else if (geometry.type === "Point") {
7273
- const point = this.readPointForCoordinates(geometry.coordinates);
7274
- geometries.push(point);
7260
+ feature.setGeometry(geometries);
7261
+ features.push(feature);
7275
7262
  }
7276
- return geometries;
7277
- }
7278
- readPolygonForCoordinates(coordinates) {
7279
- const outerRing = coordinates[0];
7280
- const extent = extentForCoordinates(outerRing);
7281
- return new Polygon({ extent, coordinates });
7282
- }
7283
- readLineStringForCoordinates(coordinates) {
7284
- const extent = extentForCoordinates(coordinates);
7285
- return new LineString({ extent, coordinates });
7286
- }
7287
- readPointForCoordinates(coordinates) {
7288
- return new Point({ coordinates });
7289
- }
7263
+ return features;
7264
+ };
7265
+ }
7266
+ function interpolateStyles(firstStyle, secondStyle, interpolateColors, interpolateNumbers) {
7267
+ const fillInterpolator = interpolateFill(
7268
+ firstStyle.fill,
7269
+ secondStyle.fill,
7270
+ interpolateColors,
7271
+ interpolateNumbers
7272
+ );
7273
+ const strokeInterpolator = interpolateStroke(
7274
+ firstStyle.stroke,
7275
+ secondStyle.stroke,
7276
+ interpolateColors,
7277
+ interpolateNumbers
7278
+ );
7279
+ return (t) => {
7280
+ return new Style({
7281
+ fill: fillInterpolator(t),
7282
+ stroke: strokeInterpolator(t)
7283
+ });
7284
+ };
7285
+ }
7286
+ function interpolateFill(fillA, fillB, interpolateColors, interpolateNumbers) {
7287
+ const colorInterpolator = interpolateColors(
7288
+ (fillA == null ? void 0 : fillA.color) ?? "transparent",
7289
+ (fillB == null ? void 0 : fillB.color) ?? "transparent"
7290
+ );
7291
+ const opacityInterpolator = interpolateNumbers(
7292
+ (fillA == null ? void 0 : fillA.opacity) ?? 1,
7293
+ (fillB == null ? void 0 : fillB.opacity) ?? 1
7294
+ );
7295
+ return (t) => {
7296
+ return new Fill({
7297
+ color: colorInterpolator(t),
7298
+ opacity: opacityInterpolator(t)
7299
+ });
7300
+ };
7301
+ }
7302
+ function interpolateStroke(strokeA, strokeB, interpolateColors, interpolateNumbers) {
7303
+ const colorInterpolator = interpolateColors(
7304
+ (strokeA == null ? void 0 : strokeA.color) ?? "transparent",
7305
+ (strokeB == null ? void 0 : strokeB.color) ?? "transparent"
7306
+ );
7307
+ const opacityInterpolator = interpolateNumbers(
7308
+ (strokeA == null ? void 0 : strokeA.opacity) ?? 1,
7309
+ (strokeB == null ? void 0 : strokeB.opacity) ?? 1
7310
+ );
7311
+ const widthInterpolator = interpolateNumbers(
7312
+ (strokeA == null ? void 0 : strokeA.width) ?? 1,
7313
+ (strokeB == null ? void 0 : strokeB.width) ?? 1
7314
+ );
7315
+ return (t) => {
7316
+ return new Stroke({
7317
+ color: colorInterpolator(t),
7318
+ opacity: opacityInterpolator(t),
7319
+ width: widthInterpolator(t)
7320
+ });
7321
+ };
7290
7322
  }
7291
7323
  function useWindowSize() {
7292
7324
  const [windowSize, setWindowSize] = hooks.useState(() => {
@@ -7725,6 +7757,8 @@
7725
7757
  exports2.ControlChange = ControlChange;
7726
7758
  exports2.Dispatcher = Dispatcher;
7727
7759
  exports2.Dropdown = Dropdown;
7760
+ exports2.Feature = Feature;
7761
+ exports2.FeatureCollection = FeatureCollection;
7728
7762
  exports2.Fill = Fill;
7729
7763
  exports2.FirstPastThePostWaffle = FirstPastThePostWaffle;
7730
7764
  exports2.GeoJSON = GeoJSON;