@maplibre/geojson-vt 6.2.0 → 6.3.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.
package/src/geojsonvt.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import {convertToInternal} from './convert';
2
2
  import {convertToGeoJSON, featureToGeoJSON} from './deconvert';
3
+ import {getWorldCoordinateHelper, type WorldCoordinateHelper} from './world-coordinate-helper';
3
4
  import {wrap} from './wrap';
4
5
  import {applySourceDiff, type GeoJSONVTSourceDiff} from './difference';
5
6
  import {ClusterTileIndex, defaultClusterOptions} from './cluster-tile-index';
6
7
  import {TileIndex} from './tile-index';
7
- import type {ClusterOrPointFeature, GeoJSONVTTileIndex, GeoJSONVTInternalFeature, GeoJSONVTOptions, GeoJSONVTTile, SuperclusterOptions} from './definitions';
8
+ import type {ClusterOrPointFeature, GeoJSONVTTileIndex, GeoJSONVTInternalFeature, GeoJSONVTOptions, GeoJSONVTTile} from './definitions';
8
9
 
9
10
  export const defaultOptions: GeoJSONVTOptions = {
10
11
  maxZoom: 14,
@@ -20,7 +21,8 @@ export const defaultOptions: GeoJSONVTOptions = {
20
21
  cluster: false,
21
22
  clusterOptions: defaultClusterOptions,
22
23
  debug: 0,
23
- worldCoordinates: false
24
+ coordinates: 'lngLat',
25
+ wrap: true
24
26
  };
25
27
 
26
28
  /**
@@ -28,12 +30,15 @@ export const defaultOptions: GeoJSONVTOptions = {
28
30
  */
29
31
  export class GeoJSONVT {
30
32
  private options: GeoJSONVTOptions;
33
+ private worldCoordinateHelper: WorldCoordinateHelper;
31
34
 
32
35
  private source?: GeoJSONVTInternalFeature[];
36
+ private unlocated?: Map<string | number, GeoJSON.Feature>;
33
37
  private tileIndex: GeoJSONVTTileIndex;
34
38
 
35
39
  constructor(data: GeoJSON.GeoJSON, options?: GeoJSONVTOptions) {
36
40
  options = this.options = Object.assign({}, defaultOptions, options);
41
+ this.worldCoordinateHelper = getWorldCoordinateHelper(options.coordinates);
37
42
 
38
43
  const debug = options.debug;
39
44
 
@@ -42,8 +47,11 @@ export class GeoJSONVT {
42
47
  if (options.maxZoom < 0 || options.maxZoom > 24) throw new Error('maxZoom should be in the 0-24 range');
43
48
  if (options.promoteId && options.generateId) throw new Error('promoteId and generateId cannot be used together.');
44
49
 
50
+ // for updateable indexes, keep the properties of the features without coordinates, so that an update can give them some
51
+ if (options.updateable) this.unlocated = new Map();
52
+
45
53
  // projects and adds simplification info
46
- let features = convertToInternal(data, options);
54
+ let features = convertToInternal(data, options, this.unlocated);
47
55
 
48
56
  if (debug) {
49
57
  console.timeEnd('preprocess data');
@@ -63,7 +71,7 @@ export class GeoJSONVT {
63
71
  }
64
72
 
65
73
  private initializeIndex(features: GeoJSONVTInternalFeature[], options: GeoJSONVTOptions) {
66
- this.tileIndex = options.cluster ? new ClusterTileIndex(options.clusterOptions, options.worldCoordinates) : new TileIndex(options);
74
+ this.tileIndex = options.cluster ? new ClusterTileIndex({...options.clusterOptions, coordinates: options.coordinates, wrap: options.wrap}) : new TileIndex(options);
67
75
  if (!features.length) return;
68
76
  this.tileIndex.initialize(features);
69
77
  }
@@ -95,7 +103,7 @@ export class GeoJSONVT {
95
103
  if (!options.updateable) throw new Error('to update tile geojson `updateable` option must be set to true');
96
104
 
97
105
  // apply diff and collect affected features and updated source that will be used to invalidate tiles
98
- let {affected, source} = applySourceDiff(this.source, diff, options);
106
+ let {affected, source} = applySourceDiff(this.source, diff, options, this.unlocated);
99
107
 
100
108
  if (filter) {
101
109
  ({affected, source} = this.filterUpdate(source, affected, filter));
@@ -111,19 +119,25 @@ export class GeoJSONVT {
111
119
  }
112
120
 
113
121
  /**
114
- * Filter an update using a predicate function. Returns the affected and updated source features.
122
+ * Filter an update using a predicate function, the features an update emptied included, so that one the filter
123
+ * rejects can't come back with a later update. A feature without a geometry at all isn't passed to the predicate.
124
+ * Returns the affected and updated source features.
115
125
  */
116
126
  private filterUpdate(source: GeoJSONVTInternalFeature[], affected: GeoJSONVTInternalFeature[], predicate: (feature: GeoJSON.Feature) => boolean) {
117
127
  const removeIds = new Set();
118
128
 
119
129
  for (const feature of source) {
120
130
  if (feature.id == undefined) continue;
121
- if (predicate(featureToGeoJSON(feature, this.options.worldCoordinates))) continue;
131
+ if (predicate(featureToGeoJSON(feature, this.worldCoordinateHelper))) continue;
122
132
  affected.push(feature);
123
133
  removeIds.add(feature.id);
124
134
  }
125
135
  source = source.filter(feature => !removeIds.has(feature.id));
126
136
 
137
+ for (const [id, feature] of this.unlocated ?? []) {
138
+ if (feature.geometry && !predicate(feature)) this.unlocated.delete(id);
139
+ }
140
+
127
141
  return {affected, source};
128
142
  }
129
143
 
@@ -132,15 +146,15 @@ export class GeoJSONVT {
132
146
  */
133
147
  getData(): GeoJSON.GeoJSON {
134
148
  if (!this.options.updateable) throw new Error('to retrieve data the `updateable` option must be set to true');
135
- return convertToGeoJSON(this.source, this.options.worldCoordinates);
149
+ return convertToGeoJSON(this.source, this.worldCoordinateHelper);
136
150
  }
137
151
 
138
152
  /**
139
153
  * Update supercluster options and regenerate the index.
140
154
  * @param cluster - whether to enable clustering
141
- * @param clusterOptions - {@link SuperclusterOptions}
155
+ * @param clusterOptions - {@link SuperclusterOptions} without `coordinates` and `wrap`, which come from the index options
142
156
  */
143
- updateClusterOptions(cluster: boolean, clusterOptions: SuperclusterOptions) {
157
+ updateClusterOptions(cluster: boolean, clusterOptions: GeoJSONVTOptions['clusterOptions']) {
144
158
  const wasCluster = this.options.cluster;
145
159
  this.options.cluster = cluster;
146
160
  this.options.clusterOptions = clusterOptions;
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import type {GeoJSONVTFeatureDiff, GeoJSONVTSourceDiff} from './difference';
3
- import type {GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, GeoJSONVTOptions, GeoJSONToTileOptions, PartialGeoJSONVTFeature, SliceArray, SliceFixedArray, ClusterProperties, ClusterFeature, ClusterOrPointFeature, GeoJSONVTTile, GeoJSONVTFeature, GeoJSONVTFeaturePoint, GeoJSONVTFeatureNonPoint, GeoJSONVTInternalTileFeaturePoint, GeoJSONVTInternalTileFeatureNonPoint, GeoJSONVTInternalTile, GeoJSONVTInternalTileFeature, GeoJSONVTTileIndex, SuperclusterOptions} from './definitions';
3
+ import type {GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, GeoJSONVTOptions, GeoJSONToTileOptions, PartialGeoJSONVTFeature, SliceArray, SliceFixedArray, ClusterProperties, ClusterFeature, ClusterOrPointFeature, GeoJSONVTTile, GeoJSONVTFeature, GeoJSONVTFeaturePoint, GeoJSONVTFeatureNonPoint, GeoJSONVTInternalTileFeaturePoint, GeoJSONVTInternalTileFeatureNonPoint, GeoJSONVTInternalTile, GeoJSONVTInternalTileFeature, GeoJSONVTTileIndex, SuperclusterOptions, GeoJSONVTCoordinatesType} from './definitions';
4
4
  import type {KDBushWithData} from './cluster-tile-index';
5
5
  import {GeoJSONVT} from './geojsonvt';
6
6
  import {ClusterTileIndex} from './cluster-tile-index';
@@ -39,6 +39,7 @@ export type {
39
39
  GeoJSONVTInternalPolygonFeature,
40
40
  GeoJSONVTInternalMultiPolygonFeature,
41
41
  SuperclusterOptions,
42
+ GeoJSONVTCoordinatesType,
42
43
  ClusterProperties,
43
44
  ClusterFeature,
44
45
  ClusterOrPointFeature,
package/src/tile-index.ts CHANGED
@@ -84,7 +84,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
84
84
  const {extent, debug} = this.options;
85
85
 
86
86
  const z2 = 1 << z;
87
- if (this.options.worldCoordinates && (x < 0 || x >= z2)) return null; // a row outside the world has no tile either
87
+ if (!this.options.wrap && (x < 0 || x >= z2)) return null; // a row outside the world has no tile either
88
88
  x = (x + z2) & (z2 - 1); // wrap tile x coordinate
89
89
 
90
90
  const id = toID(z, x, y);
@@ -122,14 +122,16 @@ export class TileIndex implements GeoJSONVTTileIndex {
122
122
  }
123
123
 
124
124
  /**
125
- * The features the top tile starts from: all of them in a wrapping world, where wrapping already put them inside,
126
- * and only what lies inside the world square and its buffer with `worldCoordinates`. The index keeps the rest, so
127
- * an update can still move a feature in from outside.
125
+ * The features the top tile starts from. A world that wraps keeps them all left and right, where wrapping already
126
+ * put them inside, and one that doesn't only what lies inside the world square and its buffer. Positions in the
127
+ * unit square are also cut above and below in a world that wraps, where lng/lat is clamped already. The index keeps
128
+ * the rest, so an update can still move a feature in from outside.
128
129
  */
129
130
  private clipToWorld(features: GeoJSONVTInternalFeature[]): GeoJSONVTInternalFeature[] {
130
- if (!this.options.worldCoordinates) return features;
131
+ const {wrap, coordinates} = this.options;
132
+ if (wrap && coordinates !== 'unitSquare') return features;
131
133
  const buffer = this.options.buffer / this.options.extent;
132
- const inX = clip(features, 1, -buffer, 1 + buffer, AxisType.X, -Infinity, Infinity, this.options) || [];
134
+ const inX = wrap ? features : clip(features, 1, -buffer, 1 + buffer, AxisType.X, -Infinity, Infinity, this.options) || [];
133
135
  return clip(inX, 1, -buffer, 1 + buffer, AxisType.Y, -Infinity, Infinity, this.options) || [];
134
136
  }
135
137
 
@@ -0,0 +1,63 @@
1
+ import type {GeoJSONVTCoordinatesType} from './definitions';
2
+
3
+ /**
4
+ * How the positions of the input relate to the world square the tiles cover, in which x and y run from 0 to 1.
5
+ */
6
+ export interface WorldCoordinateHelper {
7
+ projectX(x: number): number;
8
+ projectY(y: number): number;
9
+ unprojectX(x: number): number;
10
+ unprojectY(y: number): number;
11
+ }
12
+
13
+ /**
14
+ * Longitude and latitude, which spherical mercator puts in the world square.
15
+ */
16
+ class MercatorWorldCoordinateHelper implements WorldCoordinateHelper {
17
+ projectX(lng: number): number {
18
+ return lng / 360 + 0.5;
19
+ }
20
+
21
+ projectY(lat: number): number {
22
+ const sin = Math.sin(lat * Math.PI / 180);
23
+ const y = 0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI;
24
+ return y < 0 ? 0 : y > 1 ? 1 : y;
25
+ }
26
+
27
+ unprojectX(x: number): number {
28
+ return (x - 0.5) * 360;
29
+ }
30
+
31
+ unprojectY(y: number): number {
32
+ const y2 = (180 - y * 360) * Math.PI / 180;
33
+ return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Positions in the unit square already, see `coordinates` in {@link GeoJSONVTOptions}.
39
+ */
40
+ class IdentityWorldCoordinateHelper implements WorldCoordinateHelper {
41
+ projectX(x: number): number {
42
+ return x;
43
+ }
44
+
45
+ projectY(y: number): number {
46
+ return y;
47
+ }
48
+
49
+ unprojectX(x: number): number {
50
+ return x;
51
+ }
52
+
53
+ unprojectY(y: number): number {
54
+ return y;
55
+ }
56
+ }
57
+
58
+ export const mercatorWorldCoordinateHelper: WorldCoordinateHelper = new MercatorWorldCoordinateHelper();
59
+ const identityWorldCoordinateHelper: WorldCoordinateHelper = new IdentityWorldCoordinateHelper();
60
+
61
+ export function getWorldCoordinateHelper(coordinates?: GeoJSONVTCoordinatesType): WorldCoordinateHelper {
62
+ return coordinates === 'unitSquare' ? identityWorldCoordinateHelper : mercatorWorldCoordinateHelper;
63
+ }
package/src/wrap.ts CHANGED
@@ -4,7 +4,7 @@ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, SliceArray, SliceFixed
4
4
  import {createFeature, optimizeLineMemory} from './feature';
5
5
 
6
6
  export function wrap(features: GeoJSONVTInternalFeature[], options: GeoJSONVTOptions): GeoJSONVTInternalFeature[] {
7
- if (options.worldCoordinates) return features;
7
+ if (!options.wrap) return features;
8
8
 
9
9
  const buffer = options.buffer / options.extent;
10
10
  let merged = features;