@loaders.gl/gis 4.0.0-alpha.4 → 4.0.0-alpha.5

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