Skip to content

Commit bddac82

Browse files
committed
feat(landing): store the maps bounds to provide a better bounding box intersection
1 parent 4bc33ff commit bddac82

File tree

5 files changed

+93
-37
lines changed

5 files changed

+93
-37
lines changed

packages/landing/src/attribution.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { Attribution } from '@basemaps/attribution';
22
import { AttributionBounds } from '@basemaps/attribution/build/attribution.js';
3-
import { GoogleTms, Stac, TileMatrixSet } from '@basemaps/geo';
4-
import { BBox } from '@linzjs/geojson';
3+
import { Stac } from '@basemaps/geo';
54
import * as maplibre from 'maplibre-gl';
65

76
import { onMapLoaded } from './components/map.js';
87
import { Config } from './config.js';
9-
import { locationTransform } from './tile.matrix.js';
8+
import { mapToBoundingBox } from './tile.matrix.js';
109
import { MapOptionType } from './url.js';
1110

1211
const Copyright = ${Stac.License} LINZ`;
@@ -39,7 +38,7 @@ export class MapAttributionState {
3938
// Note that Mapbox rendering 512×512 image tiles are offset by one zoom level compared to 256×256 tiles.
4039
// For example, 512×512 tiles at zoom level 4 are equivalent to 256×256 tiles at zoom level 5.
4140
zoom += 1;
42-
const extent = MapAttributionState.mapboxBoundToBbox(map.getBounds(), zoom, Config.map.tileMatrix);
41+
const extent = mapToBoundingBox(map, zoom, Config.map.tileMatrix);
4342
return attr.filter({
4443
extent,
4544
zoom: zoom,
@@ -62,17 +61,6 @@ export class MapAttributionState {
6261
return attrsByYear;
6362
}
6463

65-
/**
66-
* Covert Mapbox Bounds to tileMatrix BBox
67-
*/
68-
static mapboxBoundToBbox(bounds: maplibre.LngLatBounds, zoom: number, tileMatrix: TileMatrixSet): BBox {
69-
const swLocation = { lon: bounds.getWest(), lat: bounds.getSouth(), zoom: zoom };
70-
const neLocation = { lon: bounds.getEast(), lat: bounds.getNorth(), zoom: zoom };
71-
const swCoord = locationTransform(swLocation, GoogleTms, tileMatrix);
72-
const neCoord = locationTransform(neLocation, GoogleTms, tileMatrix);
73-
const bbox: BBox = [swCoord.lon, swCoord.lat, neCoord.lon, neCoord.lat];
74-
return bbox;
75-
}
7664

7765
// Ignore DEMS from the attribution list
7866
isIgnored = (attr: AttributionBounds): boolean => {

packages/landing/src/components/layer.switcher.dropdown.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Bounds, GoogleTms, Projection } from '@basemaps/geo';
2+
import { intersection, MultiPolygon, truncate, Wgs84 } from '@linzjs/geojson';
23
import { ChangeEventHandler, Component, ReactNode } from 'react';
34
import Select from 'react-select';
45

@@ -187,11 +188,11 @@ export class LayerSwitcherDropdown extends Component<unknown, LayerSwitcherDropd
187188
const filterToExtent = this.state.filterToExtent;
188189

189190
const location = Config.map.location;
190-
const loc3857 = Projection.get(GoogleTms).fromWgs84([location.lon, location.lat]);
191-
const tileSize = GoogleTms.tileSize * GoogleTms.pixelScale(Math.floor(location.zoom)); // width of 1 tile
192-
// Assume the current bounds are 3x3 tiles, todo would be more correct to use the map's bounding box but we dont have access to it here
193-
const bounds = new Bounds(loc3857[0], loc3857[1], 1, 1).scaleFromCenter(3 * tileSize, 3 * tileSize);
191+
if (location == null || location.extent == null) return { options: [], current: null, hidden, total };
194192

193+
const mapExtent = Wgs84.bboxToMultiPolygon(location.extent);
194+
195+
truncate;
195196
let current: Option | null = null;
196197

197198
for (const layer of this.state.layers.values()) {
@@ -201,7 +202,7 @@ export class LayerSwitcherDropdown extends Component<unknown, LayerSwitcherDropd
201202
// Always show the current layer
202203
if (layer.id !== currentLayer) {
203204
// Limit all other layers to the extent if requested
204-
if (filterToExtent && !doesLayerIntersect(bounds, layer)) {
205+
if (filterToExtent && !doesLayerIntersect(mapExtent, layer)) {
205206
hidden++;
206207
continue;
207208
}
@@ -228,25 +229,22 @@ export class LayerSwitcherDropdown extends Component<unknown, LayerSwitcherDropd
228229
}
229230

230231
/**
231-
* Determine if the bounds in EPSG:3857 intersects the provided layer
232-
*
233-
* TODO: It would be good to then use a more comprehensive intersection if the bounding box intersects,
234-
* there are complex polygons inside the attribution layer that could be used but they do not have all
235-
* the polygons
232+
* Determine if the polygon intersects the provided layer
236233
*
237-
* @param bounds Bounding box in EPSG:3857
234+
* @param bounds polygon to check
238235
* @param layer layer to check
239236
* @returns true if it intersects, false otherwise
240237
*/
241-
function doesLayerIntersect(bounds: Bounds, layer: LayerInfo): boolean {
238+
function doesLayerIntersect(bounds: MultiPolygon, layer: LayerInfo): boolean {
242239
// No layer information assume it intersects
243240
if (layer.lowerRight == null || layer.upperLeft == null) return true;
244241

245-
// It is somewhat easier to find intersections in EPSG:3857
246-
const ul3857 = Projection.get(GoogleTms).fromWgs84(layer.upperLeft);
247-
const lr3857 = Projection.get(GoogleTms).fromWgs84(layer.lowerRight);
248-
249-
const layerBounds = Bounds.fromBbox([ul3857[0], ul3857[1], lr3857[0], lr3857[1]]);
242+
const poly = Wgs84.bboxToMultiPolygon([
243+
layer.lowerRight[0],
244+
layer.upperLeft[1],
245+
layer.upperLeft[0],
246+
layer.lowerRight[1],
247+
]);
250248

251-
return bounds.intersects(layerBounds);
249+
return intersection(bounds, poly).length > 0;
252250
}

packages/landing/src/config.map.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { LngLatBoundsLike } from 'maplibre-gl';
1414

1515
import { ConfigDebug, DebugDefaults, DebugState } from './config.debug.js';
1616
import { Config } from './config.js';
17-
import { locationTransform } from './tile.matrix.js';
17+
import { locationTransform, mapToBoundingBox } from './tile.matrix.js';
1818
import { ensureBase58, MapLocation, MapOptionType, WindowUrl } from './url.js';
1919

2020
/** Default center point if none provided */
@@ -191,6 +191,9 @@ export class MapConfig extends Emitter<MapConfigEvents> {
191191
const pitch = map.getPitch();
192192
if (bearing !== 0) location.bearing = bearing;
193193
if (pitch !== 0) location.pitch = pitch;
194+
195+
const bounds = mapToBoundingBox(map, zoom, Config.map.tileMatrix);
196+
location.extent = bounds;
194197
return location;
195198
}
196199

@@ -204,7 +207,8 @@ export class MapConfig extends Emitter<MapConfigEvents> {
204207
l.lon === this.location.lon &&
205208
l.zoom === this.location.zoom &&
206209
l.bearing === this.location.bearing &&
207-
l.pitch === this.location.pitch
210+
l.pitch === this.location.pitch &&
211+
sameExtent(l, this.location)
208212
) {
209213
return;
210214
}
@@ -213,6 +217,7 @@ export class MapConfig extends Emitter<MapConfigEvents> {
213217
this.location.zoom = l.zoom;
214218
this.location.bearing = l.bearing;
215219
this.location.pitch = l.pitch;
220+
this.location.extent = l.extent;
216221
this.emit('location', this.location);
217222
this.emit('change');
218223
}
@@ -274,6 +279,23 @@ export class MapConfig extends Emitter<MapConfigEvents> {
274279
}
275280
}
276281

282+
/**
283+
* Are two location's extents the same
284+
* @param a location A
285+
* @param b location B
286+
* @returns true if the extents are exactly the same false otherwise
287+
*/
288+
function sameExtent(a: MapLocation, b: MapLocation): boolean {
289+
if (a.extent == null && b.extent == null) return true;
290+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
291+
for (let i = 0; i < a.length; i++) {
292+
if (a[i] !== b[i]) return false;
293+
}
294+
return true;
295+
}
296+
return false;
297+
}
298+
277299
export interface LayerInfo {
278300
/** Layer id to use when fetching tiles */
279301
id: string;

packages/landing/src/tile.matrix.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { GoogleTms, Nztm2000QuadTms, Nztm2000Tms, Projection, TileMatrixSet } from '@basemaps/geo';
1+
import { BoundingBox, Bounds, GoogleTms, Nztm2000QuadTms, Nztm2000Tms, Projection, TileMatrixSet } from '@basemaps/geo';
2+
import { BBox } from '@linzjs/geojson';
23
import { StyleSpecification } from 'maplibre-gl';
34

45
import { Config } from './config.js';
@@ -76,6 +77,25 @@ export function locationTransform(
7677
return { lon: Math.round(lonLat[0] * 1e8) / 1e8, lat: Math.round(lonLat[1] * 1e8) / 1e8, zoom: location.zoom };
7778
}
7879

80+
/**
81+
* Covert map Bounds to tileMatrix BBox
82+
*/
83+
export function mapToBoundingBox(map: maplibregl.Map, zoom: number, tileMatrix: TileMatrixSet): BBox {
84+
const bounds = map.getBounds();
85+
const swLocation = { lon: bounds.getWest(), lat: bounds.getSouth(), zoom: zoom };
86+
const neLocation = { lon: bounds.getEast(), lat: bounds.getNorth(), zoom: zoom };
87+
const swCoord = locationTransform(swLocation, GoogleTms, tileMatrix);
88+
const neCoord = locationTransform(neLocation, GoogleTms, tileMatrix);
89+
// Truncate all coordiantes to 8 DP (~1mm)
90+
const bbox: BBox = [
91+
Math.round(swCoord.lon * 1e8) / 1e8,
92+
Math.round(swCoord.lat * 1e8) / 1e8,
93+
Math.round(neCoord.lon * 1e8) / 1e8,
94+
Math.round(neCoord.lat * 1e8) / 1e8,
95+
];
96+
return bbox;
97+
}
98+
7999
/**
80100
* Project a geojson object into the target tile matrix with use with maplibre
81101
*

packages/landing/src/url.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { base58, isBase58 } from '@basemaps/config/build/base58.js';
22
import { GoogleTms, TileMatrixSet } from '@basemaps/geo';
33
import { toQueryString } from '@basemaps/shared/build/url.js';
4+
import { BBox } from '@linzjs/geojson';
45

56
import { LabelsDisabledLayers } from './components/map.label.js';
67
import { Config } from './config.js';
@@ -12,9 +13,37 @@ export interface LonLat {
1213
}
1314

1415
export interface MapLocation extends LonLat {
16+
/**
17+
* Current map zoom
18+
*
19+
* @example 11.94
20+
*/
1521
zoom: number;
22+
23+
/**
24+
* Current map bearing in degrees
25+
*
26+
* Between -180 and 180
27+
*
28+
* @default 0
29+
* @example -43.7
30+
*/
1631
bearing?: number;
32+
33+
/**
34+
* Current map pitch in degrees
35+
*
36+
* Between 0 and {@link LocationUrl.PitchMaxDegrees}
37+
*
38+
* @default 0
39+
* @example 12.7
40+
*/
1741
pitch?: number;
42+
43+
/**
44+
* Optional bounding box of the map's view
45+
*/
46+
extent?: BBox;
1847
}
1948

2049
export const enum MapOptionType {
@@ -33,7 +62,6 @@ export interface TileUrlParams {
3362
style?: string | null;
3463
config?: string | null;
3564
pipeline?: string | null;
36-
date?: FilterDate;
3765
imageFormat?: string | null;
3866
terrain?: string | null;
3967
labels?: boolean | null;

0 commit comments

Comments
 (0)