Skip to content

Commit 76d1c14

Browse files
committed
[GLJS-1274] Add support for extra_bounds in TileJSON (internal-2447)
1 parent 163f00d commit 76d1c14

15 files changed

+364
-39
lines changed

src/source/load_tilejson.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export default function (
100100
const result: TileJSON = pick(
101101
// explicit source options take precedence over TileJSON
102102
extend({}, tileJSON, options),
103-
['tilejson', 'tiles', 'minzoom', 'maxzoom', 'attribution', 'mapbox_logo', 'bounds', 'scheme', 'tileSize', 'encoding', 'vector_layers', 'raster_layers', 'worldview_options', 'worldview_default', 'worldview']
103+
['tilejson', 'tiles', 'minzoom', 'maxzoom', 'attribution', 'mapbox_logo', 'bounds', 'extra_bounds', 'scheme', 'tileSize', 'encoding', 'vector_layers', 'raster_layers', 'worldview_options', 'worldview_default', 'worldview']
104104
);
105105

106106
result.tiles = requestManager.canonicalizeTileset(result, options.url);

src/source/raster_tile_source.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class RasterTileSource<T = 'raster'> extends Evented<SourceEvents> implements IS
6363
rasterLayerIds?: Array<string>;
6464

6565
bounds: [number, number, number, number] | null | undefined;
66-
tileBounds: TileBounds;
66+
tileBounds?: TileBounds;
6767
roundZoom: boolean | undefined;
6868
reparseOverscaled: boolean | undefined;
6969
dispatcher: Dispatcher;
@@ -112,8 +112,7 @@ class RasterTileSource<T = 'raster'> extends Evented<SourceEvents> implements IS
112112
this.rasterLayerIds = this.rasterLayers.map(layer => layer.id);
113113
}
114114

115-
if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom);
116-
115+
this.tileBounds = TileBounds.fromTileJSON(tileJSON);
117116
postTurnstileEvent(tileJSON.tiles);
118117

119118
// `content` is included here to prevent a race condition where `Style#updateSources` is called

src/source/tile_bounds.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,78 @@
11
import {LngLatBounds} from '../geo/lng_lat';
22
import {mercatorXfromLng, mercatorYfromLat} from '../geo/mercator_coordinate';
33

4+
import type {TileJSON} from '../types/tilejson';
45
import type {CanonicalTileID} from './tile_id';
56

7+
function contains(bounds: LngLatBounds, tileID: CanonicalTileID): boolean {
8+
const worldSize = Math.pow(2, tileID.z);
9+
10+
const minX = Math.floor(mercatorXfromLng(bounds.getWest()) * worldSize);
11+
const minY = Math.floor(mercatorYfromLat(bounds.getNorth()) * worldSize);
12+
const maxX = Math.ceil(mercatorXfromLng(bounds.getEast()) * worldSize);
13+
const maxY = Math.ceil(mercatorYfromLat(bounds.getSouth()) * worldSize);
14+
15+
const hit = tileID.x >= minX && tileID.x < maxX && tileID.y >= minY && tileID.y < maxY;
16+
return hit;
17+
}
18+
619
class TileBounds {
7-
bounds: LngLatBounds;
20+
bounds?: LngLatBounds;
21+
extraBounds?: LngLatBounds[];
822
minzoom: number;
923
maxzoom: number;
1024

11-
constructor(bounds: [number, number, number, number], minzoom?: number | null, maxzoom?: number | null) {
12-
this.bounds = LngLatBounds.convert(this.validateBounds(bounds));
25+
constructor(bounds?: [number, number, number, number] | null, minzoom?: number | null, maxzoom?: number | null) {
26+
this.bounds = bounds ? LngLatBounds.convert(this.validateBounds(bounds)) : null;
1327
this.minzoom = minzoom || 0;
1428
this.maxzoom = maxzoom || 24;
1529
}
1630

31+
// left, bottom, right, top
1732
validateBounds(bounds: [number, number, number, number]): [number, number, number, number] {
1833
// make sure the bounds property contains valid longitude and latitudes
1934
if (!Array.isArray(bounds) || bounds.length !== 4) return [-180, -90, 180, 90];
2035
return [Math.max(-180, bounds[0]), Math.max(-90, bounds[1]), Math.min(180, bounds[2]), Math.min(90, bounds[3])];
2136
}
2237

38+
addExtraBounds(extraBounds?: [number, number, number, number][] | null) {
39+
if (!extraBounds) return;
40+
if (!this.extraBounds) this.extraBounds = [];
41+
42+
for (const bounds of extraBounds) {
43+
this.extraBounds.push(LngLatBounds.convert(this.validateBounds(bounds)));
44+
}
45+
}
46+
2347
contains(tileID: CanonicalTileID): boolean {
24-
const worldSize = Math.pow(2, tileID.z);
25-
const level = {
26-
minX: Math.floor(mercatorXfromLng(this.bounds.getWest()) * worldSize),
27-
minY: Math.floor(mercatorYfromLat(this.bounds.getNorth()) * worldSize),
28-
maxX: Math.ceil(mercatorXfromLng(this.bounds.getEast()) * worldSize),
29-
maxY: Math.ceil(mercatorYfromLat(this.bounds.getSouth()) * worldSize)
30-
};
31-
const hit = tileID.x >= level.minX && tileID.x < level.maxX && tileID.y >= level.minY && tileID.y < level.maxY;
32-
return hit;
48+
if (tileID.z > this.maxzoom || tileID.z < this.minzoom) {
49+
return false;
50+
}
51+
52+
if (this.bounds && !contains(this.bounds, tileID)) {
53+
return false;
54+
}
55+
56+
if (!this.extraBounds) {
57+
return true;
58+
}
59+
60+
for (const bounds of this.extraBounds) {
61+
if (contains(bounds, tileID)) {
62+
return true;
63+
}
64+
}
65+
66+
return false;
67+
}
68+
69+
static fromTileJSON(tileJSON: Partial<TileJSON>): TileBounds | null {
70+
if (!tileJSON.bounds && !tileJSON.extra_bounds) return null;
71+
72+
const tileBounds = new TileBounds(tileJSON.bounds, tileJSON.minzoom, tileJSON.maxzoom);
73+
tileBounds.addExtraBounds(tileJSON.extra_bounds);
74+
75+
return tileBounds;
3376
}
3477
}
3578

src/source/vector_tile_source.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class VectorTileSource extends Evented<SourceEvents> implements ISource<'vector'
7272
map: Map;
7373
bounds?: [number, number, number, number] | null;
7474
tiles: Array<string>;
75-
tileBounds: TileBounds;
75+
tileBounds?: TileBounds;
7676
reparseOverscaled?: boolean;
7777
isTileClipped?: boolean;
7878
_tileJSONRequest?: Cancelable | null;
@@ -153,7 +153,7 @@ class VectorTileSource extends Evented<SourceEvents> implements ISource<'vector'
153153
}
154154
}
155155

156-
if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom);
156+
this.tileBounds = TileBounds.fromTileJSON(tileJSON);
157157
postTurnstileEvent(tileJSON.tiles, this.map._requestManager._customAccessToken);
158158

159159
// `content` is included here to prevent a race condition where `Style#updateSources` is called

src/style-spec/reference/v8.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,16 @@
834834
],
835835
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`. When this property is included in a source, no tiles outside of the given bounds are requested by Mapbox GL."
836836
},
837+
"extra_bounds": {
838+
"type": "array",
839+
"value": {
840+
"type": "array",
841+
"value": "number",
842+
"length": 4,
843+
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`."
844+
},
845+
"doc": "An array of additional discrete geographic regions where tiles are available. When used alongside the `bounds` property, these regions act as an additional filter - Mapbox GL will only request tiles that are both within the `bounds` and any of the regions defined in `extra_bounds`. When used independently (without `bounds`), Mapbox GL will request tiles that fall within any of the regions in `extra_bounds`. This allows for more fine-grained control over tile requests, particularly when dealing with sparse data coverage."
846+
},
837847
"scheme": {
838848
"type": "enum",
839849
"values": {
@@ -913,6 +923,16 @@
913923
],
914924
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`. When this property is included in a source, no tiles outside of the given bounds are requested by Mapbox GL."
915925
},
926+
"extra_bounds": {
927+
"type": "array",
928+
"value": {
929+
"type": "array",
930+
"value": "number",
931+
"length": 4,
932+
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`."
933+
},
934+
"doc": "An array of additional discrete geographic regions where tiles are available. When used alongside the `bounds` property, these regions act as an additional filter - Mapbox GL will only request tiles that are both within the `bounds` and any of the regions defined in `extra_bounds`. When used independently (without `bounds`), Mapbox GL will request tiles that fall within any of the regions in `extra_bounds`. This allows for more fine-grained control over tile requests, particularly when dealing with sparse data coverage."
935+
},
916936
"minzoom": {
917937
"type": "number",
918938
"default": 0,
@@ -994,6 +1014,16 @@
9941014
],
9951015
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`. When this property is included in a source, no tiles outside of the given bounds are requested by Mapbox GL."
9961016
},
1017+
"extra_bounds": {
1018+
"type": "array",
1019+
"value": {
1020+
"type": "array",
1021+
"value": "number",
1022+
"length": 4,
1023+
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`."
1024+
},
1025+
"doc": "An array of additional discrete geographic regions where tiles are available. When used alongside the `bounds` property, these regions act as an additional filter - Mapbox GL will only request tiles that are both within the `bounds` and any of the regions defined in `extra_bounds`. When used independently (without `bounds`), Mapbox GL will request tiles that fall within any of the regions in `extra_bounds`. This allows for more fine-grained control over tile requests, particularly when dealing with sparse data coverage."
1026+
},
9971027
"minzoom": {
9981028
"type": "number",
9991029
"default": 0,
@@ -1076,6 +1106,16 @@
10761106
],
10771107
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`. When this property is included in a source, no tiles outside of the given bounds are requested by Mapbox GL."
10781108
},
1109+
"extra_bounds": {
1110+
"type": "array",
1111+
"value": {
1112+
"type": "array",
1113+
"value": "number",
1114+
"length": 4,
1115+
"doc": "An array containing the longitude and latitude of the southwest and northeast corners of the source's bounding box in the following order: `[sw.lng, sw.lat, ne.lng, ne.lat]`."
1116+
},
1117+
"doc": "An array of additional discrete geographic regions where tiles are available. When used alongside the `bounds` property, these regions act as an additional filter - Mapbox GL will only request tiles that are both within the `bounds` and any of the regions defined in `extra_bounds`. When used independently (without `bounds`), Mapbox GL will request tiles that fall within any of the regions in `extra_bounds`. This allows for more fine-grained control over tile requests, particularly when dealing with sparse data coverage."
1118+
},
10791119
"minzoom": {
10801120
"type": "number",
10811121
"default": 0,

src/style-spec/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ export type VectorSourceSpecification = {
387387
"url"?: string,
388388
"tiles"?: Array<string>,
389389
"bounds"?: [number, number, number, number],
390+
"extra_bounds"?: Array<[number, number, number, number]>,
390391
"scheme"?: "xyz" | "tms",
391392
"minzoom"?: number,
392393
"maxzoom"?: number,
@@ -401,6 +402,7 @@ export type RasterSourceSpecification = {
401402
"url"?: string,
402403
"tiles"?: Array<string>,
403404
"bounds"?: [number, number, number, number],
405+
"extra_bounds"?: Array<[number, number, number, number]>,
404406
"minzoom"?: number,
405407
"maxzoom"?: number,
406408
"tileSize"?: number,
@@ -415,6 +417,7 @@ export type RasterDEMSourceSpecification = {
415417
"url"?: string,
416418
"tiles"?: Array<string>,
417419
"bounds"?: [number, number, number, number],
420+
"extra_bounds"?: Array<[number, number, number, number]>,
418421
"minzoom"?: number,
419422
"maxzoom"?: number,
420423
"tileSize"?: number,
@@ -432,6 +435,7 @@ export type RasterArraySourceSpecification = {
432435
"url"?: string,
433436
"tiles"?: Array<string>,
434437
"bounds"?: [number, number, number, number],
438+
"extra_bounds"?: Array<[number, number, number, number]>,
435439
"minzoom"?: number,
436440
"maxzoom"?: number,
437441
"tileSize"?: number,

src/style-spec/validate/validate_object.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import type {LayerSpecification} from '../types';
77

88
type Options = ValidationOptions & {
99
layer?: LayerSpecification;
10-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11-
objectElementValidators?: any;
10+
objectElementValidators?: object;
1211
};
1312

1413
export default function validateObject(options: Options): Array<ValidationError> {
@@ -18,7 +17,7 @@ export default function validateObject(options: Options): Array<ValidationError>
1817
const elementValidators = options.objectElementValidators || {};
1918
const style = options.style;
2019
const styleSpec = options.styleSpec;
21-
let errors = [];
20+
let errors: ValidationError[] = [];
2221

2322
const type = getType(object);
2423
if (type !== 'object') {
@@ -67,6 +66,5 @@ export default function validateObject(options: Options): Array<ValidationError>
6766
}
6867
}
6968

70-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
7169
return errors;
7270
}

src/style-spec/validate/validate_source.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function validateSource(options: ValidationOptions): Array<Valida
2626
}
2727

2828
const type = unbundle(value.type) as string;
29-
let errors = [];
29+
let errors: ValidationError[] = [];
3030

3131
if (['vector', 'raster', 'raster-dem', 'raster-array'].includes(type)) {
3232
if (!value.url && !value.tiles) {
@@ -47,9 +47,7 @@ export default function validateSource(options: ValidationOptions): Array<Valida
4747
styleSpec,
4848
objectElementValidators
4949
}));
50-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
5150
return errors;
52-
5351
case 'geojson':
5452
errors = validateObject({
5553
key,
@@ -59,6 +57,7 @@ export default function validateSource(options: ValidationOptions): Array<Valida
5957
styleSpec,
6058
objectElementValidators
6159
});
60+
6261
if (value.cluster) {
6362
for (const prop in value.clusterProperties) {
6463
const [operator, mapExpr] = value.clusterProperties[prop];
@@ -76,9 +75,8 @@ export default function validateSource(options: ValidationOptions): Array<Valida
7675
}));
7776
}
7877
}
79-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
80-
return errors;
8178

79+
return errors;
8280
case 'video':
8381
return validateObject({
8482
key,
@@ -113,12 +111,11 @@ export default function validateSource(options: ValidationOptions): Array<Valida
113111

114112
function getSourceTypeValues(styleSpec: StyleReference) {
115113
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
116-
return styleSpec.source.reduce((memo, source) => {
114+
return styleSpec.source.reduce((memo: string[], source: string) => {
117115
const sourceType = styleSpec[source];
118116
if (sourceType.type.type === 'enum') {
119117
memo = memo.concat(Object.keys(sourceType.type.values));
120118
}
121-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
122119
return memo;
123120
}, []);
124121
}
@@ -130,7 +127,7 @@ function validatePromoteId({
130127
if (getType(value) === 'string') {
131128
return validateString({key, value});
132129
} else if (Array.isArray(value)) {
133-
const errors = [];
130+
const errors: ValidationError[] = [];
134131
const unbundledValue = deepUnbundle(value);
135132
const expression = createExpression(unbundledValue);
136133
if (expression.result === 'error') {
@@ -146,14 +143,13 @@ function validatePromoteId({
146143
errors.push(new ValidationError(`${key}`, null, 'promoteId expression should be only feature dependent'));
147144
}
148145

149-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
150146
return errors;
151147
} else {
152-
const errors = [];
148+
const errors: ValidationError[] = [];
153149
for (const prop in value) {
154150
errors.push(...validatePromoteId({key: `${key}.${prop}`, value: value[prop]}));
155151
}
156-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
152+
157153
return errors;
158154
}
159155
}

src/style-spec/validate_mapbox_api_supported.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,19 @@ function getSourceCount(source: SourceSpecification): number {
3636

3737
function getAllowedKeyErrors(obj: object, keys: string[], path?: string | null): Array<ValidationError> {
3838
const allowed = new Set(keys);
39-
const errors = [];
39+
const errors: ValidationError[] = [];
4040
Object.keys(obj).forEach(k => {
4141
if (!allowed.has(k)) {
4242
const prop = path ? `${path}.${k}` : null;
4343
errors.push(new ValidationError(prop, obj[k], `Unsupported property "${k}"`));
4444
}
4545
});
46-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
4746
return errors;
4847
}
4948

5049
const acceptedSourceTypes = new Set<SourceSpecification['type']>(['vector', 'raster', 'raster-dem', 'raster-array', 'model', 'batched-model']);
5150
function getSourceErrors(source: SourceSpecification, i: number): Array<ValidationError> {
52-
const errors = [];
51+
const errors: ValidationError[] = [];
5352

5453
/*
5554
* Inlined sources are not supported by the Mapbox Styles API, so only
@@ -76,16 +75,14 @@ function getSourceErrors(source: SourceSpecification, i: number): Array<Validati
7675
errors.push(new ValidationError(`sources[${i}].url`, (source as {url?: string}).url, 'Expected a valid Mapbox tileset url'));
7776
}
7877

79-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
8078
return errors;
8179
}
8280

8381
function getMaxSourcesErrors(sourcesCount: number): Array<ValidationError> {
84-
const errors = [];
82+
const errors: ValidationError[] = [];
8583
if (sourcesCount > MAX_SOURCES_IN_STYLE) {
8684
errors.push(new ValidationError('sources', null, `Styles must contain ${MAX_SOURCES_IN_STYLE} or fewer sources`));
8785
}
88-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
8986
return errors;
9087
}
9188

src/types/tilejson.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type TileJSON = {
1717
minzoom?: number;
1818
maxzoom?: number;
1919
bounds?: [number, number, number, number];
20+
extra_bounds?: Array<[number, number, number, number]>;
2021
center?: [number, number, number];
2122
vector_layers?: Array<SourceVectorLayer>;
2223
raster_layers?: Array<SourceRasterLayer>;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy