Skip to content

Commit 19f6d4e

Browse files
committed
Refactor sources and previewers
1 parent f43bd58 commit 19f6d4e

29 files changed

Lines changed: 2852 additions & 633 deletions

package-lock.json

Lines changed: 1781 additions & 188 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@stencil/core": "^4.43.4",
4242
"@stencil/playwright": "^0.4.3",
4343
"@stencil/vitest": "^1.11.6",
44+
"@types/mapbox__geojson-extent": "^1.0.3",
4445
"@types/node": "^22.13.5",
4546
"@types/openseadragon": "^5.0.2",
4647
"@types/terraformer__wkt": "^2.0.3",
@@ -52,15 +53,16 @@
5253
},
5354
"license": "MIT",
5455
"dependencies": {
55-
"@deck.gl/core": "^9.2.10",
56-
"@deck.gl/geo-layers": "^9.2.11",
57-
"@deck.gl/layers": "^9.2.10",
58-
"@deck.gl/mapbox": "^9.2.10",
59-
"@deck.gl/mesh-layers": "^9.2.11",
60-
"@developmentseed/deck.gl-geotiff": "^0.4.0",
56+
"@deck.gl/core": "^9.3.1",
57+
"@deck.gl/geo-layers": "^9.3.1",
58+
"@deck.gl/layers": "^9.3.1",
59+
"@deck.gl/mapbox": "^9.3.1",
60+
"@deck.gl/mesh-layers": "^9.3.1",
61+
"@developmentseed/deck.gl-geotiff": "^0.7.0",
6162
"@iiif/presentation-2": "^1.0.4",
6263
"@iiif/presentation-3": "^2.2.3",
63-
"@luma.gl/core": "^9.2.6",
64+
"@luma.gl/core": "^9.3.3",
65+
"@mapbox/geojson-extent": "^1.0.1",
6466
"@shoelace-style/shoelace": "^2.20.1",
6567
"@terraformer/wkt": "^2.2.1",
6668
"d3-scale": "^4.0.2",

src/components/ogm-map/ogm-map.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ canvas {
1818
stroke: white;
1919
}
2020

21+
.maplibregl-canvas {
22+
background-color: black;
23+
}
24+
2125
.maplibregl-ctrl.maplibregl-ctrl-group {
2226
filter: invert(1);
2327
}

src/components/ogm-map/ogm-map.tsx

Lines changed: 50 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { Component, Element, Prop, State, h, Watch, Method, Event, EventEmitter } from '@stencil/core';
2-
import maplibregl from 'maplibre-gl';
3-
import { getPreviewSource, getPreviewLayers, getBoundsPreviewSource, getBoundsPreviewLayers, recordDeckGLCOGLayer } from '../../lib/sources';
4-
import { MapboxOverlay as DeckOverlay } from '@deck.gl/mapbox';
1+
import { Component, Element, Prop, h, Watch, Method, Event, EventEmitter } from '@stencil/core';
52
import { Protocol as PMTilesProtocol } from 'pmtiles';
3+
import maplibregl from 'maplibre-gl';
4+
import type { EaseToOptions } from 'maplibre-gl';
65

7-
import { getElement } from '../../lib/elements';
86
import type { OgmRecord } from '../../lib/record';
9-
import type { AddLayerObject, EaseToOptions } from 'maplibre-gl';
10-
import type { AddSourceObject } from '../../lib/sources';
7+
import { getElement } from '../../lib/elements';
8+
import { getSources } from '../../lib/sources';
9+
import { getMapPreviewers } from '../../lib/previewers';
10+
import Previewer from '../../lib/previewers/previewer';
1111

1212
// Register PMTiles protocol
1313
const protocol = new PMTilesProtocol();
@@ -27,21 +27,15 @@ export class OgmMap {
2727
@Event() mapIdle: EventEmitter<void>;
2828
@Event() mapLoading: EventEmitter<void>;
2929

30-
// Track sources and layers for bounds and preview
31-
@State() boundsSource: AddSourceObject | undefined;
32-
@State() boundsLayers: AddLayerObject[] = [];
33-
@State() previewSource: AddSourceObject | undefined;
34-
@State() previewLayers: AddLayerObject[] = [];
35-
3630
// MapLibre map instance
3731
private map: maplibregl.Map;
3832

39-
// Deck.gl overlay for rendering rasters
40-
private deckOverlay: DeckOverlay;
41-
4233
// Container element reference for fullscreen
4334
private containerEl: HTMLElement;
4435

36+
// Previewers for the currently previewed sources
37+
private previewers: Previewer[] = [];
38+
4539
// Set up the mapLibre map and event bindings on load
4640
componentDidLoad() {
4741
this.map = new maplibregl.Map({
@@ -50,13 +44,29 @@ export class OgmMap {
5044
cooperativeGestures: true,
5145
style: this.baseMapStyle,
5246
center: [0, 0],
53-
zoom: 1,
47+
zoom: 2,
48+
minZoom: 2,
5449
});
5550
this.getContainer();
56-
this.deckOverlay = new DeckOverlay({ interleaved: true });
5751
this.addControls();
5852
this.map.on('idle', () => this.mapIdle.emit());
5953
this.map.on('load', () => this.previewRecord(this.record));
54+
55+
// View as a globe with atmosphere effects
56+
this.map.on('style.load', () => {
57+
this.map.setProjection({
58+
type: 'globe',
59+
});
60+
this.map.setSky({
61+
'sky-color': '#199EF3',
62+
'sky-horizon-blend': 0.5,
63+
'horizon-color': '#ffffff',
64+
'horizon-fog-blend': 0.5,
65+
'fog-color': '#0000ff',
66+
'fog-ground-blend': 0.5,
67+
'atmosphere-blend': ['interpolate', ['linear'], ['zoom'], 0, 1, 10, 1, 12, 0],
68+
});
69+
});
6070
}
6171

6272
// Find the container element for the map (used for fullscreen control)
@@ -86,66 +96,41 @@ export class OgmMap {
8696
},
8797
}),
8898
);
89-
this.map.addControl(
90-
new maplibregl.AttributionControl({
91-
compact: true,
92-
}),
93-
);
94-
this.map.addControl(this.deckOverlay);
9599
}
96100

97101
@Watch('record')
98102
async previewRecord(record: OgmRecord) {
99-
// Clear the map of previous layers and sources; if nothing new, bail out
100-
if (this.record) this.clearMap();
101103
if (!record) return;
102-
103-
// Indicate loading
104104
this.mapLoading.emit();
105105

106-
// Get the bounds
107-
this.boundsSource = getBoundsPreviewSource(this.record);
108-
109-
// If a COG, generate a Deck.gl COG layer
110-
if (this.record.references.cogUrl) {
111-
const deckLayer = recordDeckGLCOGLayer(this.record);
112-
this.deckOverlay.setProps({
113-
layers: [deckLayer],
114-
});
115-
}
106+
// Clear any existing preview layers and sources
107+
await Promise.all(this.previewers.map(previewer => previewer.clearPreview()));
108+
this.previewers = [];
116109

117-
// Otherwise...
118-
else if (this.boundsSource) {
119-
// Add the sources for the record's geometry and the data preview
120-
this.previewSource = await getPreviewSource(this.record);
110+
// Create sources and previewers using references
111+
const sources = getSources(record);
112+
const previewOptions = { fillColor: this.fillColor, lineColor: this.lineColor, opacity: this.previewOpacity / 100 };
113+
const previewers = await getMapPreviewers(sources, this.map, previewOptions);
121114

122-
// If the record is not restricted and has a source, add preview layers
123-
if (this.previewSource && !this.record.restricted) {
124-
this.previewLayers = await getPreviewLayers(this.record, this.previewSource);
125-
}
126-
127-
// Otherwise if we have bounds, just add the bounds preview layers
128-
else {
129-
this.boundsLayers = getBoundsPreviewLayers(this.record);
130-
}
115+
// Preview each source
116+
for (const previewer of previewers) {
117+
this.previewers.push(previewer);
118+
await previewer.preview();
131119
}
132120

133-
// Fit the map to the record's bounding box, if we can
134-
const bounds = this.record.getBounds();
135-
if (bounds) this.fitMapBounds(bounds);
136-
}
121+
// Fit to bounds from the record
122+
const bounds = record.getBounds();
123+
if (bounds) await this.fitMapBounds(bounds);
137124

138-
// Remove all layers and sources from the map
139-
clearMap() {
140-
this.boundsLayers = [];
141-
this.previewLayers = [];
142-
this.boundsSource = undefined;
143-
this.previewSource = undefined;
125+
this.mapIdle.emit();
144126
}
145127

146128
// Fit the map to the provided bounds
147-
fitMapBounds(bounds: maplibregl.LngLatBoundsLike) {
148-
this.map.fitBounds(bounds, { padding: 40 });
129+
async fitMapBounds(bounds: maplibregl.LngLatBoundsLike) {
130+
return new Promise<void>(resolve => {
131+
this.map.once('moveend', () => resolve());
132+
this.map.fitBounds(bounds, { padding: this.padding });
133+
});
149134
}
150135

151136
// When padding is changed, move the map over to make room for the sidebar
@@ -160,94 +145,19 @@ export class OgmMap {
160145
return await this.map.easeTo(options);
161146
}
162147

163-
// When new bounds/preview sources are set, swap them out for old ones
164-
@Watch('boundsSource')
165-
@Watch('previewSource')
166-
updateSource(source: AddSourceObject, oldSource: AddSourceObject) {
167-
if (!source && oldSource) return this.removeSource(oldSource.id);
168-
if (source) return this.addSource(source);
169-
}
170-
171-
// When new bounds/preview layers are set, swap them out for old ones
172-
@Watch('boundsLayers')
173-
@Watch('previewLayers')
174-
updateLayers(layers: AddLayerObject[], oldLayers: AddLayerObject[]) {
175-
if (!layers || layers.length === 0) return oldLayers.forEach(layer => this.removeLayer(layer.id));
176-
layers.forEach(layer => this.addLayer(layer));
177-
}
178-
179-
// Listen for opacity changes and adjust the preview layers
180-
@Watch('previewOpacity')
181-
updatePreviewOpacity(opacity: number) {
182-
this.previewLayers.forEach(layer => this.setLayerOpacity(layer.id, opacity));
183-
}
184-
185-
// Add a source to the map
186-
private addSource(source: AddSourceObject) {
187-
return this.map.addSource(source.id, source.source);
188-
}
189-
190-
// Remove a source from the map by ID
191-
private removeSource(id: string) {
192-
return this.map.removeSource(id);
193-
}
194-
195-
// Add a layer to the map and style it based on the theme
196-
private addLayer(layer: AddLayerObject) {
197-
this.map.addLayer(layer);
198-
this.styleLayer(layer.id);
199-
}
200-
201-
// Remove a layer from the map by ID
202-
private removeLayer(id: string) {
203-
return this.map.removeLayer(id);
204-
}
205-
206-
// Style a layer based on the theme
207-
private styleLayer(id: string) {
208-
const layer = this.map.getLayer(id);
209-
if (!layer) return;
210-
211-
if (layer.type === 'fill') {
212-
this.map.setPaintProperty(id, 'fill-color', this.fillColor);
213-
this.map.setPaintProperty(id, 'fill-outline-color', this.lineColor);
214-
} else if (layer.type === 'line') {
215-
this.map.setPaintProperty(id, 'line-color', this.lineColor);
216-
} else if (layer.type === 'circle') {
217-
this.map.setPaintProperty(id, 'circle-color', this.fillColor);
218-
this.map.setPaintProperty(id, 'circle-stroke-color', this.lineColor);
219-
}
220-
}
221-
222-
// Set the opacity of a layer
223-
private setLayerOpacity(id: string, opacity: number) {
224-
const layer = this.map.getLayer(id);
225-
if (!layer) return;
226-
227-
if (layer.type === 'raster') {
228-
this.map.setPaintProperty(layer.id, 'raster-opacity', opacity / 100);
229-
} else if (layer.type === 'fill') {
230-
this.map.setPaintProperty(layer.id, 'fill-opacity', opacity / 100);
231-
} else if (layer.type === 'line') {
232-
this.map.setPaintProperty(layer.id, 'line-opacity', opacity / 100);
233-
} else if (layer.type === 'circle') {
234-
this.map.setPaintProperty(layer.id, 'circle-opacity', opacity / 100);
235-
}
236-
}
237-
238148
// Base map style based on the theme
239149
private get baseMapStyle() {
240150
return this.theme === 'dark' ? 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json' : 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
241151
}
242152

243153
// Fill colors for vector data based on the theme
244154
private get fillColor() {
245-
return window.getComputedStyle(this.el).getPropertyValue('--sl-color-primary-200');
155+
return window.getComputedStyle(this.el).getPropertyValue('--sl-color-primary-500');
246156
}
247157

248158
// Line/stroke color for vector data based on the theme
249159
private get lineColor() {
250-
return window.getComputedStyle(this.el).getPropertyValue('--sl-color-primary-500');
160+
return window.getComputedStyle(this.el).getPropertyValue('--sl-color-neutral-900');
251161
}
252162

253163
render() {

src/index.html

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ <h1>OpenGeoMetadata Viewer Preview</h1>
7676
<p>Click a button to load a record.</p>
7777
<div class="inputs">
7878
<div class="buttons">
79-
<button data-record-url="https://raw.githubusercontent.com/OpenGeoMetadata/edu.stanford.purl/refs/heads/main/metadata-aardvark/vx/572/wx/7854/geoblacklight.json">
80-
Clowns (Points, WMS)
81-
</button>
82-
<button data-record-url="https://raw.githubusercontent.com/OpenGeoMetadata/edu.stanford.purl/refs/heads/main/metadata-aardvark/cz/128/vq/0535/geoblacklight.json">
83-
Uganda (Polygons, WMS)
79+
<button>TODO (Points, WMS)</button>
80+
<button>TODO (Polygons, WMS)</button>
81+
<button
82+
data-record-url="https://raw.githubusercontent.com/OpenGeoMetadata/edu.stanford.purl/b74664cbb36fb985fe2ebee8d0d6d9657f06ada1/metadata-aardvark/cg/357/zz/0321/geoblacklight.json"
83+
>
84+
Contours (Line, WMS, Restricted)
8485
</button>
8586
<button data-record-url="https://raw.githubusercontent.com/geoblacklight/geoblacklight/refs/heads/main/spec/fixtures/solr_documents/public_cog_princeton.json">
8687
Tibet (Scanned Map, COG)
@@ -125,11 +126,6 @@ <h1>OpenGeoMetadata Viewer Preview</h1>
125126
<button data-record-url="https://gist.githubusercontent.com/thatbudakguy/dcf881807aea33739a8cbb8fcefa42d0/raw/67b80876bdd17c21d654024bc77a1e4ece8a7fb6/hopkins.json">
126127
Hopkins (Index Map, Points)
127128
</button>
128-
<button
129-
data-record-url="https://raw.githubusercontent.com/OpenGeoMetadata/edu.stanford.purl/b74664cbb36fb985fe2ebee8d0d6d9657f06ada1/metadata-aardvark/cg/357/zz/0321/geoblacklight.json"
130-
>
131-
Contours (Line, WMS, Restricted)
132-
</button>
133129
<button
134130
data-record-url="https://raw.githubusercontent.com/geoblacklight/geoblacklight/9f7dbb4c36dc0b8e88fb9be5282d7b1663858829/spec/fixtures/solr_documents/complex-geom.json"
135131
>

0 commit comments

Comments
 (0)