@@ -7,23 +7,12 @@ import { getElement } from '../../lib/elements';
77import { getSources } from '../../lib/sources' ;
88import { getMapPreviewers } from '../../lib/previewers' ;
99import MapLibrePreviewer from '../../lib/previewers/maplibre' ;
10+ import MapLibreTheme from '../../lib/themes/maplibre' ;
1011
1112// Register PMTiles protocol
1213const protocol = new PMTilesProtocol ( ) ;
1314maplibregl . addProtocol ( 'pmtiles' , protocol . tile ) ;
1415
15- // Web Awesome's palette tokens are documented inline (e.g. "#0a3a1d /* oklch(...) */").
16- // Browsers are supposed to strip CSS comments before exposing a custom property's computed
17- // value, but Safari has a bug where the comment survives, which MapLibre then rejects as an
18- // invalid color. Strip it defensively here so we don't depend on browser-specific behavior.
19- const readColorProperty = ( el : Element , property : string ) : string => {
20- return window
21- . getComputedStyle ( el )
22- . getPropertyValue ( property )
23- . replace ( / \/ \* .* ?\* \/ / g, '' )
24- . trim ( ) ;
25- } ;
26-
2716@Component ( {
2817 tag : 'ogm-map' ,
2918 styleUrl : 'ogm-map.css' ,
@@ -33,15 +22,16 @@ export class OgmMap {
3322 @Element ( ) el : HTMLElement ;
3423 @Prop ( ) record : OgmRecord ;
3524 @Prop ( ) theme : 'light' | 'dark' ;
36- @Prop ( ) previewOpacity : number = 100 ;
3725 @Prop ( ) padding : number = 0 ;
3826 @Event ( ) mapIdle : EventEmitter < void > ;
3927 @Event ( ) mapLoading : EventEmitter < void > ;
4028
4129 // MapLibre map instance and popup instance for feature info display
4230 protected map : maplibregl . Map ;
31+ protected mapTheme : MapLibreTheme ;
4332 protected popup : maplibregl . Popup | undefined = undefined ;
4433 protected attributesEl : HTMLOgmAttributesElement ;
34+ protected hoveredFeature : maplibregl . MapGeoJSONFeature | undefined = undefined ;
4535
4636 // Container element reference for fullscreen
4737 protected containerEl : HTMLElement ;
@@ -51,11 +41,12 @@ export class OgmMap {
5141
5242 // Set up the mapLibre map and event bindings on load
5343 componentDidLoad ( ) {
44+ this . mapTheme = new MapLibreTheme ( this . el ) ;
5445 this . map = new maplibregl . Map ( {
5546 container : getElement ( this . el , '#map' ) ,
5647 attributionControl : false ,
5748 cooperativeGestures : true ,
58- style : this . baseMapStyle ,
49+ style : this . mapTheme . getBaseMapStyle ( ) ,
5950 center : [ 0 , 0 ] ,
6051 zoom : 2 ,
6152 minZoom : 2 ,
@@ -69,18 +60,8 @@ export class OgmMap {
6960
7061 // View as a globe with atmosphere effects
7162 this . map . on ( 'style.load' , ( ) => {
72- this . map . setProjection ( {
73- type : 'globe' ,
74- } ) ;
75- this . map . setSky ( {
76- 'sky-color' : '#199EF3' ,
77- 'sky-horizon-blend' : 0.5 ,
78- 'horizon-color' : '#ffffff' ,
79- 'horizon-fog-blend' : 0.5 ,
80- 'fog-color' : '#0000ff' ,
81- 'fog-ground-blend' : 0.5 ,
82- 'atmosphere-blend' : [ 'interpolate' , [ 'linear' ] , [ 'zoom' ] , 0 , 1 , 10 , 1 , 12 , 0 ] ,
83- } ) ;
63+ this . map . setProjection ( { type : 'globe' } ) ;
64+ this . map . setSky ( this . mapTheme . getSkyStyle ( ) ) ;
8465 } ) ;
8566
8667 // Keep attributes outside Stencil render pipeline so that MapLibre can
@@ -135,31 +116,20 @@ export class OgmMap {
135116 this . clearFeatureSelection ( ) ;
136117 this . destroyPopup ( ) ;
137118
138- // Collect previewers to remove after the new record is previewed
139- const oldPreviewers = [ ] ;
140- while ( this . previewers . length ) oldPreviewers . push ( this . previewers . pop ( ) as MapLibrePreviewer ) ;
119+ // Clear existing previews
120+ while ( this . previewers . length ) await this . previewers . pop ( ) ?. clearPreview ( ) ;
141121
142122 // Populate the new previewers and render them
143123 const sources = getSources ( record ) ;
144- const previewOptions = {
145- fillColor : this . fillColor ,
146- fillHighlightColor : this . fillHighlightColor ,
147- fillInvalidColor : this . fillInvalidColor ,
148- lineColor : this . lineColor ,
149- lineHighlightColor : this . lineHighlightColor ,
150- opacity : this . previewOpacity / 100 ,
151- } ;
152- const newPreviewers = await getMapPreviewers ( sources , this . map , previewOptions ) ;
153- while ( newPreviewers . length ) this . previewers . push ( newPreviewers . pop ( ) as MapLibrePreviewer ) ;
154- for ( const previewer of this . previewers ) await previewer . preview ( ) ;
124+ for ( const previewer of await getMapPreviewers ( sources , this . map , this . mapTheme . getStyle ( ) ) ) {
125+ this . previewers . push ( previewer ) ;
126+ await previewer . preview ( ) ;
127+ }
155128
156129 // Fit to bounds from the record
157130 const bounds = record . getBounds ( ) ;
158131 if ( bounds ) await this . fitMapBounds ( bounds ) ;
159132
160- // Remove old previewers after the new ones are rendered
161- for ( const previewer of oldPreviewers ) await previewer . clearPreview ( ) ;
162-
163133 // Emit map idle event to signal that the map is ready
164134 this . mapIdle . emit ( ) ;
165135 }
@@ -186,8 +156,13 @@ export class OgmMap {
186156 // Use the crosshair cursor if there's something to inspect
187157 protected handleHover ( event : maplibregl . MapMouseEvent ) {
188158 const features = this . map . queryRenderedFeatures ( event . point , { layers : this . previewLayers } ) ;
189- if ( features . length > 0 ) this . map . getCanvas ( ) . style . cursor = 'crosshair' ;
190- else this . map . getCanvas ( ) . style . cursor = '' ;
159+ if ( features . length > 0 ) {
160+ this . map . getCanvas ( ) . style . cursor = 'crosshair' ;
161+ this . hoverFeature ( features [ 0 ] ) ;
162+ } else {
163+ this . map . getCanvas ( ) . style . cursor = '' ;
164+ this . clearHoveredFeature ( ) ;
165+ }
191166 }
192167
193168 // Show the attributes popup on click
@@ -219,13 +194,28 @@ export class OgmMap {
219194 this . attributesEl . features . forEach ( feature => {
220195 this . map . setFeatureState ( { source : feature . source , id : feature . id , sourceLayer : feature . sourceLayer } , { selected : false } ) ;
221196 } ) ;
197+ this . attributesEl . features = [ ] ;
222198 }
223199
224200 // Set styling of a single feature to selected state
225201 protected selectFeature ( feature : maplibregl . MapGeoJSONFeature ) {
226202 this . map . setFeatureState ( { source : feature . source , id : feature . id , sourceLayer : feature . sourceLayer } , { selected : true } ) ;
227203 }
228204
205+ // Set styling of a single feature to hovered state
206+ protected hoverFeature ( feature : maplibregl . MapGeoJSONFeature ) {
207+ this . clearHoveredFeature ( ) ;
208+ this . hoveredFeature = feature ;
209+ this . map . setFeatureState ( { source : feature . source , id : feature . id , sourceLayer : feature . sourceLayer } , { hover : true } ) ;
210+ }
211+
212+ protected clearHoveredFeature ( ) {
213+ if ( this . hoveredFeature ) {
214+ this . map . setFeatureState ( { source : this . hoveredFeature . source , id : this . hoveredFeature . id , sourceLayer : this . hoveredFeature . sourceLayer } , { hover : false } ) ;
215+ this . hoveredFeature = undefined ;
216+ }
217+ }
218+
229219 // Create a new popup and set its content and location
230220 protected createPopup ( location : maplibregl . LngLatLike ) {
231221 this . popup = new maplibregl . Popup ( { maxWidth : 'none' , closeButton : false } ) . setDOMContent ( this . attributesEl ) . setLngLat ( location ) . addTo ( this . map ) ;
@@ -244,41 +234,6 @@ export class OgmMap {
244234 return this . previewers . flatMap ( previewer => previewer . layerIds ) ;
245235 }
246236
247- // Base map style based on the theme
248- protected get baseMapStyle ( ) {
249- 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' ;
250- }
251-
252- // Fill colors for vector data, based on the theme
253- protected get fillColor ( ) {
254- return readColorProperty ( this . el , '--wa-color-brand-50' ) ;
255- }
256-
257- // Fill color for vector data when selected, based on the theme
258- protected get fillHighlightColor ( ) {
259- return readColorProperty ( this . el , '--wa-color-success-50' ) ;
260- }
261-
262- // Fill color for vector data when missing/invalid, based on the theme
263- protected get fillInvalidColor ( ) {
264- return readColorProperty ( this . el , '--wa-color-warning-50' ) ;
265- }
266-
267- // Line/stroke color for vector data, based on the theme
268- protected get lineColor ( ) {
269- return readColorProperty ( this . el , '--wa-color-brand-80' ) ;
270- }
271-
272- // Line/stroke color for vector data when selected, based on the theme
273- protected get lineHighlightColor ( ) {
274- return readColorProperty ( this . el , '--wa-color-success-80' ) ;
275- }
276-
277- // Line/stroke color for vector data when missing/invalid, based on the theme
278- protected get lineInvalidColor ( ) {
279- return readColorProperty ( this . el , '--wa-color-warning-80' ) ;
280- }
281-
282237 render ( ) {
283238 return < div id = "map" class = { `wa-${ this . theme } ` } > </ div > ;
284239 }
0 commit comments