• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Open-S2 / open-vector-tile / #59

02 Apr 2025 03:18PM UTC coverage: 98.957% (+0.1%) from 98.854%
#59

push

Mr Martian
fix naming conventions for tessellation; fix fields starting with 0; add support to convert s2json::VectorFeature to BaseVectorFeature

1071 of 1073 new or added lines in 22 files covered. (99.81%)

1 existing line in 1 file now uncovered.

9389 of 9488 relevant lines covered (98.96%)

66.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.6
/src/open/vectorFeature.ts
1
import { OColumnName } from './columnCache';
176✔
2
import { decodeOffset } from '../base';
156✔
3
import { PbfReader, Pbf as Protobuf } from 'pbf-ts';
208✔
4
import { decodeValue, encodeValue } from './shape';
204✔
5
import { unweave2D, unweave3D, zagzig } from '../util';
220✔
6

7
import type { BaseVectorFeature } from '../base';
8
import type { Extents } from './vectorLayer';
9
import type { Shape } from './shape';
10
import type { BBox, BBox3D, Properties as OProperties } from 's2json-spec';
11
import type { ColumnCacheReader, ColumnCacheWriter } from './columnCache';
12
import type {
13
  Point,
14
  Point3D,
15
  VectorFeatureType,
16
  VectorLine,
17
  VectorLine3D,
18
  VectorLines,
19
  VectorLines3D,
20
  VectorLines3DWithOffset,
21
  VectorLinesWithOffset,
22
  VectorMultiPoly,
23
  VectorMultiPoly3D,
24
  VectorPoints,
25
  VectorPoints3D,
26
} from '../vectorTile.spec';
27

28
/**
29
 * Vector Feature Base
30
 * Common variables and functions shared by all vector features
31
 */
32
export class OVectorFeatureBase {
×
33
  type = 0;
×
34
  /**
×
35
   * @param cache - the column cache for future retrieval
×
36
   * @param id - the id of the feature
×
37
   * @param properties - the properties of the feature
×
38
   * @param mShape - the shape of the feature's mValues if they exist
×
39
   * @param extent - the extent of the feature
×
40
   * @param geometryIndices - the indices of the geometry in the cache
×
41
   * @param single - if true, you know the initial length is 1
×
42
   * @param bboxIndex - index to the values column where the BBox is stored
×
43
   * @param hasOffsets - if true, the geometryIndices has offsets encoded into it
×
44
   * @param hasMValues - if true, the feature has M values
×
45
   * @param indicesIndex - if greater than 0, the feature has indices to parse
×
NEW
46
   * @param tessellationIndex - if greater than 0, the feature has tessellation
×
47
   */
×
48
  constructor(
×
49
    readonly cache: ColumnCacheReader,
146✔
50
    readonly id: number | undefined,
104✔
51
    readonly properties: OProperties,
232✔
52
    readonly mShape: Shape,
168✔
53
    readonly extent: Extents,
168✔
54
    readonly geometryIndices: number[],
312✔
55
    readonly single: boolean,
168✔
56
    readonly bboxIndex: number, // -1 if there is no bbox
216✔
57
    readonly hasOffsets: boolean,
232✔
58
    readonly hasMValues: boolean,
232✔
59
    readonly indicesIndex: number, // -1 if there are no indices
264✔
60
    readonly tessellationIndex: number, // -1 if there is no tessellation
344✔
61
  ) {}
20✔
62

63
  /** @returns - true if the type of the feature is points */
64
  isPoints(): boolean {
48✔
65
    return this.type === 1;
102✔
66
  }
67

68
  /** @returns - true if the type of the feature is lines */
69
  isLines(): boolean {
46✔
70
    return this.type === 2;
102✔
71
  }
72

73
  /** @returns - true if the type of the feature is polygons */
74
  isPolygons(): boolean {
52✔
75
    return this.type === 3;
102✔
76
  }
77

78
  /** @returns - true if the type of the feature is points 3D */
79
  isPoints3D(): boolean {
52✔
80
    return this.type === 4;
102✔
81
  }
82

83
  /** @returns - true if the type of the feature is lines 3D */
84
  isLines3D(): boolean {
50✔
85
    return this.type === 5;
102✔
86
  }
87

88
  /** @returns - true if the type of the feature is polygons 3D */
89
  isPolygons3D(): boolean {
56✔
90
    return this.type === 6;
102✔
91
  }
92

93
  /**
94
   * adds the tessellation to the geometry
95
   * @param geometry - the input geometry to add to
96
   * @param multiplier - the multiplier to multiply the geometry by
97
   */
98
  // we need to disable the eslint rule here so that the docs register the parameters correctly
99
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
100
  addTessellation(geometry: number[], multiplier: number): void {}
134✔
101

102
  /**
103
   * @returns an empty geometry
104
   */
105
  loadGeometryFlat(): [geometry: number[], indices: number[]] {
64✔
106
    return [[], []];
74✔
107
  }
108

109
  /**
110
   * @returns the indices for the feature
111
   */
112
  readIndices(): number[] {
54✔
113
    return [];
48✔
114
  }
115
}
4✔
116

117
/**
118
 * Vector Feature Base 2D.
119
 * Extends from @see {@link OVectorFeatureBase}.
120
 */
121
export class OVectorFeatureBase2D extends OVectorFeatureBase {
250✔
122
  /** @returns the BBox of the feature (in lon-lat space) */
123
  bbox(): BBox {
40✔
124
    if (this.bboxIndex === -1) return [0, 0, 0, 0];
220✔
125
    return this.cache.getColumn<BBox>(OColumnName.bbox, this.bboxIndex);
256✔
126
  }
127
}
4✔
128

129
/**
130
 * Vector Feature Base 3D.
131
 * Extends from @see {@link OVectorFeatureBase}.
132
 */
133
export class OVectorFeatureBase3D extends OVectorFeatureBase {
250✔
134
  /** @returns the BBox3D of the feature (in lon-lat space) */
135
  bbox(): BBox3D {
40✔
136
    if (this.bboxIndex === -1) return [0, 0, 0, 0, 0, 0];
244✔
137
    return this.cache.getColumn<BBox3D>(OColumnName.bbox, this.bboxIndex);
256✔
138
  }
139
}
4✔
140

141
/**
142
 * Points Vector Feature
143
 * Type 1
144
 * Extends from @see {@link OVectorFeatureBase}.
145
 * store either a single point or a list of points
146
 */
147
export class OVectorPointsFeature extends OVectorFeatureBase2D {
260✔
148
  type: VectorFeatureType = 1;
44✔
149
  geometry?: VectorPoints;
42✔
150

151
  /** @returns the geometry as an array of points */
152
  loadPoints(): Point[] {
52✔
153
    return this.loadGeometry();
118✔
154
  }
155

156
  /** @returns the geometry as an array of lines */
157
  loadLines(): VectorLinesWithOffset {
50✔
158
    return [];
50✔
159
  }
160

161
  /** @returns the geometry as an array of points */
162
  loadGeometry(): VectorPoints {
56✔
163
    const { cache, hasMValues, single, geometryIndices: indices } = this;
292✔
164
    let indexPos = 0;
84✔
165
    const geometryIndex = indices[indexPos++];
184✔
166
    if (this.geometry === undefined) {
158✔
167
      if (single) {
82✔
168
        const { a, b } = unweave2D(geometryIndex);
200✔
169
        this.geometry = [{ x: zagzig(a), y: zagzig(b) }];
220✔
170
      } else {
62✔
171
        this.geometry = cache.getColumn<VectorPoints>(OColumnName.points, geometryIndex);
300✔
172
        // load m values if they exist
173
        if (hasMValues) {
106✔
174
          const length = this.geometry.length;
184✔
175
          for (let j = 0; j < length; j++) {
178✔
176
            const valueIndex = indices[indexPos++];
204✔
177
            this.geometry[j].m = decodeValue(valueIndex, this.mShape, cache);
300✔
178
          }
34✔
179
        }
44✔
180
      }
181
    }
18✔
182

183
    return this.geometry;
92✔
184
  }
185
}
4✔
186

187
/**
188
 * Lines Vector Feature
189
 * Type 2
190
 * Extends from @see {@link OVectorFeatureBase2D}.
191
 * Store either a single line or a list of lines
192
 */
193
export class OVectorLinesFeature extends OVectorFeatureBase2D {
256✔
194
  type: VectorFeatureType = 2;
44✔
195
  geometry?: VectorLinesWithOffset;
42✔
196

197
  /** @returns the geometry as a flattened array of points */
198
  loadPoints(): Point[] {
52✔
199
    return this.loadGeometry().flatMap((line) => line);
210✔
200
  }
201

202
  /** @returns the geometry as an array of lines objects that include offsets */
203
  loadLines(): VectorLinesWithOffset {
50✔
204
    if (this.geometry !== undefined) return this.geometry;
248✔
205
    // prepare variables
206
    const { hasOffsets, hasMValues, geometryIndices: indices, cache, single } = this;
340✔
207
    const lines: VectorLinesWithOffset = [];
84✔
208
    let indexPos = 0;
84✔
209
    const lineCount = single ? 1 : indices[indexPos++];
214✔
210
    for (let i = 0; i < lineCount; i++) {
166✔
211
      // get offset if it exists
212
      const offset = hasOffsets ? decodeOffset(indices[indexPos++]) : 0;
282✔
213
      // get geometry
214
      const geometry: VectorLine = cache.getColumn(OColumnName.points, indices[indexPos++]);
320✔
215
      // inject m values if they exist
216
      if (hasMValues) {
98✔
217
        const length = geometry.length;
156✔
218
        for (let j = 0; j < length; j++) {
170✔
219
          const valueIndex = indices[indexPos++];
196✔
220
          geometry[j].m = decodeValue(valueIndex, this.mShape, cache);
272✔
221
        }
26✔
222
      }
26✔
223
      lines.push({ offset, geometry });
148✔
224
    }
18✔
225

226
    this.geometry = lines;
104✔
227
    return lines;
62✔
228
  }
229

230
  /** @returns the geometry as an array of flattened line geometry */
231
  loadGeometry(): VectorLines {
56✔
232
    return this.loadLines().map((line) => line.geometry);
216✔
233
  }
234
}
4✔
235

236
/**
237
 * Polys Vector Feature
238
 * Type 3
239
 * Extends from @see {@link OVectorFeatureBase2D}.
240
 * Stores either one or multiple polygons. Polygons are an abstraction to polylines, and
241
 * each polyline can contain an offset.
242
 */
243
export class OVectorPolysFeature extends OVectorFeatureBase2D {
256✔
244
  type: VectorFeatureType = 3;
44✔
245
  geometry?: VectorLinesWithOffset[];
42✔
246

247
  /**
248
   * Stores the geometry incase it's used again
249
   * @returns the geometry as an array of lines objects that include offsets
250
   */
251
  #loadLinesWithOffsets(): VectorLinesWithOffset[] {
74✔
252
    if (this.geometry !== undefined) return this.geometry;
248✔
253

254
    // prepare variables
255
    const { hasOffsets, hasMValues, geometryIndices: indices, cache, single } = this;
340✔
256
    const polys: VectorLinesWithOffset[] = [];
84✔
257
    let indexPos = 0;
84✔
258
    const polyCount = single ? 1 : indices[indexPos++];
214✔
259
    for (let i = 0; i < polyCount; i++) {
166✔
260
      const lineCount = indices[indexPos++];
176✔
261
      const lines: VectorLinesWithOffset = [];
92✔
262
      for (let j = 0; j < lineCount; j++) {
174✔
263
        // get offset if it exists
264
        const offset = hasOffsets ? decodeOffset(indices[indexPos++]) : 0;
290✔
265
        // get geometry
266
        const geometry: VectorLine = cache.getColumn(OColumnName.points, indices[indexPos++]);
328✔
267
        // inject m values if they exist
268
        if (hasMValues) {
106✔
269
          const length = geometry.length;
164✔
270
          for (let j = 0; j < length; j++) {
178✔
271
            const valueIndex = indices[indexPos++];
204✔
272
            geometry[j].m = decodeValue(valueIndex, this.mShape, cache);
280✔
273
          }
34✔
274
        }
34✔
275
        lines.push({ offset, geometry });
156✔
276
      }
26✔
277
      polys.push(lines);
88✔
278
    }
18✔
279

280
    this.geometry = polys;
104✔
281
    return polys;
62✔
282
  }
283

284
  /** @returns the geometry as a flattened array of points */
285
  loadPoints(): Point[] {
52✔
286
    return this.loadGeometry().flatMap((poly) => {
206✔
287
      return poly.flatMap((line) => line);
148✔
288
    });
18✔
289
  }
290

291
  /** @returns the geometry flattened into an array with offsets */
292
  loadLines(): VectorLinesWithOffset {
50✔
293
    const lines = this.#loadLinesWithOffsets();
188✔
294
    // flatten
295
    return lines.flatMap((line) => line);
154✔
296
  }
297

298
  /**
299
   * @returns the geometry as an array of raw poly geometry
300
   */
301
  loadGeometry(): VectorMultiPoly {
56✔
302
    return this.#loadLinesWithOffsets().map((poly) => {
226✔
303
      return poly.map((line) => line.geometry);
168✔
304
    });
18✔
305
  }
306

307
  /**
308
   * Automatically adds the tessellation to the geometry if the tessellationIndex exists
309
   * @returns the geometry as an array of totally flattend poly geometry with indices
310
   */
311
  loadGeometryFlat(): [geometry: number[], indices: number[]] {
64✔
312
    const geo = this.#loadLinesWithOffsets();
180✔
313
    const multiplier = 1 / this.extent;
156✔
314
    const geometry: number[] = [];
96✔
315

316
    for (const poly of geo) {
122✔
317
      for (const line of poly) {
134✔
318
        for (const point of line.geometry) {
182✔
319
          geometry.push(point.x * multiplier, point.y * multiplier);
264✔
320
        }
26✔
321
      }
18✔
322
    }
18✔
323

324
    this.addTessellation(geometry, multiplier);
188✔
325

326
    return [geometry, this.readIndices()];
162✔
327
  }
328

329
  /** @returns the indices of the geometry */
330
  readIndices(): number[] {
54✔
331
    if (this.indicesIndex === -1) return [];
174✔
332
    return this.cache.getColumn<number[]>(OColumnName.indices, this.indicesIndex);
282✔
333
  }
334

335
  /**
336
   * adds the tessellation to the geometry
337
   * @param geometry - the geometry of the feature
338
   * @param multiplier - the multiplier to apply the extent shift
339
   */
340
  addTessellation(geometry: number[], multiplier: number): void {
142✔
341
    if (this.tessellationIndex === -1) return;
188✔
342
    const data = this.cache.getColumn<Point[]>(OColumnName.points, this.tessellationIndex);
328✔
343
    for (const point of data) {
130✔
344
      geometry.push(point.x * multiplier, point.y * multiplier);
248✔
345
    }
16✔
346
  }
347
}
4✔
348

349
/**
350
 * 3D Point Vector Feature
351
 * Type 4.
352
 * Extends from @see {@link OVectorFeatureBase3D}.
353
 * Store either a single 3D point or a list of 3D points.
354
 */
355
export class OVectorPoints3DFeature extends OVectorFeatureBase3D {
268✔
356
  type: VectorFeatureType = 4;
44✔
357
  geometry?: VectorPoints3D;
42✔
358

359
  /** @returns the geometry as a flattened array of points */
360
  loadPoints(): Point3D[] {
52✔
361
    return this.loadGeometry();
118✔
362
  }
363

364
  /** @returns the geometry as an array of lines */
365
  loadLines(): VectorLines3DWithOffset {
50✔
366
    return [];
50✔
367
  }
368

369
  /**
370
   * Read in the 3D Point Geometry. Can be more than one point.
371
   * @returns the 3D Point Geometry
372
   */
373
  loadGeometry(): VectorPoints3D {
56✔
374
    const { cache, hasMValues, single, geometryIndices: indices } = this;
292✔
375
    let indexPos = 0;
84✔
376
    const geometryIndex = indices[indexPos++];
184✔
377
    if (this.geometry === undefined) {
158✔
378
      if (single) {
82✔
379
        const { a, b, c } = unweave3D(geometryIndex);
212✔
380
        this.geometry = [{ x: zagzig(a), y: zagzig(b), z: zagzig(c) }];
276✔
381
      } else {
62✔
382
        this.geometry = cache.getColumn<VectorPoints3D>(OColumnName.points3D, geometryIndex);
308✔
383
        // load m values if they exist
384
        if (hasMValues) {
106✔
385
          const length = this.geometry.length;
184✔
386
          for (let j = 0; j < length; j++) {
178✔
387
            const valueIndex = indices[indexPos++];
204✔
388
            this.geometry[j].m = decodeValue(valueIndex, this.mShape, cache);
300✔
389
          }
34✔
390
        }
44✔
391
      }
392
    }
18✔
393

394
    return this.geometry;
92✔
395
  }
396
}
4✔
397
/**
398
 * 3D Lines Vector Feature
399
 * Type 5
400
 * Extends from @see {@link OVectorFeatureBase3D}.
401
 * Store either a single 3D line or a list of 3D lines.
402
 */
403
export class OVectorLines3DFeature extends OVectorFeatureBase3D {
264✔
404
  type: VectorFeatureType = 5;
44✔
405
  geometry?: VectorLines3DWithOffset;
42✔
406

407
  /** @returns the geometry as a flattened array of points */
408
  loadPoints(): Point3D[] {
52✔
409
    return this.loadGeometry().flatMap((line) => line);
210✔
410
  }
411

412
  /** @returns the geometry as an array of lines objects that include offsets */
413
  loadLines(): VectorLines3DWithOffset {
50✔
414
    if (this.geometry !== undefined) return this.geometry;
248✔
415
    // prepare variables
416
    const { hasOffsets, hasMValues, geometryIndices: indices, cache, single } = this;
340✔
417
    const lines: VectorLines3DWithOffset = [];
84✔
418
    let indexPos = 0;
84✔
419
    const lineCount = single ? 1 : indices[indexPos++];
214✔
420
    for (let i = 0; i < lineCount; i++) {
166✔
421
      // get offset if it exists
422
      const offset = hasOffsets ? decodeOffset(indices[indexPos++]) : 0;
282✔
423
      // get geometry
424
      const geometry: VectorLine3D = cache.getColumn(OColumnName.points3D, indices[indexPos++]);
328✔
425
      // inject m values if they exist
426
      if (hasMValues) {
98✔
427
        const length = geometry.length;
156✔
428
        for (let j = 0; j < length; j++) {
170✔
429
          const valueIndex = indices[indexPos++];
196✔
430
          geometry[j].m = decodeValue(valueIndex, this.mShape, cache);
272✔
431
        }
26✔
432
      }
26✔
433
      lines.push({ offset, geometry });
148✔
434
    }
18✔
435

436
    this.geometry = lines;
104✔
437
    return lines;
62✔
438
  }
439

440
  /** @returns the geometry as an array of flattened line geometry */
441
  loadGeometry(): VectorLines3D {
56✔
442
    return this.loadLines().map((line) => line.geometry);
216✔
443
  }
444
}
4✔
445
/**
446
 * 3D Polygons Vector Feature
447
 * Type 6
448
 * Extends from @see {@link OVectorFeatureBase3D}.
449
 * Store either a single 3D polygon or a list of 3D polygons.
450
 */
451
export class OVectorPolys3DFeature extends OVectorFeatureBase3D {
264✔
452
  type: VectorFeatureType = 6;
44✔
453
  geometry?: VectorLines3DWithOffset[];
42✔
454

455
  /**
456
   * Stores the geometry incase it's used again
457
   * @returns the geometry as an array of lines objects that include offsets
458
   */
459
  #loadLinesWithOffsets(): VectorLines3DWithOffset[] {
74✔
460
    if (this.geometry !== undefined) return this.geometry;
248✔
461

462
    // prepare variables
463
    const { hasOffsets, hasMValues, geometryIndices: indices, cache, single } = this;
340✔
464
    const polys: VectorLines3DWithOffset[] = [];
84✔
465
    let indexPos = 0;
84✔
466
    const polyCount = single ? 1 : indices[indexPos++];
214✔
467
    for (let i = 0; i < polyCount; i++) {
166✔
468
      const lineCount = indices[indexPos++];
176✔
469
      const lines: VectorLines3DWithOffset = [];
92✔
470
      for (let j = 0; j < lineCount; j++) {
174✔
471
        // get offset if it exists
472
        const offset = hasOffsets ? decodeOffset(indices[indexPos++]) : 0;
290✔
473
        // get geometry
474
        const geometry: VectorLine3D = cache.getColumn(OColumnName.points3D, indices[indexPos++]);
336✔
475
        // inject m values if they exist
476
        if (hasMValues) {
106✔
477
          const length = geometry.length;
164✔
478
          for (let j = 0; j < length; j++) {
178✔
479
            const valueIndex = indices[indexPos++];
204✔
480
            geometry[j].m = decodeValue(valueIndex, this.mShape, cache);
280✔
481
          }
34✔
482
        }
34✔
483
        lines.push({ offset, geometry });
156✔
484
      }
26✔
485
      polys.push(lines);
88✔
486
    }
18✔
487

488
    this.geometry = polys;
104✔
489
    return polys;
62✔
490
  }
491

492
  /** @returns the geometry as a flattened array of points */
493
  loadPoints(): Point3D[] {
52✔
494
    return this.loadGeometry().flatMap((poly) => {
206✔
495
      return poly.flatMap((line) => line);
148✔
496
    });
18✔
497
  }
498

499
  /** @returns the geometry flattened into an array with offsets */
500
  loadLines(): VectorLines3DWithOffset {
50✔
501
    const lines = this.#loadLinesWithOffsets();
188✔
502
    // flatten
503
    return lines.flatMap((line) => line);
154✔
504
  }
505

506
  /** @returns the geometry as an array of raw poly geometry */
507
  loadGeometry(): VectorMultiPoly3D {
56✔
508
    return this.#loadLinesWithOffsets().map((poly) => {
226✔
509
      return poly.map((line) => line.geometry);
168✔
510
    });
18✔
511
  }
512

513
  /**
514
   * Automatically adds the tessellation to the geometry if the tessellationIndex exists
515
   * @returns the geometry as an array of totally flattend poly geometry with indices
516
   */
517
  loadGeometryFlat(): [geometry: number[], indices: number[]] {
64✔
518
    const geo = this.#loadLinesWithOffsets();
180✔
519
    const multiplier = 1 / this.extent;
156✔
520
    const geometry: number[] = [];
96✔
521

522
    for (const poly of geo) {
122✔
523
      for (const line of poly) {
134✔
524
        for (const point of line.geometry) {
182✔
525
          geometry.push(point.x * multiplier, point.y * multiplier);
264✔
526
        }
26✔
527
      }
18✔
528
    }
18✔
529

530
    this.addTessellation(geometry, multiplier);
188✔
531

532
    return [geometry, this.readIndices()];
162✔
533
  }
534

535
  /** @returns the indices of the geometry */
536
  readIndices(): number[] {
54✔
537
    if (this.indicesIndex === -1) return [];
174✔
538
    return this.cache.getColumn<number[]>(OColumnName.indices, this.indicesIndex);
282✔
539
  }
540

541
  /**
542
   * adds the tessellation to the geometry
543
   * @param geometry - the geometry of the feature
544
   * @param multiplier - the multiplier to apply the extent shift
545
   */
546
  addTessellation(geometry: number[], multiplier: number): void {
142✔
547
    if (this.tessellationIndex === -1) return;
188✔
548
    const data = this.cache.getColumn<Point3D[]>(OColumnName.points3D, this.tessellationIndex);
336✔
549
    for (const point of data) {
130✔
550
      geometry.push(point.x * multiplier, point.y * multiplier, point.z * multiplier);
336✔
551
    }
16✔
552
  }
553
}
4✔
554

555
/** All feature class types. Points, Lines, and Polys for both 2D and 3D */
556
export type OVectorFeature =
557
  | OVectorPointsFeature
558
  | OVectorLinesFeature
559
  | OVectorPolysFeature
560
  | OVectorPoints3DFeature
561
  | OVectorLines3DFeature
562
  | OVectorPolys3DFeature;
563

564
/**
565
 * @param T - the feature type
566
 * @param cache - the column cache to read from
567
 * @param id - the id of the feature
568
 * @param properties - the properties of the feature
569
 * @param mShape - the shape of the feature's m-values if they exist
570
 * @param extent - the extent of the vector layer to help decode the geometry
571
 * @param geometryIndices - the indices of the geometry
572
 * @param single - whether the geometry is a single point
573
 * @param bboxIndex - the index of the bbox
574
 * @param hasOffsets - whether the geometry has offsets
575
 * @param hasMValues - whether the geometry has m values
576
 * @returns - the feature type
577
 */
578
type Constructor<T> = new (
579
  cache: ColumnCacheReader,
580
  id: number | undefined,
581
  properties: OProperties,
582
  mShape: Shape,
583
  extent: Extents,
584
  geometryIndices: number[],
585
  single: boolean,
586
  bboxIndex: number,
587
  hasOffsets: boolean,
588
  hasMValues: boolean,
589
  indicesIndex: number,
590
  tessellationIndex: number,
591
) => T;
592

593
/**
594
 * @param bytes - the bytes to read from
595
 * @param extent - the extent of the vector layer to help decode the geometry
596
 * @param cache - the column cache to read from
597
 * @param shape - the shape of the feature's properties data
598
 * @param mShape - the shape of the feature's m-values if they exist
599
 * @returns - the decoded feature
600
 */
601
export function readFeature(
66✔
602
  bytes: Uint8Array,
28✔
603
  extent: Extents,
32✔
604
  cache: ColumnCacheReader,
28✔
605
  shape: Shape,
28✔
606
  mShape: Shape = {},
52✔
607
): OVectorFeature {
12✔
608
  const pbf = new PbfReader(bytes);
140✔
609
  // pull in the type
610
  const type = pbf.readVarint();
128✔
611
  // next the flags
612
  const flags = pbf.readVarint();
132✔
613
  // read the id if it exists
614
  const id = (flags & 1) > 0 ? pbf.readVarint() : undefined;
234✔
615
  const hashBBOX = (flags & (1 << 1)) > 0;
140✔
616
  const hasOffsets = (flags & (1 << 2)) > 0;
148✔
617
  const hasIndices = (flags & (1 << 3)) > 0;
148✔
618
  const hasTessellation = (flags & (1 << 4)) > 0;
172✔
619
  const hasMValues = (flags & (1 << 5)) > 0;
152✔
620
  const single: boolean = (flags & (1 << 6)) !== 0;
144✔
621
  // read the properties
622
  const valueIndex = pbf.readVarint();
152✔
623
  const properties = decodeValue(valueIndex, shape, cache);
236✔
624
  // if type is 1 or 4, read geometry as a single index, otherwise as an array
625
  let Constructor: Constructor<OVectorFeature>;
72✔
626
  let geometryIndices: number[];
88✔
627
  let indices = -1;
76✔
628
  let tessellationIndex = -1;
116✔
629
  if (type === 1 || type === 4) {
138✔
630
    if (single) geometryIndices = [pbf.readVarint()];
228✔
631
    else geometryIndices = cache.getColumn(OColumnName.indices, pbf.readVarint());
346✔
632
    if (type === 1) Constructor = OVectorPointsFeature;
236✔
633
    else Constructor = OVectorPoints3DFeature;
194✔
634
  } else {
46✔
635
    geometryIndices = cache.getColumn(OColumnName.indices, pbf.readVarint());
308✔
636
    if (type === 2) Constructor = OVectorLinesFeature;
232✔
637
    else if (type === 3) Constructor = OVectorPolysFeature;
252✔
638
    else if (type === 5) Constructor = OVectorLines3DFeature;
260✔
639
    else if (type === 6) Constructor = OVectorPolys3DFeature;
252✔
640
    else throw new Error('Type is not supported.');
122✔
641
  }
642
  // read indices and tessellation if they exist
643
  if (type === 3 || type === 6) {
138✔
644
    if (hasIndices) indices = pbf.readVarint();
204✔
645
    if (hasTessellation) tessellationIndex = pbf.readVarint();
256✔
646
  }
10✔
647
  const bboxIndex = hashBBOX ? pbf.readVarint() : -1;
206✔
648

649
  return new Constructor(
92✔
650
    cache,
28✔
651
    id,
16✔
652
    properties,
48✔
653
    mShape,
32✔
654
    extent,
32✔
655
    geometryIndices,
68✔
656
    single,
32✔
657
    bboxIndex,
44✔
658
    hasOffsets,
48✔
659
    hasMValues,
48✔
660
    indices,
36✔
661
    tessellationIndex,
68✔
662
  );
12✔
663
}
664

665
/**
666
 * @param feature - BaseVectorFeature to build a buffer from
667
 * @param shape - The shape of the feature's properties data
668
 * @param mShape - The shape of the feature's m-values if they exist
669
 * @param cache - where to store all feature data to in columns
670
 * @returns - Compressed indexes for the feature
671
 */
672
export function writeOVFeature(
72✔
673
  feature: BaseVectorFeature,
36✔
674
  shape: Shape,
28✔
675
  mShape: Shape = {},
52✔
676
  cache: ColumnCacheWriter,
28✔
677
): Uint8Array {
12✔
678
  // write id, type, properties, bbox, geometry, indices, tessellation, mValues
679
  const pbf = new Protobuf();
108✔
680
  // type is just stored as a varint
681
  pbf.writeVarint(feature.type);
128✔
682
  // store flags if each one exists or not into a single byte
683
  const hasID: boolean = feature.id !== undefined;
164✔
684
  const hasIndices: boolean = 'indices' in feature && feature.indices.length !== 0;
296✔
685
  const hasTessellation: boolean = 'tessellation' in feature && feature.tessellation.length !== 0;
356✔
686
  const hasOffsets: boolean = feature.hasOffsets;
160✔
687
  const hasBBox = 'bbox' in feature && feature.hasBBox;
220✔
688
  const hasMValues = feature.hasMValues;
160✔
689
  const single = feature.geometry.length === 1;
188✔
690
  let flags = 0;
64✔
691
  if (hasID) flags += 1;
104✔
692
  if (hasBBox) flags += 1 << 1;
112✔
693
  if (hasOffsets) flags += 1 << 2;
124✔
694
  if (hasIndices) flags += 1 << 3;
124✔
695
  if (hasTessellation) flags += 1 << 4;
148✔
696
  if (hasMValues) flags += 1 << 5;
128✔
697
  if (single) flags += 1 << 6;
112✔
698
  pbf.writeVarint(flags); // just 1 byte
100✔
699
  // id is stored in unsigned column
700
  if (hasID) pbf.writeVarint(feature.id ?? 0);
192✔
701
  // index to values column
702
  const valueIndex = encodeValue(feature.properties, shape, cache);
268✔
703
  pbf.writeVarint(valueIndex);
120✔
704
  // geometry
705
  const storedGeo = feature.addGeometryToCache(cache, mShape);
248✔
706
  pbf.writeVarint(storedGeo);
116✔
707
  // indices
708
  if ('indices' in feature && hasIndices)
170✔
709
    pbf.writeVarint(cache.addColumnData(OColumnName.indices, feature.indices));
306✔
710
  // tessellation
711
  if ('tessellation' in feature && hasTessellation)
210✔
712
    pbf.writeVarint(cache.addColumnData(OColumnName.points, feature.tessellation));
322✔
713
  // bbox is stored in double column.
714
  if (hasBBox) pbf.writeVarint(cache.addColumnData(OColumnName.bbox, feature.bbox));
344✔
715

716
  return pbf.commit();
82✔
717
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc