@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
@@ -0,0 +1,564 @@
1
+ /* eslint-disable indent */
2
+ import {earcut} from '@math.gl/polygon';
3
+ import type {
4
+ BinaryAttribute,
5
+ BinaryFeatures,
6
+ FlatFeature,
7
+ FlatPoint,
8
+ FlatLineString,
9
+ FlatPolygon,
10
+ GeojsonGeometryInfo,
11
+ TypedArray
12
+ } from '@loaders.gl/schema';
13
+ import {PropArrayConstructor, Lines, Points, Polygons} from './flat-geojson-to-binary-types';
14
+
15
+ /**
16
+ * Convert binary features to flat binary arrays. Similar to
17
+ * `geojsonToBinary` helper function, except that it expects
18
+ * a binary representation of the feature data, which enables
19
+ * 2X-3X speed increase in parse speed, compared to using
20
+ * geoJSON. See `binary-vector-tile/VectorTileFeature` for
21
+ * data format detais
22
+ *
23
+ * @param features
24
+ * @param geometryInfo
25
+ * @param options
26
+ * @returns filled arrays
27
+ */
28
+ export function flatGeojsonToBinary(
29
+ features: FlatFeature[],
30
+ geometryInfo: GeojsonGeometryInfo,
31
+ options?: FlatGeojsonToBinaryOptions
32
+ ) {
33
+ const propArrayTypes = extractNumericPropTypes(features);
34
+ const numericPropKeys = Object.keys(propArrayTypes).filter((k) => propArrayTypes[k] !== Array);
35
+ return fillArrays(
36
+ features,
37
+ {
38
+ propArrayTypes,
39
+ ...geometryInfo
40
+ },
41
+ {
42
+ numericPropKeys: (options && options.numericPropKeys) || numericPropKeys,
43
+ PositionDataType: options ? options.PositionDataType : Float32Array
44
+ }
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Options for `flatGeojsonToBinary`
50
+ */
51
+ export type FlatGeojsonToBinaryOptions = {
52
+ numericPropKeys?: string[];
53
+ PositionDataType?: Float32ArrayConstructor | Float64ArrayConstructor;
54
+ };
55
+
56
+ export const TEST_EXPORTS = {
57
+ extractNumericPropTypes
58
+ };
59
+
60
+ /**
61
+ * Extracts properties that are always numeric
62
+ *
63
+ * @param features
64
+ * @returns object with numeric types
65
+ */
66
+ function extractNumericPropTypes(features: FlatFeature[]): {
67
+ [key: string]: PropArrayConstructor;
68
+ } {
69
+ const propArrayTypes = {};
70
+ for (const feature of features) {
71
+ if (feature.properties) {
72
+ for (const key in feature.properties) {
73
+ // If property has not been seen before, or if property has been numeric
74
+ // in all previous features, check if numeric in this feature
75
+ // If not numeric, Array is stored to prevent rechecking in the future
76
+ // Additionally, detects if 64 bit precision is required
77
+ const val = feature.properties[key];
78
+ propArrayTypes[key] = deduceArrayType(val, propArrayTypes[key]);
79
+ }
80
+ }
81
+ }
82
+
83
+ return propArrayTypes;
84
+ }
85
+
86
+ /**
87
+ * Fills coordinates into pre-allocated typed arrays
88
+ *
89
+ * @param features
90
+ * @param geometryInfo
91
+ * @param options
92
+ * @returns an accessor object with value and size keys
93
+ */
94
+ // eslint-disable-next-line complexity
95
+ function fillArrays(
96
+ features: FlatFeature[],
97
+ geometryInfo: GeojsonGeometryInfo & {
98
+ propArrayTypes: {[key: string]: PropArrayConstructor};
99
+ },
100
+ options: FlatGeojsonToBinaryOptions
101
+ ) {
102
+ const {
103
+ pointPositionsCount,
104
+ pointFeaturesCount,
105
+ linePositionsCount,
106
+ linePathsCount,
107
+ lineFeaturesCount,
108
+ polygonPositionsCount,
109
+ polygonObjectsCount,
110
+ polygonRingsCount,
111
+ polygonFeaturesCount,
112
+ propArrayTypes,
113
+ coordLength
114
+ } = geometryInfo;
115
+ const {numericPropKeys = [], PositionDataType = Float32Array} = options;
116
+ const hasGlobalId = features[0] && 'id' in features[0];
117
+ const GlobalFeatureIdsDataType = features.length > 65535 ? Uint32Array : Uint16Array;
118
+ const points: Points = {
119
+ type: 'Point',
120
+ positions: new PositionDataType(pointPositionsCount * coordLength),
121
+ globalFeatureIds: new GlobalFeatureIdsDataType(pointPositionsCount),
122
+ featureIds:
123
+ pointFeaturesCount > 65535
124
+ ? new Uint32Array(pointPositionsCount)
125
+ : new Uint16Array(pointPositionsCount),
126
+ numericProps: {},
127
+ properties: [],
128
+ fields: []
129
+ };
130
+ const lines: Lines = {
131
+ type: 'LineString',
132
+ pathIndices:
133
+ linePositionsCount > 65535
134
+ ? new Uint32Array(linePathsCount + 1)
135
+ : new Uint16Array(linePathsCount + 1),
136
+ positions: new PositionDataType(linePositionsCount * coordLength),
137
+ globalFeatureIds: new GlobalFeatureIdsDataType(linePositionsCount),
138
+ featureIds:
139
+ lineFeaturesCount > 65535
140
+ ? new Uint32Array(linePositionsCount)
141
+ : new Uint16Array(linePositionsCount),
142
+ numericProps: {},
143
+ properties: [],
144
+ fields: []
145
+ };
146
+ const polygons: Polygons = {
147
+ type: 'Polygon',
148
+ polygonIndices:
149
+ polygonPositionsCount > 65535
150
+ ? new Uint32Array(polygonObjectsCount + 1)
151
+ : new Uint16Array(polygonObjectsCount + 1),
152
+ primitivePolygonIndices:
153
+ polygonPositionsCount > 65535
154
+ ? new Uint32Array(polygonRingsCount + 1)
155
+ : new Uint16Array(polygonRingsCount + 1),
156
+ positions: new PositionDataType(polygonPositionsCount * coordLength),
157
+ triangles: [],
158
+ globalFeatureIds: new GlobalFeatureIdsDataType(polygonPositionsCount),
159
+ featureIds:
160
+ polygonFeaturesCount > 65535
161
+ ? new Uint32Array(polygonPositionsCount)
162
+ : new Uint16Array(polygonPositionsCount),
163
+ numericProps: {},
164
+ properties: [],
165
+ fields: []
166
+ };
167
+
168
+ // Instantiate numeric properties arrays; one value per vertex
169
+ for (const object of [points, lines, polygons]) {
170
+ for (const propName of numericPropKeys) {
171
+ // If property has been numeric in all previous features in which the property existed, check
172
+ // if numeric in this feature
173
+ const T = propArrayTypes[propName];
174
+ object.numericProps[propName] = new T(object.positions.length / coordLength) as TypedArray;
175
+ }
176
+ }
177
+
178
+ // Set last element of path/polygon indices as positions length
179
+ lines.pathIndices[linePathsCount] = linePositionsCount;
180
+ polygons.polygonIndices[polygonObjectsCount] = polygonPositionsCount;
181
+ polygons.primitivePolygonIndices[polygonRingsCount] = polygonPositionsCount;
182
+
183
+ const indexMap = {
184
+ pointPosition: 0,
185
+ pointFeature: 0,
186
+ linePosition: 0,
187
+ linePath: 0,
188
+ lineFeature: 0,
189
+ polygonPosition: 0,
190
+ polygonObject: 0,
191
+ polygonRing: 0,
192
+ polygonFeature: 0,
193
+ feature: 0
194
+ };
195
+
196
+ for (const feature of features) {
197
+ const geometry = feature.geometry;
198
+ const properties = feature.properties || {};
199
+
200
+ switch (geometry.type) {
201
+ case 'Point':
202
+ handlePoint(geometry, points, indexMap, coordLength, properties);
203
+ points.properties.push(keepStringProperties(properties, numericPropKeys));
204
+ if (hasGlobalId) {
205
+ points.fields.push({id: feature.id});
206
+ }
207
+ indexMap.pointFeature++;
208
+ break;
209
+ case 'LineString':
210
+ handleLineString(geometry, lines, indexMap, coordLength, properties);
211
+ lines.properties.push(keepStringProperties(properties, numericPropKeys));
212
+ if (hasGlobalId) {
213
+ lines.fields.push({id: feature.id});
214
+ }
215
+ indexMap.lineFeature++;
216
+ break;
217
+ case 'Polygon':
218
+ handlePolygon(geometry, polygons, indexMap, coordLength, properties);
219
+ polygons.properties.push(keepStringProperties(properties, numericPropKeys));
220
+ if (hasGlobalId) {
221
+ polygons.fields.push({id: feature.id});
222
+ }
223
+ indexMap.polygonFeature++;
224
+ break;
225
+ default:
226
+ throw new Error('Invalid geometry type');
227
+ }
228
+
229
+ indexMap.feature++;
230
+ }
231
+
232
+ // Wrap each array in an accessor object with value and size keys
233
+ return makeAccessorObjects(points, lines, polygons, coordLength);
234
+ }
235
+
236
+ /**
237
+ * Fills (Multi)Point coordinates into points object of arrays
238
+ *
239
+ * @param geometry
240
+ * @param points
241
+ * @param indexMap
242
+ * @param coordLength
243
+ * @param properties
244
+ */
245
+ function handlePoint(
246
+ geometry: FlatPoint,
247
+ points: Points,
248
+ indexMap: {
249
+ pointPosition: number;
250
+ pointFeature: number;
251
+ linePosition?: number;
252
+ linePath?: number;
253
+ lineFeature?: number;
254
+ polygonPosition?: number;
255
+ polygonObject?: number;
256
+ polygonRing?: number;
257
+ polygonFeature?: number;
258
+ feature: number;
259
+ },
260
+ coordLength: number,
261
+ properties: {[x: string]: string | number | boolean | null}
262
+ ): void {
263
+ points.positions.set(geometry.data, indexMap.pointPosition * coordLength);
264
+
265
+ const nPositions = geometry.data.length / coordLength;
266
+ fillNumericProperties(points, properties, indexMap.pointPosition, nPositions);
267
+ points.globalFeatureIds.fill(
268
+ indexMap.feature,
269
+ indexMap.pointPosition,
270
+ indexMap.pointPosition + nPositions
271
+ );
272
+ points.featureIds.fill(
273
+ indexMap.pointFeature,
274
+ indexMap.pointPosition,
275
+ indexMap.pointPosition + nPositions
276
+ );
277
+
278
+ indexMap.pointPosition += nPositions;
279
+ }
280
+
281
+ /**
282
+ * Fills (Multi)LineString coordinates into lines object of arrays
283
+ *
284
+ * @param geometry
285
+ * @param lines
286
+ * @param indexMap
287
+ * @param coordLength
288
+ * @param properties
289
+ */
290
+ function handleLineString(
291
+ geometry: FlatLineString,
292
+ lines: Lines,
293
+ indexMap: {
294
+ pointPosition?: number;
295
+ pointFeature?: number;
296
+ linePosition: number;
297
+ linePath: number;
298
+ lineFeature: number;
299
+ polygonPosition?: number;
300
+ polygonObject?: number;
301
+ polygonRing?: number;
302
+ polygonFeature?: number;
303
+ feature: number;
304
+ },
305
+ coordLength: number,
306
+ properties: {[x: string]: string | number | boolean | null}
307
+ ): void {
308
+ lines.positions.set(geometry.data, indexMap.linePosition * coordLength);
309
+
310
+ const nPositions = geometry.data.length / coordLength;
311
+ fillNumericProperties(lines, properties, indexMap.linePosition, nPositions);
312
+
313
+ lines.globalFeatureIds.fill(
314
+ indexMap.feature,
315
+ indexMap.linePosition,
316
+ indexMap.linePosition + nPositions
317
+ );
318
+ lines.featureIds.fill(
319
+ indexMap.lineFeature,
320
+ indexMap.linePosition,
321
+ indexMap.linePosition + nPositions
322
+ );
323
+
324
+ for (let i = 0, il = geometry.indices.length; i < il; ++i) {
325
+ // Extract range of data we are working with, defined by start
326
+ // and end indices (these index into the geometry.data array)
327
+ const start = geometry.indices[i];
328
+ const end =
329
+ i === il - 1
330
+ ? geometry.data.length // last line, so read to end of data
331
+ : geometry.indices[i + 1]; // start index for next line
332
+
333
+ lines.pathIndices[indexMap.linePath++] = indexMap.linePosition;
334
+ indexMap.linePosition += (end - start) / coordLength;
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Fills (Multi)Polygon coordinates into polygons object of arrays
340
+ *
341
+ * @param geometry
342
+ * @param polygons
343
+ * @param indexMap
344
+ * @param coordLength
345
+ * @param properties
346
+ */
347
+ function handlePolygon(
348
+ geometry: FlatPolygon,
349
+ polygons: Polygons,
350
+ indexMap: {
351
+ pointPosition?: number;
352
+ pointFeature?: number;
353
+ linePosition?: number;
354
+ linePath?: number;
355
+ lineFeature?: number;
356
+ polygonPosition: number;
357
+ polygonObject: number;
358
+ polygonRing: number;
359
+ polygonFeature: number;
360
+ feature: number;
361
+ },
362
+ coordLength: number,
363
+ properties: {[x: string]: string | number | boolean | null}
364
+ ): void {
365
+ polygons.positions.set(geometry.data, indexMap.polygonPosition * coordLength);
366
+
367
+ const nPositions = geometry.data.length / coordLength;
368
+ fillNumericProperties(polygons, properties, indexMap.polygonPosition, nPositions);
369
+ polygons.globalFeatureIds.fill(
370
+ indexMap.feature,
371
+ indexMap.polygonPosition,
372
+ indexMap.polygonPosition + nPositions
373
+ );
374
+ polygons.featureIds.fill(
375
+ indexMap.polygonFeature,
376
+ indexMap.polygonPosition,
377
+ indexMap.polygonPosition + nPositions
378
+ );
379
+
380
+ // Unlike Point & LineString geometry.indices is a 2D array
381
+ for (let l = 0, ll = geometry.indices.length; l < ll; ++l) {
382
+ const startPosition = indexMap.polygonPosition;
383
+ polygons.polygonIndices[indexMap.polygonObject++] = startPosition;
384
+
385
+ const areas = geometry.areas[l];
386
+ const indices = geometry.indices[l];
387
+ const nextIndices = geometry.indices[l + 1];
388
+
389
+ for (let i = 0, il = indices.length; i < il; ++i) {
390
+ const start = indices[i];
391
+ const end =
392
+ i === il - 1
393
+ ? // last line, so either read to:
394
+ nextIndices === undefined
395
+ ? geometry.data.length // end of data (no next indices)
396
+ : nextIndices[0] // start of first line in nextIndices
397
+ : indices[i + 1]; // start index for next line
398
+
399
+ polygons.primitivePolygonIndices[indexMap.polygonRing++] = indexMap.polygonPosition;
400
+ indexMap.polygonPosition += (end - start) / coordLength;
401
+ }
402
+
403
+ const endPosition = indexMap.polygonPosition;
404
+ triangulatePolygon(polygons, areas, indices, {startPosition, endPosition, coordLength});
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Triangulate polygon using earcut
410
+ *
411
+ * @param polygons
412
+ * @param areas
413
+ * @param indices
414
+ * @param param3
415
+ */
416
+ function triangulatePolygon(
417
+ polygons: Polygons,
418
+ areas: number[],
419
+ indices: number[],
420
+ {
421
+ startPosition,
422
+ endPosition,
423
+ coordLength
424
+ }: {startPosition: number; endPosition: number; coordLength: number}
425
+ ): void {
426
+ const start = startPosition * coordLength;
427
+ const end = endPosition * coordLength;
428
+
429
+ // Extract positions and holes for just this polygon
430
+ const polygonPositions = polygons.positions.subarray(start, end);
431
+
432
+ // Holes are referenced relative to outer polygon
433
+ const offset = indices[0];
434
+ const holes = indices.slice(1).map((n: number) => (n - offset) / coordLength);
435
+
436
+ // Compute triangulation
437
+ const triangles = earcut(polygonPositions, holes, coordLength, areas);
438
+
439
+ // Indices returned by triangulation are relative to start
440
+ // of polygon, so we need to offset
441
+ for (let t = 0, tl = triangles.length; t < tl; ++t) {
442
+ polygons.triangles.push(startPosition + triangles[t]);
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Wraps an object containing array into accessors
448
+ *
449
+ * @param obj
450
+ * @param size
451
+ */
452
+ function wrapProps(
453
+ obj: {[key: string]: TypedArray},
454
+ size: number
455
+ ): {[key: string]: BinaryAttribute} {
456
+ const returnObj = {};
457
+ for (const key in obj) {
458
+ returnObj[key] = {value: obj[key], size};
459
+ }
460
+ return returnObj;
461
+ }
462
+
463
+ /**
464
+ * Wrap each array in an accessor object with value and size keys
465
+ *
466
+ * @param points
467
+ * @param lines
468
+ * @param polygons
469
+ * @param coordLength
470
+ * @returns object
471
+ */
472
+ function makeAccessorObjects(
473
+ points: Points,
474
+ lines: Lines,
475
+ polygons: Polygons,
476
+ coordLength: number
477
+ ): BinaryFeatures {
478
+ return {
479
+ points: {
480
+ ...points,
481
+ positions: {value: points.positions, size: coordLength},
482
+ globalFeatureIds: {value: points.globalFeatureIds, size: 1},
483
+ featureIds: {value: points.featureIds, size: 1},
484
+ numericProps: wrapProps(points.numericProps, 1)
485
+ },
486
+ lines: {
487
+ ...lines,
488
+ positions: {value: lines.positions, size: coordLength},
489
+ pathIndices: {value: lines.pathIndices, size: 1},
490
+ globalFeatureIds: {value: lines.globalFeatureIds, size: 1},
491
+ featureIds: {value: lines.featureIds, size: 1},
492
+ numericProps: wrapProps(lines.numericProps, 1)
493
+ },
494
+ polygons: {
495
+ ...polygons,
496
+ positions: {value: polygons.positions, size: coordLength},
497
+ polygonIndices: {value: polygons.polygonIndices, size: 1},
498
+ primitivePolygonIndices: {value: polygons.primitivePolygonIndices, size: 1},
499
+ triangles: {value: new Uint32Array(polygons.triangles), size: 1},
500
+ globalFeatureIds: {value: polygons.globalFeatureIds, size: 1},
501
+ featureIds: {value: polygons.featureIds, size: 1},
502
+ numericProps: wrapProps(polygons.numericProps, 1)
503
+ }
504
+ };
505
+ }
506
+
507
+ /**
508
+ * Add numeric properties to object
509
+ *
510
+ * @param object
511
+ * @param properties
512
+ * @param index
513
+ * @param length
514
+ */
515
+ function fillNumericProperties(
516
+ object: Points | Lines | Polygons,
517
+ properties: {[x: string]: string | number | boolean | null},
518
+ index: number,
519
+ length: number
520
+ ): void {
521
+ for (const numericPropName in object.numericProps) {
522
+ if (numericPropName in properties) {
523
+ const value = properties[numericPropName] as number;
524
+ object.numericProps[numericPropName].fill(value, index, index + length);
525
+ }
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Keep string properties in object
531
+ *
532
+ * @param properties
533
+ * @param numericKeys
534
+ * @returns object
535
+ */
536
+ function keepStringProperties(
537
+ properties: {[x: string]: string | number | boolean | null},
538
+ numericKeys: string[]
539
+ ) {
540
+ const props = {};
541
+ for (const key in properties) {
542
+ if (!numericKeys.includes(key)) {
543
+ props[key] = properties[key];
544
+ }
545
+ }
546
+ return props;
547
+ }
548
+
549
+ /**
550
+ *
551
+ * Deduce correct array constructor to use for a given value
552
+ *
553
+ * @param x value to test
554
+ * @param constructor previous constructor deduced
555
+ * @returns PropArrayConstructor
556
+ */
557
+ function deduceArrayType(x: any, constructor: PropArrayConstructor): PropArrayConstructor {
558
+ if (constructor === Array || !Number.isFinite(x)) {
559
+ return Array;
560
+ }
561
+
562
+ // If this or previous value required 64bits use Float64Array
563
+ return constructor === Float64Array || Math.fround(x) !== x ? Float64Array : Float32Array;
564
+ }