Skip to content

Commit ef93543

Browse files
Wentao-KuangAndrew Jacombs
andauthored
feat(landing): Update the daterange slider to years button. (#2764)
* wip: dump of changes to implement clamping * Fix the attribution now loading and filter attribution by date range * Filter attribution by year * Wip * Update the daterange slider * Add some top padding * Remove unused logic * Fix broken attribution * Fix the ignored layers * Remove bathmery ingore * Load the attribution for the screenshot. --------- Co-authored-by: Andrew Jacombs <ajacombs@linz.govt.nz>
1 parent d7f0b50 commit ef93543

File tree

12 files changed

+182
-159
lines changed

12 files changed

+182
-159
lines changed

packages/attribution/src/attribution.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,15 @@ export class Attribution {
163163
* @param extent a bounding box in the projection supplied to the constructor
164164
* @param zoom the zoom level the extent is viewed at
165165
*/
166-
filter(params: AttributionFilter): AttributionCollection[] {
166+
filter(params: AttributionFilter): AttributionBounds[] {
167167
params.zoom = Math.round(params.zoom);
168168

169-
const filtered: AttributionCollection[] = [];
169+
const filtered: AttributionBounds[] = [];
170170
const { attributions } = this;
171171
if (attributions == null) return filtered;
172172
for (const attr of attributions) {
173173
if (this.isIgnored != null && this.isIgnored(attr)) continue;
174-
if (attr.intersects(params)) filtered.push(attr.collection);
174+
if (attr.intersects(params)) filtered.push(attr);
175175
}
176176

177177
return filtered;
@@ -185,16 +185,16 @@ export class Attribution {
185185
*
186186
* @param list the filtered list of attributions
187187
*/
188-
renderList(list: AttributionCollection[]): string {
188+
renderList(list: AttributionBounds[]): string {
189189
if (list.length === 0) return '';
190-
let result = escapeHtml(list[0].title);
190+
let result = escapeHtml(list[0].collection.title);
191191
if (list.length > 1) {
192192
if (list.length === 2) {
193-
result += ` & ${escapeHtml(list[1].title)}`;
193+
result += ` & ${escapeHtml(list[1].collection.title)}`;
194194
} else {
195-
let [minYear, maxYear] = getYears(list[1]);
195+
let [minYear, maxYear] = getYears(list[1].collection);
196196
for (let i = 1; i < list.length; ++i) {
197-
const [a, b] = getYears(list[i]);
197+
const [a, b] = getYears(list[i].collection);
198198
if (a !== -1 && (minYear === -1 || a < minYear)) minYear = a;
199199
if (b !== -1 && (maxYear === -1 || b > maxYear)) maxYear = b;
200200
}

packages/geo/src/stac/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface StacExtent {
4242
spatial: {
4343
bbox: [number, number, number, number][];
4444
};
45-
temporal?: {
45+
temporal: {
4646
interval: [string, string][];
4747
};
4848
}

packages/lambda-tiler/src/routes/attribution.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,16 @@ async function tileSetAttribution(
9393
const im = imagery.get(imgId);
9494
if (im == null) continue;
9595
const title = im.title;
96+
const years = extractYearRangeFromTitle(im.title) ?? extractYearRangeFromName(im.name);
97+
if (years == null) continue;
98+
const interval = yearRangeToInterval(years);
9699

97100
const bbox = proj.boundsToWgs84BoundingBox(im.bounds).map(roundNumber) as BBox;
98101

99-
const extent: StacExtent = { spatial: { bbox: [bbox] } };
102+
const extent: StacExtent = {
103+
spatial: { bbox: [bbox] },
104+
temporal: { interval: [[interval[0].toISOString(), interval[1].toISOString()]] },
105+
};
100106

101107
const item: AttributionItem = {
102108
type: 'Feature',
@@ -110,16 +116,12 @@ async function tileSetAttribution(
110116
properties: {
111117
title,
112118
category: im.category,
119+
datetime: null,
120+
start_datetime: interval[0].toISOString(),
121+
end_datetime: interval[1].toISOString(),
113122
},
114123
};
115-
const years = extractYearRangeFromTitle(im.title) ?? extractYearRangeFromName(im.name);
116-
if (years) {
117-
const interval = yearRangeToInterval(years);
118-
extent.temporal = { interval: [[interval[0].toISOString(), interval[1].toISOString()]] };
119-
item.properties.datetime = null;
120-
item.properties.start_datetime = interval[0].toISOString();
121-
item.properties.end_datetime = interval[1].toISOString();
122-
}
124+
123125
items.push(item);
124126

125127
const minZoom = layer.disabled ? 32 : layer.minZoom;

packages/landing/src/__tests__/map.config.test.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,25 +94,17 @@ o.spec('WindowUrl', () => {
9494
o('should extract dateRange', () => {
9595
mc.updateFromUrl('?i=abc123&p=nztm2000&d=true&debug=yes&date[before]=1975-12-31T23:59:59.999Z');
9696
o(mc.filter.date.before).equals('1975-12-31T23:59:59.999Z');
97-
o(mc.filter.date.after).equals(undefined);
9897
mc.updateFromUrl('?date[after]=1952-01-01T00:00:00.000Z&date[before]=1975-12-31T23:59:59.999Z');
9998
o(mc.filter.date.before).equals('1975-12-31T23:59:59.999Z');
100-
o(mc.filter.date.after).equals('1952-01-01T00:00:00.000Z');
10199
mc.updateFromUrl('?date%5Bafter%5D=1952-01-01T00%3A00%3A00.000Z&date%5Bbefore%5D=1975-12-31T23%3A59%3A59.999Z');
102100
o(mc.filter.date.before).equals('1975-12-31T23:59:59.999Z');
103-
o(mc.filter.date.after).equals('1952-01-01T00:00:00.000Z');
104101
});
105102

106103
o('should resolve the invalid dateRange', () => {
107104
mc.updateFromUrl('?i=abc123&p=nztm2000&d=true&debug=yes&date[before]=1949-12-31T23:59:59.999Z');
108105
o(mc.filter.date.before).equals(undefined);
109-
o(mc.filter.date.after).equals(undefined);
110-
mc.updateFromUrl('?date[after]=1952-01-01T00:00:00.000Z&date[before]=2099-12-31T23:59:59.999Z');
106+
mc.updateFromUrl('?date[before]=2099-12-31T23:59:59.999Z');
111107
o(mc.filter.date.before).equals(undefined);
112-
o(mc.filter.date.after).equals('1952-01-01T00:00:00.000Z');
113-
mc.updateFromUrl('?date%5Bafter%5D=1988-01-01T00%3A00%3A00.000Z&date%5Bbefore%5D=1987-12-31T23%3A59%3A59.999Z');
114-
o(mc.filter.date.before).equals(undefined);
115-
o(mc.filter.date.after).equals(undefined);
116108
});
117109

118110
o('should convert to a url', () => {

packages/landing/src/attribution.ts

Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,86 @@
11
import { Attribution } from '@basemaps/attribution';
2-
import { AttributionBounds } from '@basemaps/attribution/build/attribution';
3-
import { AttributionCollection, GoogleTms, Stac, TileMatrixSet } from '@basemaps/geo';
2+
import { AttributionBounds } from '@basemaps/attribution/build/attribution.js';
3+
import { GoogleTms, Stac, TileMatrixSet } from '@basemaps/geo';
44
import { BBox } from '@linzjs/geojson';
5-
import maplibre, { LngLatBounds } from 'maplibre-gl';
5+
import * as maplibre from 'maplibre-gl';
66
import { onMapLoaded } from './components/map.js';
77
import { Config } from './config.js';
88
import { locationTransform } from './tile.matrix.js';
99
import { MapOptionType } from './url.js';
1010

1111
const Copyright = ${Stac.License} LINZ`;
1212

13-
/** Cache the loading of attribution */
14-
export const Attributions: Map<string, Promise<Attribution | null>> = new Map();
15-
/** Rendering process needs synch access */
16-
const AttributionSync: Map<string, Attribution> = new Map();
13+
export class MapAttributionState {
14+
/** Cache the loading of attribution */
15+
_attrs: Map<string, Promise<Attribution | null>> = new Map();
16+
/** Rendering process needs synch access */
17+
_attrsSync: Map<string, Attribution> = new Map();
18+
19+
/** Load a attribution from a url, return a cached copy if we have one */
20+
getCurrentAttribution(): Promise<Attribution | null> {
21+
const cacheKey = Config.map.layerKeyTms;
22+
let attrs = this._attrs.get(cacheKey);
23+
if (attrs == null) {
24+
attrs = Attribution.load(Config.map.toTileUrl(MapOptionType.Attribution)).catch(() => null);
25+
this._attrs.set(cacheKey, attrs);
26+
attrs.then((a) => {
27+
if (a == null) return;
28+
a.isIgnored = this.isIgnored;
29+
this._attrsSync.set(Config.map.layerKeyTms, a);
30+
});
31+
}
32+
return attrs;
33+
}
34+
35+
/** Filter the attribution to the map bounding box */
36+
filterAttributionToMap(attr: Attribution, map: maplibregl.Map): AttributionBounds[] {
37+
let zoom = Math.round(map.getZoom() ?? 0);
38+
// Note that Mapbox rendering 512×512 image tiles are offset by one zoom level compared to 256×256 tiles.
39+
// For example, 512×512 tiles at zoom level 4 are equivalent to 256×256 tiles at zoom level 5.
40+
zoom += 1;
41+
const extent = MapAttributionState.mapboxBoundToBbox(map.getBounds(), zoom, Config.map.tileMatrix);
42+
return attr.filter({
43+
extent,
44+
zoom: zoom,
45+
dateBefore: Config.map.filter.date.before,
46+
});
47+
}
48+
49+
getAttributionByYear(attribution: AttributionBounds[]): Map<number, AttributionBounds[]> {
50+
const attrsByYear = new Map<number, AttributionBounds[]>();
51+
for (const a of attribution) {
52+
if (!a.startDate || !a.endDate) continue;
53+
const startYear = Number(a.startDate.slice(0, 4));
54+
const endYear = Number(a.endDate.slice(0, 4));
55+
for (let year = startYear; year <= endYear; year++) {
56+
const attrs = attrsByYear.get(year) ?? [];
57+
attrs.push(a);
58+
attrsByYear.set(year, attrs);
59+
}
60+
}
61+
return attrsByYear;
62+
}
63+
64+
/**
65+
* Covert Mapbox Bounds to tileMatrix BBox
66+
*/
67+
static mapboxBoundToBbox(bounds: maplibre.LngLatBounds, zoom: number, tileMatrix: TileMatrixSet): BBox {
68+
const swLocation = { lon: bounds.getWest(), lat: bounds.getSouth(), zoom: zoom };
69+
const neLocation = { lon: bounds.getEast(), lat: bounds.getNorth(), zoom: zoom };
70+
const swCoord = locationTransform(swLocation, GoogleTms, tileMatrix);
71+
const neCoord = locationTransform(neLocation, GoogleTms, tileMatrix);
72+
const bbox: BBox = [swCoord.lon, swCoord.lat, neCoord.lon, neCoord.lat];
73+
return bbox;
74+
}
75+
76+
// Ignore DEMS from the attribution list
77+
isIgnored = (attr: AttributionBounds): boolean => {
78+
const title = attr.collection.title.toLowerCase();
79+
return title.startsWith('geographx') || title.includes(' dem ');
80+
};
81+
}
82+
83+
export const MapAttrState = new MapAttributionState();
1784

1885
/**
1986
* Handles displaying attributions for the OpenLayers interface
@@ -27,9 +94,8 @@ export class MapAttribution {
2794
private _raf = 0;
2895

2996
attributionHtml = '';
30-
bounds: LngLatBounds = new LngLatBounds([0, 0, 0, 0]);
97+
bounds: maplibre.LngLatBounds = new maplibre.LngLatBounds([0, 0, 0, 0]);
3198
zoom = -1;
32-
filteredRecords: AttributionCollection[] = [];
3399
attributionControl?: maplibregl.AttributionControl | null;
34100

35101
constructor(map: maplibregl.Map) {
@@ -61,25 +127,8 @@ export class MapAttribution {
61127
updateAttribution = (): void => {
62128
// Vector layers currently have no attribution
63129
if (Config.map.isVector) return this.vectorAttribution();
64-
const cacheKey = Config.map.layerKeyTms;
65-
let loader = Attributions.get(cacheKey);
66-
if (loader == null) {
67-
loader = Attribution.load(Config.map.toTileUrl(MapOptionType.Attribution)).catch(() => null);
68-
Attributions.set(cacheKey, loader);
69-
loader.then((attr) => {
70-
if (attr == null) return;
71-
attr.isIgnored = this.isIgnored;
72-
AttributionSync.set(cacheKey, attr);
73-
this.scheduleRender();
74-
});
75-
}
76-
this.scheduleRender();
77-
};
78-
79-
// Ignore DEMS from the attribution list
80-
isIgnored = (attr: AttributionBounds): boolean => {
81-
const title = attr.collection.title.toLowerCase();
82-
return title.startsWith('geographx') || title.includes(' dem ');
130+
const loader = MapAttrState.getCurrentAttribution();
131+
loader.then(() => this.scheduleRender());
83132
};
84133

85134
/**
@@ -109,23 +158,10 @@ export class MapAttribution {
109158
*/
110159
renderAttribution = (): void => {
111160
this._raf = 0;
112-
const attr = AttributionSync.get(Config.map.layerKeyTms);
161+
const attr = MapAttrState._attrsSync.get(Config.map.layerKeyTms);
113162
if (attr == null) return this.removeAttribution();
114-
this.zoom = Math.round(this.map.getZoom() ?? 0);
115-
this.bounds = this.map.getBounds();
116-
117-
// Note that Mapbox rendering 512×512 image tiles are offset by one zoom level compared to 256×256 tiles.
118-
// For example, 512×512 tiles at zoom level 4 are equivalent to 256×256 tiles at zoom level 5.
119-
this.zoom += 1;
120-
121-
const extent = this.mapboxBoundToBbox(this.bounds, Config.map.tileMatrix);
122-
const filtered = attr.filter({
123-
extent,
124-
zoom: this.zoom,
125-
dateAfter: Config.map.filter.date.after,
126-
dateBefore: Config.map.filter.date.before,
127-
});
128-
const filteredLayerIds = filtered.map((x) => x.id).join('_');
163+
const filtered = MapAttrState.filterAttributionToMap(attr, this.map);
164+
const filteredLayerIds = filtered.map((x) => x.collection.id).join('_');
129165
Config.map.emit('visibleLayers', filteredLayerIds);
130166

131167
let attributionHTML = attr.renderList(filtered);
@@ -135,27 +171,13 @@ export class MapAttribution {
135171
attributionHTML = Copyright + ' - ' + attributionHTML;
136172
}
137173
if (attributionHTML !== this.attributionHtml) {
138-
const customAttribution = (this.attributionHtml = attributionHTML);
174+
this.attributionHtml = attributionHTML;
139175
this.removeAttribution();
140-
this.attributionControl = new maplibre.AttributionControl({ compact: false, customAttribution });
176+
this.attributionControl = new maplibre.AttributionControl({ compact: false, customAttribution: attributionHTML });
141177
this.map.addControl(this.attributionControl, 'bottom-right');
142178
}
143-
144-
this.filteredRecords = filtered;
145179
};
146180

147-
/**
148-
* Covert Mapbox Bounds to tileMatrix BBox
149-
*/
150-
mapboxBoundToBbox(bounds: LngLatBounds, tileMatrix: TileMatrixSet): BBox {
151-
const swLocation = { lon: bounds.getWest(), lat: bounds.getSouth(), zoom: this.zoom };
152-
const neLocation = { lon: bounds.getEast(), lat: bounds.getNorth(), zoom: this.zoom };
153-
const swCoord = locationTransform(swLocation, GoogleTms, tileMatrix);
154-
const neCoord = locationTransform(neLocation, GoogleTms, tileMatrix);
155-
const bbox: BBox = [swCoord.lon, swCoord.lat, neCoord.lon, neCoord.lat];
156-
return bbox;
157-
}
158-
159181
/**
160182
* Add attribution for vector map
161183
*/

0 commit comments

Comments
 (0)