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

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

06 Dec 2024 02:32PM UTC coverage: 98.708% (+1.3%) from 97.451%
#27

push

Mr Martian
fix coveralls

8783 of 8898 relevant lines covered (98.71%)

58.5 hits per line

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

95.61
/src/open/vectorFeature.ts
1
import { OColumnName } from './columnCache';
176✔
2
import { Pbf as Protobuf } from 's2-tools';
172✔
3
import { decodeOffset } from '../base';
156✔
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 {
11
  BBox,
12
  BBox3D,
13
  OProperties,
14
  Point,
15
  Point3D,
16
  VectorFeatureType,
17
  VectorLine,
18
  VectorLine3D,
19
  VectorLines,
20
  VectorLines3D,
21
  VectorLines3DWithOffset,
22
  VectorLinesWithOffset,
23
  VectorMultiPoly,
24
  VectorMultiPoly3D,
25
  VectorPoints,
26
  VectorPoints3D,
27
} from '../vectorTile.spec';
28
import type { ColumnCacheReader, ColumnCacheWriter } from './columnCache';
29

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

65
  /** @returns - true if the type of the feature is points */
66
  isPoints(): boolean {
36✔
67
    return this.type === 1;
114✔
68
  }
69

70
  /** @returns - true if the type of the feature is lines */
71
  isLines(): boolean {
34✔
72
    return this.type === 2;
114✔
73
  }
74

75
  /** @returns - true if the type of the feature is polygons */
76
  isPolygons(): boolean {
40✔
77
    return this.type === 3;
114✔
78
  }
79

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

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

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

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

104
  /**
105
   * @returns an empty geometry
106
   */
107
  loadGeometryFlat(): [geometry: number[], indices: number[]] {
52✔
108
    return [[], []];
86✔
109
  }
110

111
  /**
112
   * @returns the indices for the feature
113
   */
114
  readIndices(): number[] {
42✔
115
    return [];
60✔
116
  }
117
}
4✔
118

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

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

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

153
  /** @returns the geometry as an array of points */
154
  loadPoints(): Point[] {
40✔
155
    return this.loadGeometry();
130✔
156
  }
157

158
  /** @returns the geometry as an array of lines */
159
  loadLines(): VectorLinesWithOffset {
38✔
160
    return [];
62✔
161
  }
162

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

185
    return this.geometry;
104✔
186
  }
187
}
4✔
188

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

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

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

228
    this.geometry = lines;
104✔
229
    return lines;
74✔
230
  }
231

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

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

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

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

282
    this.geometry = polys;
104✔
283
    return polys;
74✔
284
  }
285

286
  /** @returns the geometry as a flattened array of points */
287
  loadPoints(): Point[] {
40✔
288
    return this.loadGeometry().flatMap((poly) => {
198✔
289
      return poly.flatMap((line) => line);
168✔
290
    });
18✔
291
  }
292

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

300
  /**
301
   * @returns the geometry as an array of raw poly geometry
302
   */
303
  loadGeometry(): VectorMultiPoly {
44✔
304
    return this.#loadLinesWithOffsets().map((poly) => {
218✔
305
      return poly.map((line) => line.geometry);
188✔
306
    });
18✔
307
  }
308

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

318
    for (const poly of geo) {
114✔
319
      for (const line of poly) {
126✔
320
        for (const point of line.geometry) {
174✔
321
          geometry.push(point.x * multiplier, point.y * multiplier);
300✔
322
        }
26✔
323
      }
18✔
324
    }
6✔
325

326
    this.addTesselation(geometry, multiplier);
184✔
327

328
    return [geometry, this.readIndices()];
174✔
329
  }
330

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

337
  /**
338
   * adds the tesselation to the geometry
339
   * @param geometry - the geometry of the feature
340
   * @param multiplier - the multiplier to apply the extent shift
341
   */
342
  addTesselation(geometry: number[], multiplier: number): void {
128✔
343
    if (this.tesselationIndex === -1) return;
184✔
344
    const data = this.cache.getColumn<Point[]>(OColumnName.points, this.tesselationIndex);
324✔
345
    for (const point of data) {
122✔
346
      geometry.push(point.x * multiplier, point.y * multiplier);
268✔
347
    }
16✔
348
  }
349
}
4✔
350

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

361
  /** @returns the geometry as a flattened array of points */
362
  loadPoints(): Point3D[] {
40✔
363
    return this.loadGeometry();
130✔
364
  }
365

366
  /** @returns the geometry as an array of lines */
367
  loadLines(): VectorLines3DWithOffset {
38✔
368
    return [];
62✔
369
  }
370

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

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

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

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

438
    this.geometry = lines;
104✔
439
    return lines;
74✔
440
  }
441

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

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

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

490
    this.geometry = polys;
104✔
491
    return polys;
74✔
492
  }
493

494
  /** @returns the geometry as a flattened array of points */
495
  loadPoints(): Point3D[] {
40✔
496
    return this.loadGeometry().flatMap((poly) => {
198✔
497
      return poly.flatMap((line) => line);
168✔
498
    });
18✔
499
  }
500

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

508
  /** @returns the geometry as an array of raw poly geometry */
509
  loadGeometry(): VectorMultiPoly3D {
44✔
510
    return this.#loadLinesWithOffsets().map((poly) => {
218✔
511
      return poly.map((line) => line.geometry);
188✔
512
    });
18✔
513
  }
514

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

524
    for (const poly of geo) {
114✔
525
      for (const line of poly) {
126✔
526
        for (const point of line.geometry) {
174✔
527
          geometry.push(point.x * multiplier, point.y * multiplier);
300✔
528
        }
26✔
529
      }
18✔
530
    }
6✔
531

532
    this.addTesselation(geometry, multiplier);
184✔
533

534
    return [geometry, this.readIndices()];
174✔
535
  }
536

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

543
  /**
544
   * adds the tesselation to the geometry
545
   * @param geometry - the geometry of the feature
546
   * @param multiplier - the multiplier to apply the extent shift
547
   */
548
  addTesselation(geometry: number[], multiplier: number): void {
128✔
549
    if (this.tesselationIndex === -1) return;
184✔
550
    const data = this.cache.getColumn<Point3D[]>(OColumnName.points3D, this.tesselationIndex);
332✔
551
    for (const point of data) {
122✔
552
      geometry.push(point.x * multiplier, point.y * multiplier, point.z * multiplier);
356✔
553
    }
16✔
554
  }
555
}
4✔
556

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

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

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

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

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

718
  const data = pbf.commit();
112✔
719
  return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
270✔
720
}
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

© 2026 Coveralls, Inc