@loaders.gl/gis 3.1.0 → 3.1.1

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.
Files changed (49) hide show
  1. package/dist/es5/index.js +16 -0
  2. package/dist/es5/index.js.map +1 -1
  3. package/dist/es5/lib/extract-geometry-info.js +204 -0
  4. package/dist/es5/lib/extract-geometry-info.js.map +1 -0
  5. package/dist/es5/lib/flat-geojson-to-binary-types.js +2 -0
  6. package/dist/es5/lib/flat-geojson-to-binary-types.js.map +1 -0
  7. package/dist/es5/lib/flat-geojson-to-binary.js +397 -0
  8. package/dist/es5/lib/flat-geojson-to-binary.js.map +1 -0
  9. package/dist/es5/lib/geojson-to-binary.js +15 -579
  10. package/dist/es5/lib/geojson-to-binary.js.map +1 -1
  11. package/dist/es5/lib/geojson-to-flat-geojson.js +169 -0
  12. package/dist/es5/lib/geojson-to-flat-geojson.js.map +1 -0
  13. package/dist/esm/index.js +2 -0
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/lib/extract-geometry-info.js +105 -0
  16. package/dist/esm/lib/extract-geometry-info.js.map +1 -0
  17. package/dist/esm/lib/flat-geojson-to-binary-types.js +2 -0
  18. package/dist/esm/lib/flat-geojson-to-binary-types.js.map +1 -0
  19. package/dist/esm/lib/flat-geojson-to-binary.js +339 -0
  20. package/dist/esm/lib/flat-geojson-to-binary.js.map +1 -0
  21. package/dist/esm/lib/geojson-to-binary.js +17 -410
  22. package/dist/esm/lib/geojson-to-binary.js.map +1 -1
  23. package/dist/esm/lib/geojson-to-flat-geojson.js +116 -0
  24. package/dist/esm/lib/geojson-to-flat-geojson.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -1
  28. package/dist/lib/extract-geometry-info.d.ts +8 -0
  29. package/dist/lib/extract-geometry-info.d.ts.map +1 -0
  30. package/dist/lib/extract-geometry-info.js +96 -0
  31. package/dist/lib/flat-geojson-to-binary-types.d.ts +58 -0
  32. package/dist/lib/flat-geojson-to-binary-types.d.ts.map +1 -0
  33. package/dist/lib/flat-geojson-to-binary-types.js +2 -0
  34. package/dist/lib/flat-geojson-to-binary.d.ts +37 -0
  35. package/dist/lib/flat-geojson-to-binary.d.ts.map +1 -0
  36. package/dist/lib/flat-geojson-to-binary.js +375 -0
  37. package/dist/lib/geojson-to-binary.d.ts +12 -36
  38. package/dist/lib/geojson-to-binary.d.ts.map +1 -1
  39. package/dist/lib/geojson-to-binary.js +18 -360
  40. package/dist/lib/geojson-to-flat-geojson.d.ts +17 -0
  41. package/dist/lib/geojson-to-flat-geojson.d.ts.map +1 -0
  42. package/dist/lib/geojson-to-flat-geojson.js +128 -0
  43. package/package.json +5 -4
  44. package/src/index.ts +2 -0
  45. package/src/lib/extract-geometry-info.ts +101 -0
  46. package/src/lib/flat-geojson-to-binary-types.ts +58 -0
  47. package/src/lib/flat-geojson-to-binary.ts +564 -0
  48. package/src/lib/geojson-to-binary.ts +24 -459
  49. package/src/lib/geojson-to-flat-geojson.ts +171 -0
@@ -1,471 +1,36 @@
1
- import {Feature, GeoJsonProperties} from '@loaders.gl/schema';
1
+ import type {Feature} from '@loaders.gl/schema';
2
2
  import type {BinaryFeatures} from '@loaders.gl/schema';
3
3
 
4
+ import {extractGeometryInfo} from './extract-geometry-info';
5
+ import {geojsonToFlatGeojson} from './geojson-to-flat-geojson';
6
+ import {flatGeojsonToBinary} from './flat-geojson-to-binary';
7
+
8
+ /**
9
+ * Options for `geojsonToBinary`
10
+ */
4
11
  export type GeojsonToBinaryOptions = {
5
- coordLength?: number;
12
+ fixRingWinding: boolean;
6
13
  numericPropKeys?: string[];
7
- PositionDataType?: Function;
14
+ PositionDataType?: Float32ArrayConstructor | Float64ArrayConstructor;
8
15
  };
9
16
 
10
- /** Convert GeoJSON features to flat binary arrays */
17
+ /**
18
+ * Convert GeoJSON features to flat binary arrays
19
+ *
20
+ * @param features
21
+ * @param options
22
+ * @returns features in binary format, grouped by geometry type
23
+ */
11
24
  export function geojsonToBinary(
12
25
  features: Feature[],
13
- options: GeojsonToBinaryOptions = {}
26
+ options: GeojsonToBinaryOptions = {fixRingWinding: true}
14
27
  ): BinaryFeatures {
15
- const firstPassData = firstPass(features);
16
- return secondPass(features, firstPassData, {
17
- coordLength: options.coordLength || firstPassData.coordLength,
18
- numericPropKeys: options.numericPropKeys || firstPassData.numericPropKeys,
28
+ const geometryInfo = extractGeometryInfo(features);
29
+ const coordLength = geometryInfo.coordLength;
30
+ const {fixRingWinding} = options;
31
+ const flatFeatures = geojsonToFlatGeojson(features, {coordLength, fixRingWinding});
32
+ return flatGeojsonToBinary(flatFeatures, geometryInfo, {
33
+ numericPropKeys: options.numericPropKeys,
19
34
  PositionDataType: options.PositionDataType || Float32Array
20
35
  });
21
36
  }
22
-
23
- export const TEST_EXPORTS = {
24
- firstPass,
25
- secondPass
26
- };
27
-
28
- type PropArrayConstructor = Float32ArrayConstructor | Float64ArrayConstructor | ArrayConstructor;
29
-
30
- type FirstPassData = {
31
- coordLength: number;
32
- numericPropKeys: string[];
33
- propArrayTypes: {[key: string]: PropArrayConstructor};
34
-
35
- pointPositionsCount: number;
36
- pointFeaturesCount: number;
37
- linePositionsCount: number;
38
- linePathsCount: number;
39
- lineFeaturesCount: number;
40
- polygonPositionsCount: number;
41
- polygonObjectsCount: number;
42
- polygonRingsCount: number;
43
- polygonFeaturesCount: number;
44
- };
45
-
46
- /**
47
- * Initial scan over GeoJSON features
48
- * Counts number of coordinates of each geometry type and
49
- * keeps track of the max coordinate dimensions
50
- */
51
- // eslint-disable-next-line complexity, max-statements
52
- function firstPass(features: Feature[]): FirstPassData {
53
- // Counts the number of _positions_, so [x, y, z] counts as one
54
- let pointPositionsCount = 0;
55
- let pointFeaturesCount = 0;
56
- let linePositionsCount = 0;
57
- let linePathsCount = 0;
58
- let lineFeaturesCount = 0;
59
- let polygonPositionsCount = 0;
60
- let polygonObjectsCount = 0;
61
- let polygonRingsCount = 0;
62
- let polygonFeaturesCount = 0;
63
- const coordLengths = new Set<number>();
64
- const propArrayTypes = {};
65
-
66
- for (const feature of features) {
67
- const geometry = feature.geometry;
68
- switch (geometry.type) {
69
- case 'Point':
70
- pointFeaturesCount++;
71
- pointPositionsCount++;
72
- coordLengths.add(geometry.coordinates.length);
73
- break;
74
- case 'MultiPoint':
75
- pointFeaturesCount++;
76
- pointPositionsCount += geometry.coordinates.length;
77
- for (const point of geometry.coordinates) {
78
- coordLengths.add(point.length);
79
- }
80
- break;
81
- case 'LineString':
82
- lineFeaturesCount++;
83
- linePositionsCount += geometry.coordinates.length;
84
- linePathsCount++;
85
-
86
- for (const coord of geometry.coordinates) {
87
- coordLengths.add(coord.length);
88
- }
89
- break;
90
- case 'MultiLineString':
91
- lineFeaturesCount++;
92
- for (const line of geometry.coordinates) {
93
- linePositionsCount += line.length;
94
- linePathsCount++;
95
-
96
- // eslint-disable-next-line max-depth
97
- for (const coord of line) {
98
- coordLengths.add(coord.length);
99
- }
100
- }
101
- break;
102
- case 'Polygon':
103
- polygonFeaturesCount++;
104
- polygonObjectsCount++;
105
- polygonRingsCount += geometry.coordinates.length;
106
- polygonPositionsCount += flatten(geometry.coordinates).length;
107
-
108
- for (const coord of flatten(geometry.coordinates)) {
109
- coordLengths.add(coord.length);
110
- }
111
- break;
112
- case 'MultiPolygon':
113
- polygonFeaturesCount++;
114
- for (const polygon of geometry.coordinates) {
115
- polygonObjectsCount++;
116
- polygonRingsCount += polygon.length;
117
- polygonPositionsCount += flatten(polygon).length;
118
-
119
- // eslint-disable-next-line max-depth
120
- for (const coord of flatten(polygon)) {
121
- coordLengths.add(coord.length);
122
- }
123
- }
124
- break;
125
- default:
126
- throw new Error(`Unsupported geometry type: ${geometry.type}`);
127
- }
128
-
129
- if (feature.properties) {
130
- for (const key in feature.properties) {
131
- const val = feature.properties[key];
132
-
133
- // If property has not been seen before, or if property has been numeric
134
- // in all previous features, check if numeric in this feature
135
- // If not numeric, Array is stored to prevent rechecking in the future
136
- // Additionally, detects if 64 bit precision is required
137
- propArrayTypes[key] = deduceArrayType(val, propArrayTypes[key]);
138
- }
139
- }
140
- }
141
-
142
- return {
143
- coordLength: coordLengths.size > 0 ? Math.max(...coordLengths) : 2,
144
-
145
- pointPositionsCount,
146
- pointFeaturesCount,
147
- linePositionsCount,
148
- linePathsCount,
149
- lineFeaturesCount,
150
- polygonPositionsCount,
151
- polygonObjectsCount,
152
- polygonRingsCount,
153
- polygonFeaturesCount,
154
-
155
- // Array of keys whose values are always numeric
156
- numericPropKeys: Object.keys(propArrayTypes).filter((k) => propArrayTypes[k] !== Array),
157
- propArrayTypes
158
- };
159
- }
160
-
161
- /**
162
- * Second scan over GeoJSON features
163
- * Fills coordinates into pre-allocated typed arrays
164
- */
165
- // eslint-disable-next-line complexity
166
- function secondPass(
167
- features,
168
- firstPassData: FirstPassData,
169
- options: Required<GeojsonToBinaryOptions>
170
- ) {
171
- const {
172
- pointPositionsCount,
173
- pointFeaturesCount,
174
- linePositionsCount,
175
- linePathsCount,
176
- lineFeaturesCount,
177
- polygonPositionsCount,
178
- polygonObjectsCount,
179
- polygonRingsCount,
180
- propArrayTypes,
181
- polygonFeaturesCount
182
- } = firstPassData;
183
- const {coordLength, numericPropKeys, PositionDataType = Float32Array} = options;
184
- const GlobalFeatureIdsDataType = features.length > 65535 ? Uint32Array : Uint16Array;
185
- const points = {
186
- // @ts-ignore Typescript doesn't like dynamic constructors
187
- positions: new PositionDataType(pointPositionsCount * coordLength),
188
- globalFeatureIds: new GlobalFeatureIdsDataType(pointPositionsCount),
189
- featureIds:
190
- pointFeaturesCount > 65535
191
- ? new Uint32Array(pointPositionsCount)
192
- : new Uint16Array(pointPositionsCount),
193
- numericProps: {},
194
- properties: Array<any>(),
195
- fields: Array<any>()
196
- };
197
- const lines = {
198
- // @ts-ignore Typescript doesn't like dynamic constructors
199
- positions: new PositionDataType(linePositionsCount * coordLength),
200
- pathIndices:
201
- linePositionsCount > 65535
202
- ? new Uint32Array(linePathsCount + 1)
203
- : new Uint16Array(linePathsCount + 1),
204
- globalFeatureIds: new GlobalFeatureIdsDataType(linePositionsCount),
205
- featureIds:
206
- lineFeaturesCount > 65535
207
- ? new Uint32Array(linePositionsCount)
208
- : new Uint16Array(linePositionsCount),
209
- numericProps: {},
210
- properties: Array<any>(),
211
- fields: Array<any>()
212
- };
213
- const polygons = {
214
- // @ts-ignore Typescript doesn't like dynamic constructors
215
- positions: new PositionDataType(polygonPositionsCount * coordLength),
216
- polygonIndices:
217
- polygonPositionsCount > 65535
218
- ? new Uint32Array(polygonObjectsCount + 1)
219
- : new Uint16Array(polygonObjectsCount + 1),
220
- primitivePolygonIndices:
221
- polygonPositionsCount > 65535
222
- ? new Uint32Array(polygonRingsCount + 1)
223
- : new Uint16Array(polygonRingsCount + 1),
224
- globalFeatureIds: new GlobalFeatureIdsDataType(polygonPositionsCount),
225
- featureIds:
226
- polygonFeaturesCount > 65535
227
- ? new Uint32Array(polygonPositionsCount)
228
- : new Uint16Array(polygonPositionsCount),
229
- numericProps: {},
230
- properties: Array<any>(),
231
- fields: Array<any>()
232
- };
233
-
234
- // Instantiate numeric properties arrays; one value per vertex
235
- for (const object of [points, lines, polygons]) {
236
- for (const propName of numericPropKeys || []) {
237
- // If property has been numeric in all previous features in which the property existed, check
238
- // if numeric in this feature
239
- const TypedArray = propArrayTypes[propName];
240
- object.numericProps[propName] = new TypedArray(object.positions.length / coordLength);
241
- }
242
- }
243
-
244
- // Set last element of path/polygon indices as positions length
245
- lines.pathIndices[linePathsCount] = linePositionsCount;
246
- polygons.polygonIndices[polygonObjectsCount] = polygonPositionsCount;
247
- polygons.primitivePolygonIndices[polygonRingsCount] = polygonPositionsCount;
248
-
249
- const indexMap = {
250
- pointPosition: 0,
251
- pointFeature: 0,
252
- linePosition: 0,
253
- linePath: 0,
254
- lineFeature: 0,
255
- polygonPosition: 0,
256
- polygonObject: 0,
257
- polygonRing: 0,
258
- polygonFeature: 0,
259
- feature: 0
260
- };
261
-
262
- for (const feature of features) {
263
- const geometry = feature.geometry;
264
- const properties: GeoJsonProperties = feature.properties || {};
265
-
266
- switch (geometry.type) {
267
- case 'Point':
268
- handlePoint(geometry.coordinates, points, indexMap, coordLength, properties);
269
- points.properties.push(keepStringProperties(properties, numericPropKeys));
270
- indexMap.pointFeature++;
271
- break;
272
- case 'MultiPoint':
273
- handleMultiPoint(geometry.coordinates, points, indexMap, coordLength, properties);
274
- points.properties.push(keepStringProperties(properties, numericPropKeys));
275
- indexMap.pointFeature++;
276
- break;
277
- case 'LineString':
278
- handleLineString(geometry.coordinates, lines, indexMap, coordLength, properties);
279
- lines.properties.push(keepStringProperties(properties, numericPropKeys));
280
- indexMap.lineFeature++;
281
- break;
282
- case 'MultiLineString':
283
- handleMultiLineString(geometry.coordinates, lines, indexMap, coordLength, properties);
284
- lines.properties.push(keepStringProperties(properties, numericPropKeys));
285
- indexMap.lineFeature++;
286
- break;
287
- case 'Polygon':
288
- handlePolygon(geometry.coordinates, polygons, indexMap, coordLength, properties);
289
- polygons.properties.push(keepStringProperties(properties, numericPropKeys));
290
- indexMap.polygonFeature++;
291
- break;
292
- case 'MultiPolygon':
293
- handleMultiPolygon(geometry.coordinates, polygons, indexMap, coordLength, properties);
294
- polygons.properties.push(keepStringProperties(properties, numericPropKeys));
295
- indexMap.polygonFeature++;
296
- break;
297
- default:
298
- throw new Error('Invalid geometry type');
299
- }
300
-
301
- indexMap.feature++;
302
- }
303
-
304
- // Wrap each array in an accessor object with value and size keys
305
- return makeAccessorObjects(points, lines, polygons, coordLength);
306
- }
307
-
308
- /** Fills Point coordinates into points object of arrays */
309
- function handlePoint(coords, points, indexMap, coordLength, properties) {
310
- points.positions.set(coords, indexMap.pointPosition * coordLength);
311
- points.globalFeatureIds[indexMap.pointPosition] = indexMap.feature;
312
- points.featureIds[indexMap.pointPosition] = indexMap.pointFeature;
313
-
314
- fillNumericProperties(points, properties, indexMap.pointPosition, 1);
315
- indexMap.pointPosition++;
316
- }
317
-
318
- /** Fills MultiPoint coordinates into points object of arrays */
319
- function handleMultiPoint(coords, points, indexMap, coordLength, properties) {
320
- for (const point of coords) {
321
- handlePoint(point, points, indexMap, coordLength, properties);
322
- }
323
- }
324
-
325
- /** Fills LineString coordinates into lines object of arrays */
326
- function handleLineString(coords, lines, indexMap, coordLength, properties) {
327
- lines.pathIndices[indexMap.linePath] = indexMap.linePosition;
328
- indexMap.linePath++;
329
-
330
- fillCoords(lines.positions, coords, indexMap.linePosition, coordLength);
331
-
332
- const nPositions = coords.length;
333
- fillNumericProperties(lines, properties, indexMap.linePosition, nPositions);
334
-
335
- lines.globalFeatureIds.set(
336
- new Uint32Array(nPositions).fill(indexMap.feature),
337
- indexMap.linePosition
338
- );
339
- lines.featureIds.set(
340
- new Uint32Array(nPositions).fill(indexMap.lineFeature),
341
- indexMap.linePosition
342
- );
343
- indexMap.linePosition += nPositions;
344
- }
345
-
346
- /** Fills MultiLineString coordinates into lines object of arrays */
347
- function handleMultiLineString(coords, lines, indexMap, coordLength, properties) {
348
- for (const line of coords) {
349
- handleLineString(line, lines, indexMap, coordLength, properties);
350
- }
351
- }
352
-
353
- /** Fills Polygon coordinates into polygons object of arrays */
354
- function handlePolygon(coords, polygons, indexMap, coordLength, properties) {
355
- polygons.polygonIndices[indexMap.polygonObject] = indexMap.polygonPosition;
356
- indexMap.polygonObject++;
357
-
358
- for (const ring of coords) {
359
- polygons.primitivePolygonIndices[indexMap.polygonRing] = indexMap.polygonPosition;
360
- indexMap.polygonRing++;
361
-
362
- fillCoords(polygons.positions, ring, indexMap.polygonPosition, coordLength);
363
-
364
- const nPositions = ring.length;
365
- fillNumericProperties(polygons, properties, indexMap.polygonPosition, nPositions);
366
-
367
- polygons.globalFeatureIds.set(
368
- new Uint32Array(nPositions).fill(indexMap.feature),
369
- indexMap.polygonPosition
370
- );
371
- polygons.featureIds.set(
372
- new Uint32Array(nPositions).fill(indexMap.polygonFeature),
373
- indexMap.polygonPosition
374
- );
375
- indexMap.polygonPosition += nPositions;
376
- }
377
- }
378
-
379
- /** Fills MultiPolygon coordinates into polygons object of arrays */
380
- function handleMultiPolygon(coords, polygons, indexMap, coordLength, properties) {
381
- for (const polygon of coords) {
382
- handlePolygon(polygon, polygons, indexMap, coordLength, properties);
383
- }
384
- }
385
-
386
- /** Wrap each array in an accessor object with value and size keys */
387
- function makeAccessorObjects(points, lines, polygons, coordLength): BinaryFeatures {
388
- const returnObj = {
389
- points: {
390
- ...points,
391
- positions: {value: points.positions, size: coordLength},
392
- globalFeatureIds: {value: points.globalFeatureIds, size: 1},
393
- featureIds: {value: points.featureIds, size: 1},
394
- type: 'Point'
395
- },
396
- lines: {
397
- ...lines,
398
- pathIndices: {value: lines.pathIndices, size: 1},
399
- positions: {value: lines.positions, size: coordLength},
400
- globalFeatureIds: {value: lines.globalFeatureIds, size: 1},
401
- featureIds: {value: lines.featureIds, size: 1},
402
- type: 'LineString'
403
- },
404
- polygons: {
405
- ...polygons,
406
- polygonIndices: {value: polygons.polygonIndices, size: 1},
407
- primitivePolygonIndices: {value: polygons.primitivePolygonIndices, size: 1},
408
- positions: {value: polygons.positions, size: coordLength},
409
- globalFeatureIds: {value: polygons.globalFeatureIds, size: 1},
410
- featureIds: {value: polygons.featureIds, size: 1},
411
- type: 'Polygon'
412
- }
413
- };
414
-
415
- for (const geomType in returnObj) {
416
- for (const numericProp in returnObj[geomType].numericProps) {
417
- returnObj[geomType].numericProps[numericProp] = {
418
- value: returnObj[geomType].numericProps[numericProp],
419
- size: 1
420
- };
421
- }
422
- }
423
-
424
- return returnObj as unknown as BinaryFeatures;
425
- }
426
-
427
- /** Add numeric properties to object */
428
- function fillNumericProperties(object, properties, index, length) {
429
- for (const numericPropName in object.numericProps) {
430
- if (numericPropName in properties) {
431
- object.numericProps[numericPropName].set(
432
- new Array(length).fill(properties[numericPropName]),
433
- index
434
- );
435
- }
436
- }
437
- }
438
-
439
- /** Keep string properties in object */
440
- function keepStringProperties(properties, numericKeys: string[]): GeoJsonProperties {
441
- const props = {};
442
- for (const key in properties) {
443
- if (!numericKeys.includes(key)) {
444
- props[key] = properties[key];
445
- }
446
- }
447
- return props;
448
- }
449
-
450
- /** @param coords is expected to be a list of arrays, each with length 2-3 */
451
- function fillCoords(array, coords, startVertex, coordLength): void {
452
- let index = startVertex * coordLength;
453
- for (const coord of coords) {
454
- array.set(coord, index);
455
- index += coordLength;
456
- }
457
- }
458
-
459
- // TODO - how does this work? Different `coordinates` have different nesting
460
- function flatten(arrays): number[][] {
461
- return [].concat(...arrays);
462
- }
463
-
464
- function deduceArrayType(x: any, constructor: PropArrayConstructor): PropArrayConstructor {
465
- if (constructor === Array || !Number.isFinite(x)) {
466
- return Array;
467
- }
468
-
469
- // If this or previous value required 64bits use Float64Array
470
- return constructor === Float64Array || Math.fround(x) !== x ? Float64Array : Float32Array;
471
- }
@@ -0,0 +1,171 @@
1
+ import {getPolygonSignedArea} from '@math.gl/polygon';
2
+
3
+ import {Feature, Position, FlatFeature} from '@loaders.gl/schema';
4
+
5
+ /**
6
+ * Options for `geojsonToFlatGeojson`
7
+ */
8
+ export type GeojsonToFlatGeojsonOptions = {
9
+ coordLength: number;
10
+ fixRingWinding: boolean;
11
+ };
12
+
13
+ // Coordinates defining a Point
14
+ type PointCoordinates = Position;
15
+ // Coordinates defining a LineString
16
+ type LineStringCoordinates = Position[];
17
+ // Coordinates defining a Polygon
18
+ type PolygonCoordinates = Position[][];
19
+
20
+ /**
21
+ * Convert GeoJSON features to Flat GeoJSON features
22
+ *
23
+ * @param features
24
+ * @param options
25
+ * @returns an Array of Flat GeoJSON features
26
+ */
27
+ export function geojsonToFlatGeojson(
28
+ features: Feature[],
29
+ options: GeojsonToFlatGeojsonOptions = {coordLength: 2, fixRingWinding: true}
30
+ ): FlatFeature[] {
31
+ return features.map((feature) => flattenFeature(feature, options));
32
+ }
33
+
34
+ /**
35
+ * Helper function to copy Point values from `coordinates` into `data` & `indices`
36
+ *
37
+ * @param coordinates
38
+ * @param data
39
+ * @param indices
40
+ * @param options
41
+ */
42
+ function flattenPoint(
43
+ coordinates: PointCoordinates,
44
+ data: number[],
45
+ indices: number[],
46
+ options: GeojsonToFlatGeojsonOptions
47
+ ) {
48
+ indices.push(data.length);
49
+ data.push(...coordinates);
50
+
51
+ // Pad up to coordLength
52
+ for (let i = coordinates.length; i < options.coordLength; i++) {
53
+ data.push(0);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Helper function to copy LineString values from `coordinates` into `data` & `indices`
59
+ *
60
+ * @param coordinates
61
+ * @param data
62
+ * @param indices
63
+ * @param options
64
+ */
65
+ function flattenLineString(
66
+ coordinates: LineStringCoordinates,
67
+ data: number[],
68
+ indices: number[],
69
+ options: GeojsonToFlatGeojsonOptions
70
+ ) {
71
+ indices.push(data.length);
72
+ for (const c of coordinates) {
73
+ data.push(...c);
74
+
75
+ // Pad up to coordLength
76
+ for (let i = c.length; i < options.coordLength; i++) {
77
+ data.push(0);
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Helper function to copy Polygon values from `coordinates` into `data` & `indices` & `areas`
84
+ *
85
+ * @param coordinates
86
+ * @param data
87
+ * @param indices
88
+ * @param areas
89
+ * @param options
90
+ */
91
+ function flattenPolygon(
92
+ coordinates: PolygonCoordinates,
93
+ data: number[],
94
+ indices: number[][],
95
+ areas: number[][],
96
+ options: GeojsonToFlatGeojsonOptions
97
+ ) {
98
+ let count = 0;
99
+ const ringAreas: number[] = [];
100
+ const polygons: number[] = [];
101
+ for (const lineString of coordinates) {
102
+ const lineString2d = lineString.map((p) => p.slice(0, 2));
103
+ let area = getPolygonSignedArea(lineString2d.flat());
104
+ const ccw = area < 0;
105
+
106
+ // Exterior ring must be CCW and interior rings CW
107
+ if (options.fixRingWinding && ((count === 0 && !ccw) || (count > 0 && ccw))) {
108
+ lineString.reverse();
109
+ area = -area;
110
+ }
111
+ ringAreas.push(area);
112
+ flattenLineString(lineString, data, polygons, options);
113
+ count++;
114
+ }
115
+
116
+ if (count > 0) {
117
+ areas.push(ringAreas);
118
+ indices.push(polygons);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Flatten single GeoJSON feature into Flat GeoJSON
124
+ *
125
+ * @param feature
126
+ * @param options
127
+ * @returns A Flat GeoJSON feature
128
+ */
129
+ function flattenFeature(feature: Feature, options: GeojsonToFlatGeojsonOptions): FlatFeature {
130
+ const {geometry} = feature;
131
+ if (geometry.type === 'GeometryCollection') {
132
+ throw new Error('GeometryCollection type not supported');
133
+ }
134
+ const data = [];
135
+ const indices = [];
136
+ let areas;
137
+ let type;
138
+
139
+ switch (geometry.type) {
140
+ case 'Point':
141
+ type = 'Point';
142
+ flattenPoint(geometry.coordinates, data, indices, options);
143
+ break;
144
+ case 'MultiPoint':
145
+ type = 'Point';
146
+ geometry.coordinates.map((c) => flattenPoint(c, data, indices, options));
147
+ break;
148
+ case 'LineString':
149
+ type = 'LineString';
150
+ flattenLineString(geometry.coordinates, data, indices, options);
151
+ break;
152
+ case 'MultiLineString':
153
+ type = 'LineString';
154
+ geometry.coordinates.map((c) => flattenLineString(c, data, indices, options));
155
+ break;
156
+ case 'Polygon':
157
+ type = 'Polygon';
158
+ areas = [];
159
+ flattenPolygon(geometry.coordinates, data, indices, areas, options);
160
+ break;
161
+ case 'MultiPolygon':
162
+ type = 'Polygon';
163
+ areas = [];
164
+ geometry.coordinates.map((c) => flattenPolygon(c, data, indices, areas, options));
165
+ break;
166
+ default:
167
+ throw new Error(`Unknown type: ${type}`);
168
+ }
169
+
170
+ return {...feature, geometry: {type, indices, data, areas}};
171
+ }