@maplibre/geojson-vt 6.0.4 → 6.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.
package/src/difference.ts CHANGED
@@ -74,7 +74,7 @@ type HashedGeoJSONVTSourceDiff = {
74
74
  */
75
75
  export function applySourceDiff(source: GeoJSONVTInternalFeature[], dataDiff: GeoJSONVTSourceDiff, options: GeoJSONVTOptions): ApplySourceDiffResult {
76
76
  // convert diff to sets/maps for o(1) lookups
77
- const diff = diffToHashed(dataDiff);
77
+ const diff = diffToHashed(dataDiff, options);
78
78
 
79
79
  // collection for features that will be affected by this update and used to invalidate tiles
80
80
  let affected: GeoJSONVTInternalFeature[] = [];
@@ -110,26 +110,24 @@ export function applySourceDiff(source: GeoJSONVTInternalFeature[], dataDiff: Ge
110
110
 
111
111
  if (diff.update.size) {
112
112
  // Features can be duplicated across the antimeridian (wrap) in a single tile, so must update all instances with the same id
113
- for (const [id, update] of diff.update) {
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
- }
113
+ const oldFeaturesMap = new Map<string | number, GeoJSONVTInternalFeature[]>();
114
+ const keepFeatures = [];
115
+ for (const feature of source) {
116
+ if (diff.update.has(feature.id)) {
117
+ oldFeaturesMap.set(feature.id, [...(oldFeaturesMap.get(feature.id) || []), feature]);
118
+ } else {
119
+ keepFeatures.push(feature);
123
120
  }
124
- if (!oldFeatures.length) continue;
125
-
121
+ }
122
+ for (const [id, update] of diff.update) {
123
+ const oldFeatures = oldFeaturesMap.get(id);
124
+ if (!oldFeatures || oldFeatures.length === 0) continue;
126
125
  const updatedFeatures = getUpdatedFeatures(oldFeatures, update, options);
127
- if (!updatedFeatures.length) continue;
128
126
 
129
127
  affected.push(...oldFeatures, ...updatedFeatures);
130
128
  keepFeatures.push(...updatedFeatures);
131
- source = keepFeatures;
132
129
  }
130
+ source = keepFeatures;
133
131
  }
134
132
 
135
133
  return {affected, source};
@@ -174,7 +172,7 @@ function getUpdatedFeatures(vtFeatures: GeoJSONVTInternalFeature[], update: GeoJ
174
172
  return updated;
175
173
  }
176
174
 
177
- return [];
175
+ return vtFeatures;
178
176
  }
179
177
 
180
178
  /**
@@ -205,7 +203,7 @@ function applyPropertyUpdates(tags: GeoJSON.GeoJsonProperties, update: GeoJSONVT
205
203
  /**
206
204
  * Convert a GeoJSON Source Diff to an idempotent hashed representation using Sets and Maps
207
205
  */
208
- export function diffToHashed(diff: GeoJSONVTSourceDiff): HashedGeoJSONVTSourceDiff {
206
+ export function diffToHashed(diff: GeoJSONVTSourceDiff, options: GeoJSONVTOptions): HashedGeoJSONVTSourceDiff {
209
207
  if (!diff) return {
210
208
  remove: new Set(),
211
209
  add: new Map(),
@@ -215,7 +213,7 @@ export function diffToHashed(diff: GeoJSONVTSourceDiff): HashedGeoJSONVTSourceDi
215
213
  const hashed: HashedGeoJSONVTSourceDiff = {
216
214
  removeAll: diff.removeAll,
217
215
  remove: new Set(diff.remove || []),
218
- add: new Map(diff.add?.map(feature => [feature.id, feature])),
216
+ add: new Map(diff.add?.map(feature => [options.promoteId ? feature.properties[options.promoteId] : feature.id, feature])),
219
217
  update: new Map(diff.update?.map(update => [update.id, update]))
220
218
  };
221
219
 
package/src/feature.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature } from "./definitions";
1
+ import type { GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, SliceArray, SliceFixedArray } from "./definitions";
2
2
 
3
3
  type FeatureTypeMap = {
4
4
  Point: GeoJSONVTInternalPointFeature["geometry"];
@@ -35,25 +35,28 @@ export function createFeature<T extends GeoJSONVTInternalFeature["type"]>(id: nu
35
35
  switch (data.type) {
36
36
  case 'Point':
37
37
  case 'MultiPoint':
38
- case 'LineString':
39
38
  calcLineBBox(feature, data.geom);
40
39
  break;
41
40
 
41
+ case 'LineString':
42
+ calcLineBBox(feature, data.geom.points);
43
+ break;
44
+
42
45
  case 'Polygon':
43
46
  // the outer ring (ie [0]) contains all inner rings
44
- calcLineBBox(feature, data.geom[0]);
47
+ calcLineBBox(feature, data.geom[0].points);
45
48
  break;
46
49
 
47
50
  case 'MultiLineString':
48
51
  for (const line of data.geom) {
49
- calcLineBBox(feature, line);
52
+ calcLineBBox(feature, line.points);
50
53
  }
51
54
  break;
52
55
 
53
56
  case 'MultiPolygon':
54
57
  for (const polygon of data.geom) {
55
58
  // the outer ring (ie [0]) contains all inner rings
56
- calcLineBBox(feature, polygon[0]);
59
+ calcLineBBox(feature, polygon[0].points);
57
60
  }
58
61
  break;
59
62
  }
@@ -61,7 +64,14 @@ export function createFeature<T extends GeoJSONVTInternalFeature["type"]>(id: nu
61
64
  return feature;
62
65
  }
63
66
 
64
- function calcLineBBox(feature: GeoJSONVTInternalFeature, geom: number[]) {
67
+ export function optimizeLineMemory(line: SliceArray) {
68
+ const lineImmutable = line as SliceFixedArray;
69
+ if (line.points.length > 64) {
70
+ lineImmutable.points = new Float64Array(line.points);
71
+ }
72
+ }
73
+
74
+ function calcLineBBox(feature: GeoJSONVTInternalFeature, geom: number[] | Float64Array) {
65
75
  for (let i = 0; i < geom.length; i += 3) {
66
76
  feature.minX = Math.min(feature.minX, geom[i]);
67
77
  feature.minY = Math.min(feature.minY, geom[i + 1]);
package/src/geojsonvt.ts CHANGED
@@ -26,32 +26,6 @@ export const defaultOptions: GeoJSONVTOptions = {
26
26
  * Main class for creating and managing a vector tile index from GeoJSON data.
27
27
  */
28
28
  export class GeoJSONVT {
29
-
30
- /**
31
- * @internal
32
- * This is for the tests
33
- */
34
- public get tiles() {
35
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
- return (this.tileIndex as any)?.tiles ?? {};
37
- }
38
- /**
39
- * @internal
40
- * This is for the tests
41
- */
42
- public get stats() {
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- return (this.tileIndex as any).stats;
45
- }
46
- /**
47
- * @internal
48
- * This is for the tests
49
- */
50
- public get total() {
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- return (this.tileIndex as any).total;
53
- }
54
-
55
29
  private options: GeoJSONVTOptions;
56
30
 
57
31
  private source?: GeoJSONVTInternalFeature[];
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, StartEndSizeArray, 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} 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';
@@ -22,7 +22,8 @@ export type {
22
22
  GeoJSONVTInternalTile,
23
23
  GeoJSONVTInternalTileFeature,
24
24
  PartialGeoJSONVTFeature,
25
- StartEndSizeArray,
25
+ SliceArray,
26
+ SliceFixedArray,
26
27
  GeoJSONVTTile,
27
28
  GeoJSONVTFeature,
28
29
  GeoJSONVTSourceDiff,
@@ -0,0 +1,270 @@
1
+ import {describe, expect, test} from "vitest";
2
+ import {TileIndex} from "./tile-index";
3
+ import {convertToInternal} from "./convert";
4
+ import {applySourceDiff} from "./difference";
5
+ import {wrap} from "./wrap";
6
+ import {defaultOptions} from "./geojsonvt";
7
+
8
+ function toID(z: number, x: number, y: number): number {
9
+ return (((1 << z) * y + x) * 32) + z;
10
+ }
11
+
12
+ describe('TileIndex', () => {
13
+ test('updateData: invalidates empty tiles', () => {
14
+ const initialData = {
15
+ type: 'FeatureCollection' as const,
16
+ features: [
17
+ {
18
+ type: 'Feature' as const,
19
+ id: 'nw-only',
20
+ geometry: {
21
+ type: 'Point' as const,
22
+ coordinates: [-90, 45] // top left quadrant only
23
+ },
24
+ properties: {}
25
+ }
26
+ ]
27
+ };
28
+ const options = {
29
+ ...defaultOptions,
30
+ updateable: true,
31
+ indexMaxZoom: 1,
32
+ indexMaxPoints: 0,
33
+ debug: 2
34
+ };
35
+ const index = new TileIndex(options);
36
+ let sourceFeatures = convertToInternal(initialData, options);
37
+ sourceFeatures = wrap(sourceFeatures, options);
38
+ index.initialize(sourceFeatures);
39
+ expect(index.stats.z1).toBe(4);
40
+
41
+ const globalFeature = {
42
+ type: 'Feature' as const,
43
+ id: 'global',
44
+ geometry: {
45
+ type: 'LineString' as const,
46
+ coordinates: [[-180, -85], [180, 85]] // spans whole world
47
+ },
48
+ properties: {}
49
+ };
50
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [globalFeature]}, options);
51
+ index.updateIndex(source, affected, options);
52
+ expect(index.stats.z1).toBe(0);
53
+ });
54
+
55
+ test('updateData: invalidates tiles when feature is within the buffer edge', () => {
56
+ const initialData = {
57
+ type: 'FeatureCollection' as const,
58
+ features: [{
59
+ type: 'Feature' as const,
60
+ id: 'feature1',
61
+ geometry: {
62
+ type: 'Point' as const,
63
+ coordinates: [-45, 45] // inside tile 1-0-0
64
+ },
65
+ properties: {}
66
+ }]
67
+ };
68
+
69
+ const options = {
70
+ ...defaultOptions,
71
+ updateable: true,
72
+ indexMaxZoom: 1,
73
+ indexMaxPoints: 0
74
+ };
75
+ const index = new TileIndex(options);
76
+ let sourceFeatures = convertToInternal(initialData, options);
77
+ sourceFeatures = wrap(sourceFeatures, options);
78
+ index.initialize(sourceFeatures);
79
+
80
+ const tileId = toID(1, 0, 0);
81
+ index.getTile(1, 0, 0);
82
+ expect(index.tiles[tileId]).toBeTruthy();
83
+
84
+ const featureWithinBuffer = {
85
+ type: 'Feature' as const,
86
+ id: 'buffer-feature',
87
+ geometry: {
88
+ type: 'Point' as const,
89
+ coordinates: [2, 0] // feature within tile buffer edge
90
+ },
91
+ properties: {}
92
+ };
93
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [featureWithinBuffer]}, options);
94
+ index.updateIndex(source, affected, options);
95
+ expect(index.tiles[tileId]).toBeUndefined();
96
+ });
97
+
98
+ test('updateData: invalidates tiles at deeper zoom', () => {
99
+ const initialData = {
100
+ type: 'FeatureCollection' as const,
101
+ features: [{
102
+ type: 'Feature' as const,
103
+ id: 'feature1',
104
+ geometry: {
105
+ type: 'Polygon' as const,
106
+ coordinates: [[
107
+ [0, 0], [5, 0], [5, 5], [0, 5], [0, 0]
108
+ ]]
109
+ },
110
+ properties: {name: 'Original'}
111
+ }]
112
+ };
113
+
114
+ const options = {
115
+ ...defaultOptions,
116
+ updateable: true,
117
+ indexMaxZoom: 5,
118
+ indexMaxPoints: 0
119
+ };
120
+ const index = new TileIndex(options);
121
+ let sourceFeatures = convertToInternal(initialData, options);
122
+ sourceFeatures = wrap(sourceFeatures, options);
123
+ index.initialize(sourceFeatures);
124
+
125
+ const tileId = toID(5, 16, 16);
126
+
127
+ const tileBefore = index.tiles[tileId];
128
+ expect(tileBefore).toBeTruthy();
129
+ expect(tileBefore.numFeatures).toBe(1);
130
+
131
+ const updatedFeature = {
132
+ type: 'Feature' as const,
133
+ id: 'feature1',
134
+ geometry: {
135
+ type: 'Polygon' as const,
136
+ coordinates: [[
137
+ [0, 0], [10, 0], [10, 10], [0, 10], [0, 0]
138
+ ]]
139
+ },
140
+ properties: {name: 'Updated'}
141
+ };
142
+
143
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [updatedFeature]}, options);
144
+ index.updateIndex(source, affected, options);
145
+
146
+ const tileAfter = index.tiles[tileId];
147
+ expect(tileAfter).toBeUndefined();
148
+
149
+ const tileRegenerated = index.getTile(5, 16, 16);
150
+ expect(tileRegenerated).toBeTruthy();
151
+ expect(tileRegenerated.features[0].tags.name).toBe('Updated');
152
+ });
153
+
154
+ test('updateData: does not invalidate unaffected tiles', () => {
155
+ const initialData = {
156
+ type: 'FeatureCollection' as const,
157
+ features: [
158
+ {
159
+ type: 'Feature' as const,
160
+ id: 'northwest',
161
+ geometry: {
162
+ type: 'Point' as const,
163
+ coordinates: [-90, 45] // NW quadrant only
164
+ },
165
+ properties: {}
166
+ },
167
+ {
168
+ type: 'Feature' as const,
169
+ id: 'southeast',
170
+ geometry: {
171
+ type: 'Point' as const,
172
+ coordinates: [90, -45] // SE quadrant only
173
+ },
174
+ properties: {}
175
+ }
176
+ ]
177
+ };
178
+
179
+ const options = {
180
+ ...defaultOptions,
181
+ updateable: true,
182
+ indexMaxZoom: 2,
183
+ indexMaxPoints: 0
184
+ };
185
+ const index = new TileIndex(options);
186
+ let sourceFeatures = convertToInternal(initialData, options);
187
+ sourceFeatures = wrap(sourceFeatures, options);
188
+ index.initialize(sourceFeatures);
189
+
190
+ const nwTileId = toID(1, 0, 0);
191
+ const seTileId = toID(1, 1, 1);
192
+
193
+ const nwTileBefore = index.tiles[nwTileId];
194
+ const seTileBefore = index.tiles[seTileId];
195
+
196
+ expect(nwTileBefore).toBeTruthy();
197
+ expect(seTileBefore).toBeTruthy();
198
+
199
+ const updatedFeature = {
200
+ type: 'Feature' as const,
201
+ id: 'northwest',
202
+ geometry: {
203
+ type: 'Point' as const,
204
+ coordinates: [-85, 40] // NW different coordinate
205
+ },
206
+ properties: {}
207
+ };
208
+
209
+
210
+ const {source, affected} = applySourceDiff(sourceFeatures, {add: [updatedFeature]}, options);
211
+ index.updateIndex(source, affected, options);
212
+
213
+ const nwTileAfter = index.tiles[nwTileId];
214
+ expect(nwTileAfter).toBeUndefined();
215
+
216
+ const seTileAfter = index.tiles[seTileId];
217
+ expect(seTileAfter).toBe(seTileBefore);
218
+ });
219
+ });
220
+
221
+ describe('Multiworld test', () => {
222
+ const leftPoint = {
223
+ type: 'Feature' as const,
224
+ properties: {},
225
+ geometry: {
226
+ coordinates: [-540, 0],
227
+ type: 'Point' as const
228
+ }
229
+ };
230
+
231
+ const rightPoint = {
232
+ type: 'Feature' as const,
233
+ properties: {},
234
+ geometry: {
235
+ coordinates: [540, 0],
236
+ type: 'Point' as const
237
+ }
238
+ };
239
+
240
+ test('handle point only in the rightside world', () => {
241
+ const vt = new TileIndex(defaultOptions);
242
+ let sourceFeatures = convertToInternal(rightPoint, defaultOptions);
243
+ sourceFeatures = wrap(sourceFeatures, defaultOptions);
244
+ vt.initialize(sourceFeatures);
245
+ expect(vt.tiles[0].features[0].geometry[0]).toBe(1);
246
+ expect(vt.tiles[0].features[0].geometry[1]).toBe(.5);
247
+ });
248
+
249
+ test('handle point only in the leftside world', () => {
250
+ const vt = new TileIndex(defaultOptions);
251
+ let sourceFeatures = convertToInternal(leftPoint, defaultOptions);
252
+ sourceFeatures = wrap(sourceFeatures, defaultOptions);
253
+ vt.initialize(sourceFeatures);
254
+ expect(vt.tiles[0].features[0].geometry[0]).toBe(0);
255
+ expect(vt.tiles[0].features[0].geometry[1]).toBe(.5);
256
+ });
257
+
258
+ test('handle points in the leftside world and the rightside world', () => {
259
+ const vt = new TileIndex(defaultOptions);
260
+ let sourceFeatures = convertToInternal({type: 'FeatureCollection', features: [leftPoint, rightPoint]}, defaultOptions);
261
+ sourceFeatures = wrap(sourceFeatures, defaultOptions);
262
+ vt.initialize(sourceFeatures);
263
+
264
+ expect(vt.tiles[0].features[0].geometry[0]).toBe(0);
265
+ expect(vt.tiles[0].features[0].geometry[1]).toBe(.5);
266
+
267
+ expect(vt.tiles[0].features[1].geometry[0]).toBe(1);
268
+ expect(vt.tiles[0].features[1].geometry[1]).toBe(.5);
269
+ });
270
+ })
package/src/tile-index.ts CHANGED
@@ -6,13 +6,13 @@ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, ClusterOrPointFeature,
6
6
  export class TileIndex implements GeoJSONVTTileIndex {
7
7
 
8
8
  private tileCoords: {z: number, x: number, y: number, id: number}[];
9
+ private total: number = 0;
9
10
 
10
11
  /** @internal */
11
12
  public tiles: {[key: string]: GeoJSONVTInternalTile};
12
13
  /** @internal */
13
14
  public stats: {[key: string]: number} = {};
14
- /** @internal */
15
- public total: number = 0;
15
+
16
16
 
17
17
  constructor(private options: GeoJSONVTOptions) {
18
18
  this.tiles = {};
@@ -54,7 +54,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
54
54
  this.tileCoords.push({z, x, y, id});
55
55
 
56
56
  if (options.debug) {
57
- const key = `z${ z}`;
57
+ const key = `z${z}`;
58
58
  this.stats[key] = (this.stats[key] || 0) + 1;
59
59
  this.total++;
60
60
  }
@@ -164,7 +164,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
164
164
  z, x, y, tile.numFeatures, tile.numPoints, tile.numSimplified);
165
165
  console.timeEnd('creation');
166
166
  }
167
- const key = `z${ z}`;
167
+ const key = `z${z}`;
168
168
  this.stats[key] = (this.stats[key] || 0) + 1;
169
169
  this.total++;
170
170
  }
@@ -289,7 +289,7 @@ export class TileIndex implements GeoJSONVTTileIndex {
289
289
  console.log('invalidate tile z%d-%d-%d (features: %d, points: %d, simplified: %d)',
290
290
  tile.z, tile.x, tile.y, tile.numFeatures, tile.numPoints, tile.numSimplified);
291
291
  }
292
- const key = `z${ tile.z}`;
292
+ const key = `z${tile.z}`;
293
293
  this.stats[key] = (this.stats[key] || 0) - 1;
294
294
  this.total--;
295
295
  }
package/src/tile.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, GeoJSONVTInternalTile, GeoJSONVTInternalTileFeature, GeoJSONVTOptions, StartEndSizeArray } from "./definitions";
1
+ import type { GeoJSONVTInternalFeature, GeoJSONVTInternalLineStringFeature, GeoJSONVTInternalMultiLineStringFeature, GeoJSONVTInternalMultiPointFeature, GeoJSONVTInternalMultiPolygonFeature, GeoJSONVTInternalPointFeature, GeoJSONVTInternalPolygonFeature, GeoJSONVTInternalTile, GeoJSONVTInternalTileFeature, GeoJSONVTOptions, SliceFixedArray } from "./definitions";
2
2
 
3
3
  export const GEOJSONVT_CLIP_START = 'geojsonvt_clip_start';
4
4
  export const GEOJSONVT_CLIP_END = 'geojsonvt_clip_end';
@@ -140,20 +140,20 @@ function addMultiPolygonTileFeature(tile: GeoJSONVTInternalTile, feature: GeoJSO
140
140
  tile.features.push(tileFeature);
141
141
  }
142
142
 
143
- function addLine(result: number[][], geom: StartEndSizeArray, tile: GeoJSONVTInternalTile, tolerance: number, isPolygon: boolean, isOuter: boolean) {
143
+ function addLine(result: number[][], geom: SliceFixedArray, tile: GeoJSONVTInternalTile, tolerance: number, isPolygon: boolean, isOuter: boolean) {
144
144
  const sqTolerance = tolerance * tolerance;
145
145
 
146
146
  if (tolerance > 0 && (geom.size < (isPolygon ? sqTolerance : tolerance))) {
147
- tile.numPoints += geom.length / 3;
147
+ tile.numPoints += geom.points.length / 3;
148
148
  return;
149
149
  }
150
150
 
151
151
  const ring = [];
152
152
 
153
- for (let i = 0; i < geom.length; i += 3) {
154
- if (tolerance === 0 || geom[i + 2] > sqTolerance) {
153
+ for (let i = 0; i < geom.points.length; i += 3) {
154
+ if (tolerance === 0 || geom.points[i + 2] > sqTolerance) {
155
155
  tile.numSimplified++;
156
- ring.push(geom[i], geom[i + 1]);
156
+ ring.push(geom.points[i], geom.points[i + 1]);
157
157
  }
158
158
  tile.numPoints++;
159
159
  }
package/src/wrap.ts CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  import {AxisType, clip} from './clip';
3
- import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, StartEndSizeArray } from './definitions';
4
- import {createFeature} from './feature';
3
+ import type { GeoJSONVTInternalFeature, GeoJSONVTOptions, SliceArray, SliceFixedArray } from './definitions';
4
+ import {createFeature, optimizeLineMemory} from './feature';
5
5
 
6
6
  export function wrap(features: GeoJSONVTInternalFeature[], options: GeoJSONVTOptions): GeoJSONVTInternalFeature[] {
7
7
  const buffer = options.buffer / options.extent;
@@ -26,9 +26,15 @@ function shiftFeatureCoords(features: GeoJSONVTInternalFeature[], offset: number
26
26
  for (const feature of features) {
27
27
  switch (feature.type) {
28
28
  case 'Point':
29
- case 'MultiPoint':
29
+ case 'MultiPoint': {
30
+ const newGeometry = shiftPointCoords(feature.geometry, offset);
31
+
32
+ newFeatures.push(createFeature(feature.id, feature.type, newGeometry, feature.tags));
33
+ continue;
34
+ }
35
+
30
36
  case 'LineString': {
31
- const newGeometry = shiftCoords(feature.geometry, offset);
37
+ const newGeometry = shiftLineCoords(feature.geometry, offset);
32
38
 
33
39
  newFeatures.push(createFeature(feature.id, feature.type, newGeometry, feature.tags));
34
40
  continue;
@@ -36,9 +42,9 @@ function shiftFeatureCoords(features: GeoJSONVTInternalFeature[], offset: number
36
42
 
37
43
  case 'MultiLineString':
38
44
  case 'Polygon': {
39
- const newGeometry = [];
45
+ const newGeometry: SliceFixedArray[] = [];
40
46
  for (const line of feature.geometry) {
41
- newGeometry.push(shiftCoords(line, offset));
47
+ newGeometry.push(shiftLineCoords(line, offset));
42
48
  }
43
49
 
44
50
  newFeatures.push(createFeature(feature.id, feature.type, newGeometry, feature.tags));
@@ -46,11 +52,11 @@ function shiftFeatureCoords(features: GeoJSONVTInternalFeature[], offset: number
46
52
  }
47
53
 
48
54
  case 'MultiPolygon': {
49
- const newGeometry = [];
55
+ const newGeometry: SliceFixedArray[][] = [];
50
56
  for (const polygon of feature.geometry) {
51
- const newPolygon = [];
57
+ const newPolygon: SliceFixedArray[] = [];
52
58
  for (const line of polygon) {
53
- newPolygon.push(shiftCoords(line, offset));
59
+ newPolygon.push(shiftLineCoords(line, offset));
54
60
  }
55
61
  newGeometry.push(newPolygon);
56
62
  }
@@ -64,18 +70,32 @@ function shiftFeatureCoords(features: GeoJSONVTInternalFeature[], offset: number
64
70
  return newFeatures;
65
71
  }
66
72
 
67
- function shiftCoords(points: StartEndSizeArray, offset: number): number[] | StartEndSizeArray {
68
- const newPoints: StartEndSizeArray = [];
69
- newPoints.size = points.size;
73
+ function shiftPointCoords(coords: number[], offset: number): number[] {
74
+ const newCoords: number[] = [];
70
75
 
71
- if (points.start !== undefined) {
72
- newPoints.start = points.start;
73
- newPoints.end = points.end;
76
+ for (let i = 0; i < coords.length; i += 3) {
77
+ newCoords.push(coords[i] + offset, coords[i + 1], coords[i + 2]);
74
78
  }
75
79
 
76
- for (let i = 0; i < points.length; i += 3) {
77
- newPoints.push(points[i] + offset, points[i + 1], points[i + 2]);
80
+ return newCoords;
81
+ }
82
+
83
+ function shiftLineCoords(line: SliceArray | SliceFixedArray, offset: number): SliceFixedArray {
84
+ const newLine: SliceArray = {
85
+ points: [],
86
+ size: line.size
87
+ };
88
+
89
+ if (line.start !== undefined) {
90
+ newLine.start = line.start;
91
+ newLine.end = line.end;
78
92
  }
79
93
 
80
- return newPoints;
94
+ for (let i = 0; i < line.points.length; i += 3) {
95
+ newLine.points.push(line.points[i] + offset, line.points[i + 1], line.points[i + 2]);
96
+ }
97
+
98
+ optimizeLineMemory(newLine);
99
+
100
+ return newLine;
81
101
  }