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

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

26 Jun 2024 06:18PM UTC coverage: 100.0%. Remained the same
#6

push

Mr Martian
include dependecy graph

2362 of 2362 relevant lines covered (100.0%)

106.23 hits per line

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

100.0
/src/base/vectorFeature.ts
1
import { OColumnName } from '../open/columnCache';
192✔
2
import { encodeValue } from '../open/shape';
168✔
3
import { weave2D, weave3D, zigzag } from '../util';
196✔
4

5
import type { ColumnCacheWriter } from '../open/columnCache';
6
import type MapboxVectorFeature from '../mapbox/vectorFeature';
7
import type { Shape } from '../open/shape';
8
import type {
9
  BBOX,
10
  BBox,
11
  BBox3D,
12
  OProperties,
13
  Point,
14
  VectorLine,
15
  VectorLine3D,
16
  VectorLines,
17
  VectorMultiPoly,
18
  VectorPoints,
19
  VectorPoints3D,
20
} from '../vectorTile.spec';
21

22
/**
23
 * Base Vector Feature
24
 * Common variables and methods shared by all vector features
25
 */
26
export class VectorFeatureBase<G, B = BBOX> {
27
  type = 0;
28
  /**
29
   * @param geometry - the geometry of the feature
30
   * @param properties - the properties of the feature
31
   * @param id - the id of the feature if there is one
32
   * @param bbox - the BBox of the feature
33
   */
34
  constructor(
35
    public geometry: G,
192✔
36
    public properties: OProperties = {},
252✔
37
    public id?: number,
104✔
38
    public bbox?: B,
156✔
39
  ) {}
8✔
40

41
  /** @returns - true if the feature has BBox */
42
  get hasBBox(): boolean {
34✔
43
    const bbox = this.bbox as BBOX | undefined;
108✔
44
    return bbox !== undefined && bbox.some((v) => v !== 0);
236✔
45
  }
46
}
4✔
47

48
//! Points & Points3D
49

50
/** Base Vector Points Feature */
51
export class VectorFeaturePointsBase<
260✔
52
  G = VectorPoints | VectorPoints3D,
53
  B = BBOX,
54
> extends VectorFeatureBase<G, B> {
90✔
55
  /**
56
   * Points do not have this feature, so return false
57
   * @returns false always
58
   */
59
  get hasOffsets(): boolean {
40✔
60
    return false;
82✔
61
  }
62

63
  /**
64
   * Points do not have this feature, so return false
65
   * @returns false always
66
   */
67
  get hasMValues(): boolean {
40✔
68
    const geometry = this.geometry as VectorPoints | VectorPoints3D;
140✔
69
    return geometry.some(({ m }) => m !== undefined);
214✔
70
  }
71

72
  /** @returns the geometry */
73
  loadGeometry(): G {
44✔
74
    return this.geometry;
106✔
75
  }
76

77
  /** @returns the M-Values */
78
  getMValues(): undefined | OProperties[] {
40✔
79
    if (!this.hasMValues) return undefined;
148✔
80
    const geometry = this.geometry as VectorPoints | VectorPoints3D;
140✔
81
    return geometry.map(({ m }) => m ?? {});
178✔
82
  }
83

84
  /**
85
   * @param cache - the column cache to store the geometry
86
   * @param mShape - the shape of the M-values to encode the values as
87
   * @returns the index in the points column where the geometry is stored
88
   */
89
  addGeometryToCache(cache: ColumnCacheWriter, mShape: Shape = {}): number {
128✔
90
    const { hasMValues } = this;
128✔
91
    const geometry = this.geometry as VectorPoints | VectorPoints3D;
140✔
92
    const is3D = this.type === 4;
132✔
93
    const columnName = is3D ? OColumnName.points3D : OColumnName.points;
282✔
94
    if (geometry.length === 1) {
126✔
95
      if (is3D) {
66✔
96
        const { x, y, z } = (geometry as VectorPoints3D)[0];
160✔
97
        return weave3D(zigzag(x), zigzag(y), zigzag(z));
230✔
98
      } else {
34✔
99
        const { x, y } = geometry[0];
148✔
100
        return weave2D(zigzag(x), zigzag(y));
196✔
101
      }
102
    }
6✔
103
    // othwerise store the collection of points
104
    const indices: number[] = [];
92✔
105
    indices.push(cache.addColumnData(columnName, geometry));
240✔
106
    // store the mvalues indexes if they exist
107
    if (hasMValues) {
82✔
108
      for (const { m } of geometry) {
146✔
109
        indices.push(encodeValue(m ?? {}, mShape, cache));
252✔
110
      }
18✔
111
    }
6✔
112
    return cache.addColumnData(OColumnName.indices, indices);
248✔
113
  }
114
}
4✔
115

116
/**
117
 * Base Vector Points Feature
118
 * Type 1
119
 * Extends from @see {@link VectorFeatureBase}.
120
 * Store either a single point or a list of points
121
 */
122
export class BaseVectorPointsFeature extends VectorFeaturePointsBase<VectorPoints, BBox> {
284✔
123
  type = 1;
40✔
124
}
4✔
125
/**
126
 * Base Vector Point 3D Feature
127
 */
128
export class BaseVectorPoint3DFeature extends VectorFeaturePointsBase<VectorPoints3D, BBox3D> {
288✔
129
  type = 4;
40✔
130
}
4✔
131

132
//! Lines & Lines3D
133

134
/**
135
 * Base Vector Lines Feature
136
 * Common variables and methods shared by all vector lines and/or polygons features
137
 */
138
export class BaseVectorLine<L = VectorLine | VectorLine3D> {
196✔
139
  /**
140
   * @param geometry - the geometry of the feature
141
   * @param offset - the offset of the feature
142
   */
143
  constructor(
26✔
144
    public geometry: L,
200✔
145
    public offset: number = 0,
192✔
146
  ) {}
8✔
147
}
4✔
148

149
/** Base Vector Lines Feature */
150
export class VectorFeatureLinesBase<
172✔
151
  G = VectorLine | VectorLine3D,
152
  B = BBOX,
153
> extends VectorFeatureBase<BaseVectorLine<G>[], B> {
90✔
154
  /** @returns - true if the feature has offsets */
155
  get hasOffsets(): boolean {
40✔
156
    const geometry = this.geometry as BaseVectorLine<G>[];
140✔
157
    return geometry.some(({ offset }) => offset > 0);
222✔
158
  }
159

160
  /**
161
   * @returns - true if the feature has M values
162
   */
163
  get hasMValues(): boolean {
40✔
164
    return this.geometry.some(({ geometry }) => {
194✔
165
      return (geometry as VectorLine | VectorLine3D).some(({ m }) => m !== undefined);
220✔
166
    });
18✔
167
  }
168

169
  /** @returns the flattened geometry */
170
  loadGeometry(): G[] {
44✔
171
    return this.geometry.map(({ geometry }) => geometry);
230✔
172
  }
173

174
  /** @returns the flattened M values */
175
  getMValues(): undefined | OProperties[] {
40✔
176
    if (!this.hasMValues) return undefined;
148✔
177
    return this.geometry.flatMap(({ geometry }) =>
198✔
178
      (geometry as VectorLine | VectorLine3D).map(({ m }) => m ?? {}),
122✔
179
    );
18✔
180
  }
181

182
  /**
183
   * @param cache - the column cache to store the geometry
184
   * @param mShape - the shape of the M-values to encode the values as
185
   * @returns the indexes in the points column where the geometry is stored
186
   */
187
  addGeometryToCache(cache: ColumnCacheWriter, mShape: Shape = {}): number {
128✔
188
    const { hasOffsets, hasMValues } = this;
176✔
189
    const geometry = this.geometry as BaseVectorLine<VectorLine | VectorLine3D>[];
140✔
190
    const columnName = this.type === 5 ? OColumnName.points3D : OColumnName.points;
326✔
191
    const indices: number[] = [];
92✔
192
    // store number of lines
193
    if (geometry.length !== 1) indices.push(geometry.length);
260✔
194
    for (const line of geometry) {
134✔
195
      // store offset for current line
196
      if (hasOffsets) indices.push(encodeOffset(line.offset));
272✔
197
      // store geometry data and track its index position
198
      indices.push(cache.addColumnData(columnName, line.geometry));
268✔
199
      // store the mvalues indexes if they exist
200
      if (hasMValues) {
90✔
201
        for (const { m } of line.geometry) {
174✔
202
          indices.push(encodeValue(m ?? {}, mShape, cache));
268✔
203
        }
26✔
204
      }
18✔
205
    }
6✔
206
    return cache.addColumnData(OColumnName.indices, indices);
248✔
207
  }
208
}
4✔
209

210
/**
211
 * Base Vector Lines Feature
212
 * Type 2
213
 * Extends from @see {@link VectorFeatureBase}.
214
 * Store either a single line or a list of lines.
215
 */
216
export class BaseVectorLinesFeature extends VectorFeatureLinesBase<VectorLine, BBox> {
276✔
217
  type = 2;
40✔
218
}
4✔
219
/**
220
 * Base Vector Lines 3D Feature
221
 * Type 5
222
 * Extends from @see {@link VectorFeatureBase}.
223
 * Store either a single 3D line or a list of 3D lines
224
 */
225
export class BaseVectorLines3DFeature extends VectorFeatureLinesBase<VectorLine3D, BBox3D> {
284✔
226
  type = 5;
40✔
227
}
4✔
228

229
//! Polys & Polys3D
230

231
/** Base Vector Polys Feature */
232
export class VectorFeaturePolysBase<
248✔
233
  G = VectorLine | VectorLine3D,
234
  B = BBOX,
235
> extends VectorFeatureBase<BaseVectorLine<G>[][], B> {
84✔
236
  tesselation: Point[];
54✔
237
  /**
238
   * @param geometry - the geometry of the feature
239
   * @param indices - the indices of the geometry
240
   * @param tesselation - the tesselation of the geometry
241
   * @param properties - the properties of the feature
242
   * @param id - the id of the feature
243
   * @param bbox - the bbox of the feature
244
   */
245
  constructor(
26✔
246
    public geometry: BaseVectorLine<G>[][],
200✔
247
    public indices: number[] = [],
204✔
248
    tesselation: number[] = [],
72✔
249
    properties: OProperties = {},
68✔
250
    id?: number,
16✔
251
    public bbox?: B,
136✔
252
  ) {
8✔
253
    super(geometry, properties, id, bbox);
168✔
254
    this.tesselation = this.#fixTesselation(tesselation);
240✔
255
  }
256

257
  /**
258
   * @param tesselation - the tesselation of the geometry but flattened
259
   * @returns - the tesselation of the geometry as a list of points
260
   */
261
  #fixTesselation(tesselation: number[]): Point[] {
94✔
262
    if (tesselation.length % 2 !== 0) {
150✔
263
      throw new Error('The input tesselation must have an even number of elements.');
176✔
264
    }
6✔
265
    return tesselation.reduce((acc, _, index, array) => {
226✔
266
      if (index % 2 === 0) {
110✔
267
        acc.push({ x: array[index], y: array[index + 1] });
256✔
268
      }
6✔
269
      return acc;
80✔
270
    }, [] as Point[]);
34✔
271
  }
272

273
  /**
274
   * @returns true if the feature has offsets
275
   */
276
  get hasOffsets(): boolean {
40✔
277
    return this.geometry.some((poly) => poly.some(({ offset }) => offset > 0));
322✔
278
  }
279

280
  /**
281
   * @returns - true if the feature has M values
282
   */
283
  get hasMValues(): boolean {
40✔
284
    return this.geometry.some((poly) =>
154✔
285
      poly.some(({ geometry }) => {
118✔
286
        return (geometry as VectorLine | VectorLine3D).some(({ m }) => m !== undefined);
220✔
287
      }),
2✔
288
    );
18✔
289
  }
290

291
  /**
292
   * @returns the flattened geometry
293
   */
294
  loadGeometry(): G[][] {
44✔
295
    return this.geometry.map((poly) => poly.map((line) => line.geometry));
294✔
296
  }
297

298
  /**
299
   * @returns the flattened M-values
300
   */
301
  getMValues(): undefined | OProperties[] {
40✔
302
    if (!this.hasMValues) return undefined;
148✔
303
    return this.geometry.flatMap((poly) => {
174✔
304
      return poly.flatMap(({ geometry }) => {
178✔
305
        return (geometry as VectorLine | VectorLine3D).map((point) => point.m ?? {});
220✔
306
      });
16✔
307
    });
18✔
308
  }
309

310
  /**
311
   * @param cache - the column cache to store the geometry
312
   * @param mShape - the shape of the M-values to encode the values as
313
   * @returns the indexes in the points column where the geometry is stored
314
   */
315
  addGeometryToCache(cache: ColumnCacheWriter, mShape: Shape = {}): number {
128✔
316
    const { hasOffsets, hasMValues } = this;
176✔
317
    const geometry = this.geometry as BaseVectorLine<G>[][];
140✔
318
    const columnName = this.type === 6 ? OColumnName.points3D : OColumnName.points;
326✔
319
    const indices: number[] = [];
92✔
320
    // store number of polygons
321
    if (this.geometry.length > 1) indices.push(geometry.length);
272✔
322
    for (const poly of geometry) {
134✔
323
      // store number of lines in the polygon
324
      indices.push(poly.length);
128✔
325
      // store each line
326
      for (const line of poly) {
126✔
327
        // store offset for current line
328
        if (hasOffsets) indices.push(encodeOffset(line.offset));
288✔
329
        // store geometry data and track its index position
330
        indices.push(cache.addColumnData(columnName, line.geometry));
276✔
331
        // store the mvalues indexes if they exist
332
        if (hasMValues) {
98✔
333
          for (const { m } of line.geometry as VectorLine | VectorLine3D) {
182✔
334
            indices.push(encodeValue(m ?? {}, mShape, cache));
284✔
335
          }
34✔
336
        }
26✔
337
      }
18✔
338
    }
6✔
339
    return cache.addColumnData(OColumnName.indices, indices);
248✔
340
  }
341
}
4✔
342

343
/**
344
 * Base Vector Polys Feature
345
 * Type 3
346
 * Extends from @see {@link VectorFeatureBase}.
347
 * Store either a single polygon or a list of polygons
348
 */
349
export class BaseVectorPolysFeature extends VectorFeaturePolysBase<VectorLine, BBox> {
276✔
350
  type = 3;
40✔
351
}
4✔
352

353
/**
354
 * Base Vector Polys 3D Feature
355
 * Type 6
356
 * Extends from @see {@link VectorFeatureBase}.
357
 * Store either a single 3D poly or a list of 3D polys
358
 */
359
export class BaseVectorPolys3DFeature extends VectorFeaturePolysBase<VectorLine3D, BBox3D> {
284✔
360
  type = 6;
40✔
361
}
2✔
362

363
/**
364
 * A type that encompasses all vector tile feature types
365
 */
366
export type BaseVectorFeature =
367
  | BaseVectorPointsFeature
368
  | BaseVectorLinesFeature
369
  | BaseVectorPolysFeature
370
  | BaseVectorPoint3DFeature
371
  | BaseVectorLines3DFeature
372
  | BaseVectorPolys3DFeature;
373

374
/**
375
 * @param feature - A mapbox vector feature that's been parsed from protobuf data
376
 * @returns - A base feature to help build a vector tile
377
 */
378
export function fromMapboxVectorFeature(feature: MapboxVectorFeature): BaseVectorFeature {
134✔
379
  const { id, properties, extent } = feature;
180✔
380
  const geometry = feature.loadGeometry();
168✔
381
  const indices = feature.readIndices();
160✔
382
  const tesselation: number[] = [];
100✔
383
  feature.addTesselation(tesselation, 1 / extent);
200✔
384
  switch (feature.type) {
130✔
385
    case 1:
12✔
386
      return new BaseVectorPointsFeature(geometry as VectorPoints, properties, id);
290✔
387
    case 2: {
20✔
388
      const geo = geometry as VectorLines;
108✔
389
      const baseLines: BaseVectorLine<VectorLine>[] = [];
108✔
390
      for (const line of geo) {
122✔
391
        baseLines.push(new BaseVectorLine(line));
216✔
392
      }
6✔
393
      return new BaseVectorLinesFeature(baseLines, properties, id);
270✔
394
    }
30✔
395
    case 3:
42✔
396
    case 4: {
20✔
397
      const geo = geometry as VectorMultiPoly;
108✔
398
      const baseMultPoly: BaseVectorLine[][] = [];
120✔
399
      for (const poly of geo) {
122✔
400
        const baseLines: BaseVectorLine[] = [];
116✔
401
        for (const line of poly) {
134✔
402
          baseLines.push(new BaseVectorLine(line));
232✔
403
        }
6✔
404
        baseMultPoly.push(baseLines);
168✔
405
      }
6✔
406
      return new BaseVectorPolysFeature(baseMultPoly, indices, tesselation, properties, id);
370✔
407
    }
28✔
408
    default:
409
      throw new Error(`Unknown feature type: ${feature.type}`);
136✔
410
  }
411
}
412

413
/**
414
 * Encode offset values into a signed integer to reduce byte cost without too much loss
415
 * @param offset - float or double value to be compressed
416
 * @returns - a signed integer that saves 3 decimal places
417
 */
418
export function encodeOffset(offset: number): number {
108✔
419
  return Math.floor(offset * 1_000);
140✔
420
}
421

422
/**
423
 * Decode offset from a signed integer into a float or double
424
 * @param offset - the signed integer to be decompressed
425
 * @returns - a float or double that restores 3 decimal places
426
 */
427
export function decodeOffset(offset: number): number {
108✔
428
  return offset / 1_000;
92✔
429
}
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