11import { 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' ;
44import { BBox } from '@linzjs/geojson' ;
5- import maplibre , { LngLatBounds } from 'maplibre-gl' ;
5+ import * as maplibre from 'maplibre-gl' ;
66import { onMapLoaded } from './components/map.js' ;
77import { Config } from './config.js' ;
88import { locationTransform } from './tile.matrix.js' ;
99import { MapOptionType } from './url.js' ;
1010
1111const 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