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

Open-S2 / s2-tilejson / #16

12 Aug 2024 07:45PM UTC coverage: 99.402%. Remained the same
#16

push

Mr Martian
improve formatting; add formatting check to actions

997 of 1003 relevant lines covered (99.4%)

17.38 hits per line

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

100.0
/src/index.ts
1
/** S2 Face */
2
export type Face = 0 | 1 | 2 | 3 | 4 | 5;
3

4
/** The Bounding box, whether the tile bounds or lon-lat bounds or whatever. */
5
export type BBox = [left: number, bottom: number, right: number, top: number];
6

7
/** 1: points, 2: lines, 3: polys, 4: points3D, 5: lines3D, 6: polys3D */
8
export enum DrawType {
268✔
9
  /** Collection of points */
10
  Points = 1,
184✔
11
  /** Collection of lines */
12
  Lines = 2,
176✔
13
  /** Collection of polygons */
14
  Polys = 3,
176✔
15
  /** Collection of points in 3D */
16
  Points3D = 4,
200✔
17
  /** Collection of lines in 3D */
18
  Lines3D = 5,
192✔
19
  /** Collection of polygons in 3D */
20
  Polys3D = 6,
200✔
21
}
22

23
//? Shapes exist solely to deconstruct and rebuild objects.
24
//?
25
//? Shape limitations:
26
//? - all keys are strings.
27
//? - all values are either:
28
//? - - primitive types: strings, numbers (f32, f64, u64, i64), true, false, or null
29
//? - - sub types: an array of a shape or a nested object which is itself a shape
30
//? - - if the sub type is an array, ensure all elements are of the same type
31
//? The interfaces below help describe how shapes are built by the user.
32

33
import ShapeSchema from './shape.schema.json';
184✔
34
export { ShapeSchema };
90✔
35

36
/**
37
 * Primitive types that can be found in a shape
38
 */
39
export type PrimitiveShapes = 'string' | 'f32' | 'f64' | 'u64' | 'i64' | 'bool' | 'null';
40

41
/** The Shape Object But the values can only be primitives */
42
export interface ShapePrimitive {
43
  [key: string]: PrimitiveShapes;
44
}
45

46
/**
47
 * Arrays may contain either a primitive or an object whose values are primitives
48
 */
49
export type ShapePrimitiveType = PrimitiveShapes | ShapePrimitive;
50

51
/**
52
 * Shape types that can be found in a shapes object.
53
 * Either a primitive, an array containing any type, or a nested shape.
54
 * If the type is an array, all elements must be the same type
55
 */
56
export type ShapeType = PrimitiveShapes | [ShapePrimitiveType] | Shape;
57

58
/** The Shape Object */
59
export interface Shape {
60
  [key: string]: ShapeType;
61
}
62

63
/** Each layer has metadata associated with it. Defined as blueprints pre-construction of vector data. */
64
export interface LayerMetaData {
65
  description?: string;
66
  minzoom: number;
67
  maxzoom: number;
68
  drawTypes: DrawType[];
69
  shape: Shape;
70
  mShape?: Shape;
71
}
72

73
/** Each layer has metadata associated with it. Defined as blueprints pre-construction of vector data. */
74
export interface LayersMetaData {
75
  [layer: string]: LayerMetaData;
76
}
77

78
/** Tilestats is simply a tracker to see where most of the tiles live */
79
export interface TileStatsMetadata {
80
  total: number;
81
  0: number;
82
  1: number;
83
  2: number;
84
  3: number;
85
  4: number;
86
  5: number;
87
}
88

89
/**
90
 * Attribution data is stored in an object.
91
 * The key is the name of the attribution, and the value is the link
92
 */
93
export type Attribution = Record<string, string>;
94

95
/** Track the S2 tile bounds of each face and zoom */
96
export interface FaceBounds {
97
  // facesbounds[face][zoom] = [...]
98
  0: { [zoom: number]: BBox };
99
  1: { [zoom: number]: BBox };
100
  2: { [zoom: number]: BBox };
101
  3: { [zoom: number]: BBox };
102
  4: { [zoom: number]: BBox };
103
  5: { [zoom: number]: BBox };
104
}
105

106
/** Track the WM tile bounds of each zoom */
107
export interface WMBounds {
108
  [zoom: number]: BBox;
109
}
110

111
/** Types of image extensions */
112
export type ImageExtensions =
113
  | 'raw'
114
  | 'png'
115
  | 'jpg'
116
  | 'jpeg'
117
  | 'jpe'
118
  | 'webp'
119
  | 'avif'
120
  | 'gif'
121
  | 'svg'
122
  | 'bmp'
123
  | 'tiff'
124
  | 'ico'
125
  | 'cur';
126

127
/** All supported extensions */
128
export type Extensions = 'geojson' | 'json' | 's2json' | 'pbf' | ImageExtensions | string;
129

130
/**
131
 * Check the source type of the layer.
132
 * If "overlay" then an old engine was used
133
 */
134
export type SourceType =
135
  | 'vector'
136
  | 'json'
137
  | 'raster'
138
  | 'raster-dem'
139
  | 'sensor'
140
  | 'markers'
141
  | 'overlay';
142

143
/** Store the encoding of the data */
144
export type Encoding = 'gz' | 'br' | 'none' | 'zstd';
145

146
/** Old spec tracks basic vector data */
147
export interface VectorLayer {
148
  id: string;
149
  description?: string;
150
  minzoom?: number;
151
  maxzoom?: number;
152
  fields: Record<string, string>;
153
}
154

155
/**
156
 * Default S2 tile scheme is `fzxy`
157
 * Default Web Mercator tile scheme is `xyz`
158
 * Adding a t prefix to the scheme will change the request to be time sensitive
159
 * TMS is an oudated version that is not supported by s2maps-gpu
160
 */
161
export type Scheme = 'fzxy' | 'tfzxy' | 'xyz' | 'txyz' | 'tms';
162

163
/** Store where the center of the data lives */
164
export interface Center {
165
  lon: number;
166
  lat: number;
167
  zoom: number;
168
}
169

170
/** Metadata for the tile data */
171
export interface Metadata {
172
  /** The version of the s2-tilejson spec */
173
  s2tilejson: string;
174
  /** The version of the data */
175
  version: string;
176
  /** The name of the data */
177
  name: string;
178
  /** The extension when requesting a tile */
179
  extension: Extensions;
180
  /** The scheme of the data */
181
  scheme: Scheme;
182
  /** The description of the data */
183
  description: string;
184
  /** The type of the data */
185
  type: SourceType;
186
  /** The encoding of the data */
187
  encoding?: Encoding;
188
  /** List of faces that have data */
189
  faces: Face[];
190
  /** WM Tile fetching bounds. Helpful to not make unecessary requests for tiles we know don't exist */
191
  bounds: WMBounds;
192
  /** S2 Tile fetching bounds. Helpful to not make unecessary requests for tiles we know don't exist */
193
  facesbounds: FaceBounds;
194
  /** minzoom at which to request tiles. [default=0] */
195
  minzoom: number;
196
  /** maxzoom at which to request tiles. [default=27] */
197
  maxzoom: number;
198
  /** The center of the data */
199
  center: Center;
200
  /** { ['human readable string']: 'href' } */
201
  attribution: Attribution;
202
  /** Track layer metadata */
203
  layers: LayersMetaData;
204
  /** Track tile stats for each face and total overall */
205
  tilestats?: TileStatsMetadata;
206
  /** Old spec, track basic layer metadata */
207
  vector_layers: VectorLayer[];
208
}
209

210
/** Builder class to help build the metadata */
211
export class MetadataBuilder {
94✔
212
  #lonLatBounds: BBox = [Infinity, Infinity, -Infinity, -Infinity];
196✔
213
  #faces: Set<Face> = new Set();
76✔
214
  #metadata: Metadata = {
68✔
215
    s2tilejson: '1.0.0',
96✔
216
    version: '1.0.0',
84✔
217
    name: 'default',
80✔
218
    scheme: 'fzxy',
76✔
219
    extension: 'pbf',
84✔
220
    description: 'Built with s2maps-cli',
164✔
221
    type: 'vector',
76✔
222
    encoding: 'none',
84✔
223
    faces: [],
56✔
224
    bounds: {},
60✔
225
    facesbounds: {
80✔
226
      0: {},
48✔
227
      1: {},
48✔
228
      2: {},
48✔
229
      3: {},
48✔
230
      4: {},
48✔
231
      5: {},
36✔
232
    },
24✔
233
    minzoom: Infinity,
76✔
234
    maxzoom: -Infinity,
80✔
235
    center: { lon: 0, lat: 0, zoom: 0 },
160✔
236
    attribution: {},
80✔
237
    tilestats: {
72✔
238
      total: 0,
60✔
239
      0: 0,
44✔
240
      1: 0,
44✔
241
      2: 0,
44✔
242
      3: 0,
44✔
243
      4: 0,
44✔
244
      5: 0,
32✔
245
    },
24✔
246
    layers: {},
60✔
247
    vector_layers: [],
76✔
248
  };
14✔
249

250
  /** @returns - resultant metadata */
251
  commit(): Metadata {
32✔
252
    // set the center
253
    this.#updateCenter();
100✔
254
    // set the faces
255
    this.#metadata.faces = [...this.#faces];
176✔
256
    // return the result
257
    return this.#metadata;
110✔
258
  }
259

260
  /**
261
   * Set the name
262
   * @param name - name of the data
263
   */
264
  setName(name: string) {
50✔
265
    this.#metadata.name = name;
136✔
266
  }
267

268
  /**
269
   * Set the extension
270
   * @param extension - extension of the data
271
   */
272
  setExtension(extension: Extensions) {
80✔
273
    this.#metadata.extension = extension;
176✔
274
  }
275

276
  /**
277
   * Set the scheme of the data. [default=fzxy]
278
   * @param scheme - scheme of the data
279
   */
280
  setScheme(scheme: Scheme) {
62✔
281
    this.#metadata.scheme = scheme;
152✔
282
  }
283

284
  /**
285
   * Set the type of the data. [default=vector]
286
   * @param type - type of the data
287
   */
288
  setType(type: SourceType) {
50✔
289
    this.#metadata.type = type;
136✔
290
  }
291

292
  /**
293
   * Set the version of the data
294
   * @param version - version of the data
295
   */
296
  setVersion(version: string) {
68✔
297
    this.#metadata.version = version;
160✔
298
  }
299

300
  /**
301
   * Describe the data
302
   * @param description - description of the data
303
   */
304
  setDescription(description: string) {
92✔
305
    this.#metadata.description = description;
192✔
306
  }
307

308
  /**
309
   * Set the encoding of each vector tile. [default=none]
310
   * @param encoding - encoding of each tile
311
   */
312
  setEncoding(encoding: Encoding) {
74✔
313
    this.#metadata.encoding = encoding;
168✔
314
  }
315

316
  /**
317
   * Add an attribution
318
   * @param displayName - name of the attribution
319
   * @param href - link to the attribution
320
   */
321
  addAttribution(displayName: string, href: string) {
116✔
322
    this.#metadata.attribution[displayName] = href;
216✔
323
  }
324

325
  /**
326
   * Add the layer metadata
327
   * @param name - name of the layer
328
   * @param layer - layer metadata
329
   */
330
  addLayer(name: string, layer: LayerMetaData) {
80✔
331
    // add layer
332
    this.#metadata.layers[name] = layer;
160✔
333
    // add vector layer
334
    this.#metadata.vector_layers?.push({
180✔
335
      id: name,
60✔
336
      description: layer.description,
148✔
337
      minzoom: layer.minzoom,
116✔
338
      maxzoom: layer.maxzoom,
116✔
339
      fields: {},
56✔
340
    });
16✔
341
    // update minzoom and maxzoom
342
    if (layer.minzoom < this.#metadata.minzoom) this.#metadata.minzoom = layer.minzoom;
364✔
343
    if (layer.maxzoom > this.#metadata.maxzoom) this.#metadata.maxzoom = layer.maxzoom;
376✔
344
  }
345

346
  /**
347
   * Add the WM tile metadata
348
   * @param zoom - zoom of the tile
349
   * @param x - x position of the tile
350
   * @param y - y position of the tile
351
   * @param llBounds - the lon-lat bounds of the tile
352
   */
353
  addTileWM(zoom: number, x: number, y: number, llBounds: BBox) {
118✔
354
    const metadata = this.#metadata;
144✔
355
    // update tile stats
356
    if (metadata.tilestats) metadata.tilestats.total++;
236✔
357
    this.#faces.add(0);
92✔
358
    this.#addBoundsWM(zoom, x, y);
136✔
359
    // update lon lat
360
    this.#updateLonLatBounds(llBounds);
168✔
361
  }
362

363
  /**
364
   * Add the S2 tile metadata
365
   * @param face - face of the tile
366
   * @param zoom - zoom of the tile
367
   * @param x - x position of the tile
368
   * @param y - y position of the tile
369
   * @param llBounds - the lon-lat bounds of the tile
370
   */
371
  addTileS2(face: Face, zoom: number, x: number, y: number, llBounds: BBox): void {
142✔
372
    const metadata = this.#metadata;
144✔
373
    // update tile stats
374
    if (metadata.tilestats) {
114✔
375
      metadata.tilestats.total++;
132✔
376
      metadata.tilestats[face]++;
144✔
377
    }
6✔
378
    this.#faces.add(face);
104✔
379
    this.#addBoundsS2(face, zoom, x, y);
160✔
380
    // update lon lat
381
    this.#updateLonLatBounds(llBounds);
168✔
382
  }
383

384
  /**
385
   * Update the center now that all tiles have been added
386
   */
387
  #updateCenter() {
46✔
388
    const { minzoom, maxzoom } = this.#metadata;
192✔
389
    const [minlon, minlat, maxlon, maxlat] = this.#lonLatBounds;
256✔
390
    this.#metadata.center = {
136✔
391
      lon: (minlon + maxlon) >> 1,
128✔
392
      lat: (minlat + maxlat) >> 1,
128✔
393
      zoom: (minzoom + maxzoom) >> 1,
128✔
394
    };
24✔
395
  }
396

397
  /**
398
   * Add the bounds of the tile for WM data
399
   * @param zoom - zoom of the tile
400
   * @param x - x position of the tile
401
   * @param y - y position of the tile
402
   */
403
  #addBoundsWM(zoom: number, x: number, y: number): void {
84✔
404
    if (this.#metadata.bounds[zoom] === undefined) {
206✔
405
      this.#metadata.bounds[zoom] = [Infinity, Infinity, -Infinity, -Infinity];
280✔
406
    }
6✔
407

408
    const bbox = this.#metadata.bounds[zoom];
180✔
409
    bbox[0] = Math.min(bbox[0], x);
140✔
410
    bbox[1] = Math.min(bbox[1], y);
140✔
411
    bbox[2] = Math.max(bbox[2], x);
140✔
412
    bbox[3] = Math.max(bbox[3], y);
152✔
413
  }
414

415
  /**
416
   * Add the bounds of the tile for S2 data
417
   * @param face - face of the tile
418
   * @param zoom - zoom of the tile
419
   * @param x - x position of the tile
420
   * @param y - y position of the tile
421
   */
422
  #addBoundsS2(face: Face, zoom: number, x: number, y: number): void {
108✔
423
    if (this.#metadata.facesbounds[face][zoom] === undefined) {
250✔
424
      this.#metadata.facesbounds[face][zoom] = [Infinity, Infinity, -Infinity, -Infinity];
324✔
425
    }
6✔
426

427
    const bbox = this.#metadata.facesbounds[face][zoom];
224✔
428
    bbox[0] = Math.min(bbox[0], x);
140✔
429
    bbox[1] = Math.min(bbox[1], y);
140✔
430
    bbox[2] = Math.max(bbox[2], x);
140✔
431
    bbox[3] = Math.max(bbox[3], y);
152✔
432
  }
433

434
  /**
435
   * Update the lon-lat bounds so eventually we can find the center point of the data
436
   * @param llBounds - the lon-lat bounds of the tile
437
   */
438
  #updateLonLatBounds(llBounds: BBox) {
90✔
439
    const [minlon, minlat, maxlon, maxlat] = llBounds;
216✔
440
    this.#lonLatBounds[0] = Math.min(this.#lonLatBounds[0], minlon);
272✔
441
    this.#lonLatBounds[1] = Math.min(this.#lonLatBounds[1], minlat);
272✔
442
    this.#lonLatBounds[2] = Math.max(this.#lonLatBounds[2], maxlon);
272✔
443
    this.#lonLatBounds[3] = Math.max(this.#lonLatBounds[3], maxlat);
282✔
444
  }
445
}
4✔
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