@maplibre/geojson-vt 6.0.2 → 6.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maplibre/geojson-vt",
3
- "version": "6.0.2",
3
+ "version": "6.0.4",
4
4
  "description": "Slice GeoJSON data into vector tiles efficiently",
5
5
  "type": "module",
6
6
  "exports": {
@@ -31,7 +31,7 @@
31
31
  "@types/benchmark": "^2.1.5",
32
32
  "@types/geojson": "^7946.0.16",
33
33
  "@types/node": "^25.5.0",
34
- "@vitest/coverage-v8": "^4.0.18",
34
+ "@vitest/coverage-v8": "^4.1.0",
35
35
  "benchmark": "^2.1.4",
36
36
  "eslint": "^10.0.3",
37
37
  "rollup": "^4.59.0",
@@ -39,7 +39,7 @@
39
39
  "tsx": "^4.21.0",
40
40
  "typedoc": "^0.28.17",
41
41
  "typescript": "^5.9.3",
42
- "typescript-eslint": "^8.57.0",
42
+ "typescript-eslint": "^8.57.1",
43
43
  "vitest": "^4.0.17"
44
44
  },
45
45
  "license": "ISC",
package/src/difference.ts CHANGED
@@ -47,6 +47,17 @@ export type GeoJSONVTFeatureDiff = {
47
47
  }[];
48
48
  };
49
49
 
50
+ export type ApplySourceDiffResult = {
51
+ /**
52
+ * The features affected by this update, which should be used to invalidate tiles
53
+ */
54
+ affected: GeoJSONVTInternalFeature[];
55
+ /**
56
+ * The updated source data, which should replace the existing source data in the index
57
+ */
58
+ source: GeoJSONVTInternalFeature[];
59
+ };
60
+
50
61
  type HashedGeoJSONVTSourceDiff = {
51
62
  removeAll?: boolean | undefined;
52
63
  remove: Set<string | number>;
@@ -61,91 +72,86 @@ type HashedGeoJSONVTSourceDiff = {
61
72
  * @param options
62
73
  * @returns
63
74
  */
64
- export function applySourceDiff(source: GeoJSONVTInternalFeature[], dataDiff: GeoJSONVTSourceDiff, options: GeoJSONVTOptions) {
65
-
75
+ export function applySourceDiff(source: GeoJSONVTInternalFeature[], dataDiff: GeoJSONVTSourceDiff, options: GeoJSONVTOptions): ApplySourceDiffResult {
66
76
  // convert diff to sets/maps for o(1) lookups
67
77
  const diff = diffToHashed(dataDiff);
68
78
 
69
- // collection for features that will be affected by this update
79
+ // collection for features that will be affected by this update and used to invalidate tiles
70
80
  let affected: GeoJSONVTInternalFeature[] = [];
71
81
 
72
- // full removal - clear everything before applying diff
73
82
  if (diff.removeAll) {
74
83
  affected = source;
75
84
  source = [];
76
85
  }
77
86
 
78
- // remove/add features and collect affected ones
79
87
  if (diff.remove.size || diff.add.size) {
80
88
  const removeFeatures = [];
81
89
 
82
- // collect source features to be removed
90
+ // Collect features to remove (explicit removals + replacements via add)
83
91
  for (const feature of source) {
84
- const {id} = feature;
85
-
86
- // explicit feature removal
87
- if (diff.remove.has(id)) {
88
- removeFeatures.push(feature);
89
- // feature with duplicate id being added
90
- } else if (diff.add.has(id)) {
92
+ if (diff.remove.has(feature.id) || diff.add.has(feature.id)) {
91
93
  removeFeatures.push(feature);
92
94
  }
93
95
  }
94
96
 
95
- // collect affected and remove from source
96
97
  if (removeFeatures.length) {
97
98
  affected.push(...removeFeatures);
98
-
99
99
  const removeIds = new Set(removeFeatures.map(f => f.id));
100
100
  source = source.filter(f => !removeIds.has(f.id));
101
101
  }
102
102
 
103
- // convert and add new features
104
103
  if (diff.add.size) {
105
- // projects and adds simplification info
106
104
  let addFeatures = convertToInternal({type: 'FeatureCollection', features: Array.from(diff.add.values())}, options);
107
-
108
- // wraps features (ie extreme west and extreme east)
109
105
  addFeatures = wrap(addFeatures, options);
110
-
111
106
  affected.push(...addFeatures);
112
107
  source.push(...addFeatures);
113
108
  }
114
109
  }
115
110
 
116
111
  if (diff.update.size) {
112
+ // Features can be duplicated across the antimeridian (wrap) in a single tile, so must update all instances with the same id
117
113
  for (const [id, update] of diff.update) {
118
- const featureIndex = source.findIndex(f => f.id === id);
119
- if (featureIndex === -1) continue;
120
-
121
- const feature = source[featureIndex];
122
-
123
- // get updated geojsonvt simplified feature
124
- const updatedFeature = getUpdatedFeature(feature, update, options);
125
- if (!updatedFeature) continue;
114
+ const oldFeatures = [];
115
+ const keepFeatures = [];
116
+
117
+ for (const feature of source) {
118
+ if (feature.id === id) {
119
+ oldFeatures.push(feature);
120
+ } else {
121
+ keepFeatures.push(feature);
122
+ }
123
+ }
124
+ if (!oldFeatures.length) continue;
126
125
 
127
- // track both features for invalidation
128
- affected.push(feature, updatedFeature);
126
+ const updatedFeatures = getUpdatedFeatures(oldFeatures, update, options);
127
+ if (!updatedFeatures.length) continue;
129
128
 
130
- // replace old feature with updated feature
131
- source[featureIndex] = updatedFeature;
129
+ affected.push(...oldFeatures, ...updatedFeatures);
130
+ keepFeatures.push(...updatedFeatures);
131
+ source = keepFeatures;
132
132
  }
133
133
  }
134
134
 
135
135
  return {affected, source};
136
136
  }
137
137
 
138
- // return an updated geojsonvt simplified feature
139
- function getUpdatedFeature(vtFeature: GeoJSONVTInternalFeature, update: GeoJSONVTFeatureDiff, options: GeoJSONVTOptions): GeoJSONVTInternalFeature | null {
138
+ /**
139
+ * Gets updated simplified feature(s) based on a diff update object.
140
+ * @param vtFeatures - the original features
141
+ * @param update - the update object to apply
142
+ * @param options - the options to use for the wrap method
143
+ * @returns Updated features. If geometry is updated, returns new feature(s) converted from geojson and wrapped. If only properties are updated, returns feature(s) with tags updated.
144
+ */
145
+ function getUpdatedFeatures(vtFeatures: GeoJSONVTInternalFeature[], update: GeoJSONVTFeatureDiff, options: GeoJSONVTOptions): GeoJSONVTInternalFeature[] {
140
146
  const changeGeometry = !!update.newGeometry;
141
-
142
147
  const changeProps =
143
148
  update.removeAllProperties ||
144
149
  update.removeProperties?.length > 0 ||
145
150
  update.addOrUpdateProperties?.length > 0;
146
151
 
147
- // if geometry changed, need to create new geojson feature and convert to simplified format
152
+ // if geometry changed, need to create a new geojson feature and convert to internal format
148
153
  if (changeGeometry) {
154
+ const vtFeature = vtFeatures[0];
149
155
  const geojsonFeature = {
150
156
  type: 'Feature' as const,
151
157
  id: vtFeature.id,
@@ -153,23 +159,22 @@ function getUpdatedFeature(vtFeature: GeoJSONVTInternalFeature, update: GeoJSONV
153
159
  properties: changeProps ? applyPropertyUpdates(vtFeature.tags, update) : vtFeature.tags
154
160
  };
155
161
 
156
- // projects and adds simplification info
157
162
  let features = convertToInternal({type: 'FeatureCollection', features: [geojsonFeature]}, options);
158
-
159
- // wraps features (ie extreme west and extreme east)
160
163
  features = wrap(features, options);
161
-
162
- return features[0];
164
+ return features;
163
165
  }
164
166
 
165
- // only properties changed - update tags directly
166
167
  if (changeProps) {
167
- const feature = {...vtFeature};
168
- feature.tags = applyPropertyUpdates(feature.tags, update);
169
- return feature;
168
+ const updated = [];
169
+ for (const vtFeature of vtFeatures) {
170
+ const feature = {...vtFeature};
171
+ feature.tags = applyPropertyUpdates(feature.tags, update);
172
+ updated.push(feature);
173
+ }
174
+ return updated;
170
175
  }
171
176
 
172
- return null;
177
+ return [];
173
178
  }
174
179
 
175
180
  /**