@@ -3,52 +3,128 @@ import { buffer } from "@turf/buffer"
33import { lineString } from "@turf/helpers"
44import { length } from "@turf/length"
55import { draw , select } from 'maplibre/edit'
6- import { getFeature , getFeatures , layers } from 'maplibre/layers/layers'
6+ import { getFeature , layers } from 'maplibre/layers/layers'
7+ import { Layer } from 'maplibre/layers/layer'
78import { map , mapProperties , removeStyleLayers } from 'maplibre/map'
89import { defaultLineWidth , featureColor , initializeClusterStyles , initializeViewStyles , labelFont , setSource , styles } from 'maplibre/styles/styles'
910
10- export function initializeGeoJSONLayers ( id = null ) {
11- // console.log('Initializing geojson layers')
12- let initLayers = layers . filter ( l => l . type === 'geojson' && l . show !== false )
13- if ( id ) { initLayers = initLayers . filter ( l => l . id === id ) }
11+ // Instance cache for GeoJSONLayer objects
12+ const instances = new Map ( )
1413
15- initLayers . forEach ( ( layer ) => {
16- initializeViewStyles ( 'geojson-source-' + layer . id , ! ! layer . heatmap )
17- if ( ! ! layer . cluster ) { initializeClusterStyles ( 'geojson-source-' + layer . id , null ) }
14+ function getInstance ( id ) {
15+ if ( ! instances . has ( id ) ) {
16+ instances . set ( id , new GeoJSONLayer ( layers . find ( l => l . id === id ) ) )
17+ }
18+ return instances . get ( id )
19+ }
1820
19- initializeKmMarkerStyles ( layer . id )
20- renderGeoJSONLayer ( layer . id )
21- } )
21+ export class GeoJSONLayer extends Layer {
22+ get kmMarkerSourceId ( ) {
23+ return `km-marker-source-${ this . id } `
24+ }
2225
23- map . fire ( 'geojson.load' , { detail : { message : 'geojson source + styles loaded' } } )
24- }
26+ initialize ( ) {
27+ initializeViewStyles ( this . sourceId , ! ! this . layer . heatmap )
28+ if ( this . layer . cluster ) { initializeClusterStyles ( this . sourceId , null ) }
29+ this . initializeKmMarkerStyles ( )
30+ this . render ( )
31+ }
2532
26- export function renderGeoJSONLayers ( resetDraw = true ) {
27- layers . filter ( l => l . type === 'geojson' ) . forEach ( ( layer ) => {
28- renderGeoJSONLayer ( layer . id , resetDraw )
29- } )
30- }
33+ render ( resetDraw = true ) {
34+ console . log ( "Redraw: Setting source data for geojson layer" , this . layer )
35+ this . ensureFeaturePropertyIds ( )
36+ this . renderKmMarkers ( )
37+ const extrusionLines = this . renderExtrusionLines ( )
38+ const geojson = { type : 'FeatureCollection' , features : this . layer . geojson . features . concat ( extrusionLines ) }
39+ map . getSource ( this . sourceId ) . setData ( geojson , false )
40+ this . resetDrawFeatures ( resetDraw )
41+ }
3142
32- export function renderGeoJSONLayer ( id , resetDraw = true ) {
33- let layer = layers . find ( l => l . id === id )
34- console . log ( "Redraw: Setting source data for geojson layer" , layer )
35-
36- // this + `promoteId: 'id'` is a workaround for the maplibre limitation:
37- // https://github.com/mapbox/mapbox-gl-js/issues/2716
38- // because to highlight a feature we need the id,
39- // and in the style layers it only accepts mumeric ids in the id field initially
40- // TODO: only needed once, not each render
41- layer . geojson . features . forEach ( ( feature ) => { feature . properties . id = feature . id } )
42- renderKmMarkersLayer ( id )
43- // - For LineStrings with a 'fill-extrusion-height', add a polygon to render extrusion
44- let extrusionLines = renderExtrusionLines ( )
45- let geojson = { type : 'FeatureCollection' , features : layer . geojson . features . concat ( extrusionLines ) }
46-
47- map . getSource ( layer . type + '-source-' + layer . id ) . setData ( geojson , false )
48-
49- // draw has its own style layers based on editStyles
50- if ( draw ) {
51- if ( resetDraw ) {
43+ renderKmMarkers ( ) {
44+ let kmMarkerFeatures = [ ]
45+ this . layer . geojson . features . filter ( feature => ( feature . geometry . type === 'LineString' &&
46+ feature . properties [ 'show-km-markers' ] &&
47+ feature . geometry . coordinates . length >= 2 ) ) . forEach ( ( f , index ) => {
48+
49+ const line = lineString ( f . geometry . coordinates )
50+ const distance = length ( line , { units : 'kilometers' } )
51+ let interval = 1
52+ for ( let i = 0 ; i < Math . ceil ( distance ) + interval ; i += interval ) {
53+ const point = along ( line , i , { units : 'kilometers' } )
54+ point . properties [ 'marker-color' ] = f . properties [ 'stroke' ] || featureColor
55+ point . properties [ 'marker-size' ] = 11
56+ point . properties [ 'marker-opacity' ] = 1
57+ point . properties [ 'km' ] = i
58+
59+ if ( i >= Math . ceil ( distance ) ) {
60+ point . properties [ 'marker-size' ] = 14
61+ point . properties [ 'km' ] = Math . round ( distance )
62+ if ( Math . ceil ( distance ) < 100 ) {
63+ point . properties [ 'km' ] = Math . round ( distance * 10 ) / 10
64+ }
65+ point . properties [ 'km-marker-numbers-end' ] = 1
66+ point . properties [ 'sort-key' ] = 2 + index
67+ }
68+ kmMarkerFeatures . push ( point )
69+ }
70+ } )
71+
72+ const markerFeatures = { type : 'FeatureCollection' , features : kmMarkerFeatures }
73+ map . getSource ( this . kmMarkerSourceId ) . setData ( markerFeatures )
74+ }
75+
76+ initializeKmMarkerStyles ( ) {
77+ removeStyleLayers ( this . kmMarkerSourceId )
78+ this . kmMarkerStyles ( ) . forEach ( style => {
79+ style = setSource ( style , this . kmMarkerSourceId )
80+ map . addLayer ( style )
81+ } )
82+ }
83+
84+ kmMarkerStyles ( ) {
85+ let styleLayers = [ ]
86+
87+ styleLayers . push ( makePointsLayer ( 2 , 11 ) )
88+ styleLayers . push ( makeNumbersLayer ( 2 , 11 ) )
89+ styleLayers . push ( makePointsLayer ( 5 , 10 , 11 ) )
90+ styleLayers . push ( makeNumbersLayer ( 5 , 10 , 11 ) )
91+ styleLayers . push ( makePointsLayer ( 10 , 9 , 10 ) )
92+ styleLayers . push ( makeNumbersLayer ( 10 , 9 , 10 ) )
93+ styleLayers . push ( makePointsLayer ( 25 , 8 , 9 ) )
94+ styleLayers . push ( makeNumbersLayer ( 25 , 8 , 9 ) )
95+ styleLayers . push ( makePointsLayer ( 50 , 7 , 8 ) )
96+ styleLayers . push ( makeNumbersLayer ( 50 , 7 , 8 ) )
97+ styleLayers . push ( makePointsLayer ( 100 , 5 , 7 ) )
98+ styleLayers . push ( makeNumbersLayer ( 100 , 5 , 7 ) )
99+
100+ const base = { ...styles ( ) [ 'points-layer' ] }
101+ styleLayers . push ( {
102+ ...base ,
103+ id : `km-marker-points-end` ,
104+ filter : [ "==" , [ "get" , "km-marker-numbers-end" ] , 1 ]
105+ } )
106+ styleLayers . push ( {
107+ id : `km-marker-numbers-end` ,
108+ type : 'symbol' ,
109+ filter : [ "==" , [ "get" , "km-marker-numbers-end" ] , 1 ] ,
110+ layout : {
111+ 'text-allow-overlap' : true ,
112+ 'text-field' : [ 'get' , 'km' ] ,
113+ 'text-size' : 12 ,
114+ 'text-font' : labelFont ,
115+ 'text-justify' : 'center' ,
116+ 'text-anchor' : 'center'
117+ } ,
118+ paint : {
119+ 'text-color' : '#ffffff'
120+ }
121+ } )
122+
123+ return styleLayers
124+ }
125+
126+ resetDrawFeatures ( resetDraw ) {
127+ if ( draw && resetDraw ) {
52128 // This has a performance drawback over draw.set(), but some feature
53129 // properties don't get updated otherwise
54130 // API: https://github.com/mapbox/mapbox-gl-draw/blob/main/docs/API.md
@@ -59,52 +135,58 @@ export function renderGeoJSONLayer(id, resetDraw = true) {
59135 let feature = getFeature ( featureId , "geojson" )
60136 if ( feature ) {
61137 draw . add ( feature )
62- // if we're in edit mode, re-select feature
63138 select ( feature )
64139 }
65140 } )
66141 }
67142 }
143+
144+ renderExtrusionLines ( ) {
145+ if ( mapProperties . terrain ) { return [ ] }
146+
147+ let extrusionLines = this . layer . geojson . features . filter ( feature => (
148+ feature . geometry . type === 'LineString' &&
149+ feature . properties [ 'fill-extrusion-height' ] &&
150+ feature . geometry . coordinates . length !== 1
151+ ) )
152+
153+ return extrusionLines . map ( feature => {
154+ const width = feature . properties [ 'fill-extrusion-width' ] || feature . properties [ 'stroke-width' ] || defaultLineWidth
155+ const extrusionLine = buffer ( feature , width , { units : 'meters' } )
156+ extrusionLine . properties = { ...feature . properties }
157+ if ( ! extrusionLine . properties [ 'fill-extrusion-color' ] && feature . properties . stroke ) {
158+ extrusionLine . properties [ 'fill-extrusion-color' ] = feature . properties . stroke
159+ }
160+ extrusionLine . properties [ 'stroke-width' ] = 0
161+ extrusionLine . properties [ 'stroke-opacity' ] = 0
162+ extrusionLine . properties [ 'fill-opacity' ] = 0
163+ return extrusionLine
164+ } )
165+ }
68166}
69167
70- export function renderKmMarkersLayer ( id ) {
71- let layer = layers . find ( l => l . id === id )
168+ // Backward-compatible wrapper exports
72169
73- let kmMarkerFeatures = [ ]
74- layer . geojson . features . filter ( feature => ( feature . geometry . type === 'LineString' &&
75- feature . properties [ 'show-km-markers' ] &&
76- feature . geometry . coordinates . length >= 2 ) ) . forEach ( ( f , index ) => {
170+ export function initializeGeoJSONLayers ( id = null ) {
171+ instances . clear ( )
172+ let initLayers = layers . filter ( l => l . type === 'geojson' && l . show !== false )
173+ if ( id ) { initLayers = initLayers . filter ( l => l . id === id ) }
77174
78- const line = lineString ( f . geometry . coordinates )
79- const distance = length ( line , { units : 'kilometers' } )
80- // Create markers at useful intervals
81- let interval = 1
82- for ( let i = 0 ; i < Math . ceil ( distance ) + interval ; i += interval ) {
83- // Get point at current kilometer
84- const point = along ( line , i , { units : 'kilometers' } )
85- point . properties [ 'marker-color' ] = f . properties [ 'stroke' ] || featureColor
86- point . properties [ 'marker-size' ] = 11
87- point . properties [ 'marker-opacity' ] = 1
88- point . properties [ 'km' ] = i
89-
90- if ( i >= Math . ceil ( distance ) ) {
91- point . properties [ 'marker-size' ] = 14
92- point . properties [ 'km' ] = Math . round ( distance )
93- if ( Math . ceil ( distance ) < 100 ) {
94- point . properties [ 'km' ] = Math . round ( distance * 10 ) / 10
95- }
96- point . properties [ 'km-marker-numbers-end' ] = 1
97- point . properties [ 'sort-key' ] = 2 + index
98- }
99- kmMarkerFeatures . push ( point )
100- }
175+ initLayers . forEach ( ( layer ) => {
176+ getInstance ( layer . id ) . initialize ( )
101177 } )
102178
103- let markerFeatures = {
104- type : 'FeatureCollection' ,
105- features : kmMarkerFeatures
106- }
107- map . getSource ( 'km-marker-source-' + id ) . setData ( markerFeatures )
179+ map . fire ( 'geojson.load' , { detail : { message : 'geojson source + styles loaded' } } )
180+ }
181+
182+ export function renderGeoJSONLayers ( resetDraw = true ) {
183+ layers . filter ( l => l . type === 'geojson' ) . forEach ( ( layer ) => {
184+ renderGeoJSONLayer ( layer . id , resetDraw )
185+ } )
186+ }
187+
188+ export function renderGeoJSONLayer ( id , resetDraw = true ) {
189+ getInstance ( id ) . render ( resetDraw )
108190}
109191
110192function makePointsLayer ( divisor , minzoom , maxzoom = 24 ) {
@@ -139,84 +221,3 @@ function makeNumbersLayer(divisor, minzoom, maxzoom=24) {
139221 }
140222}
141223
142- export function kmMarkerStyles ( _id ) {
143- let layers = [ ]
144- const base = { ...styles ( ) [ 'points-layer' ] }
145-
146- layers . push ( makePointsLayer ( 2 , 11 ) )
147- layers . push ( makeNumbersLayer ( 2 , 11 ) )
148-
149- layers . push ( makePointsLayer ( 5 , 10 , 11 ) )
150- layers . push ( makeNumbersLayer ( 5 , 10 , 11 ) )
151-
152- layers . push ( makePointsLayer ( 10 , 9 , 10 ) )
153- layers . push ( makeNumbersLayer ( 10 , 9 , 10 ) )
154-
155- layers . push ( makePointsLayer ( 25 , 8 , 9 ) )
156- layers . push ( makeNumbersLayer ( 25 , 8 , 9 ) )
157-
158- layers . push ( makePointsLayer ( 50 , 7 , 8 ) )
159- layers . push ( makeNumbersLayer ( 50 , 7 , 8 ) )
160-
161- layers . push ( makePointsLayer ( 100 , 5 , 7 ) )
162- layers . push ( makeNumbersLayer ( 100 , 5 , 7 ) )
163-
164- // end point has different style
165- layers . push ( {
166- ...base ,
167- id : `km-marker-points-end` ,
168- filter : [ "==" , [ "get" , "km-marker-numbers-end" ] , 1 ]
169- } )
170- layers . push ( {
171- id : `km-marker-numbers-end` ,
172- type : 'symbol' ,
173- filter : [ "==" , [ "get" , "km-marker-numbers-end" ] , 1 ] ,
174- layout : {
175- 'text-allow-overlap' : true ,
176- 'text-field' : [ 'get' , 'km' ] ,
177- 'text-size' : 12 ,
178- 'text-font' : labelFont ,
179- 'text-justify' : 'center' ,
180- 'text-anchor' : 'center'
181- } ,
182- paint : {
183- 'text-color' : '#ffffff'
184- }
185- } )
186-
187- return layers
188- }
189-
190- export function initializeKmMarkerStyles ( id ) {
191- removeStyleLayers ( 'km-marker-source-' + id )
192- kmMarkerStyles ( id ) . forEach ( style => {
193- style = setSource ( style , 'km-marker-source-' + id )
194- map . addLayer ( style )
195- } )
196- }
197-
198- function renderExtrusionLines ( ) {
199- // Disable extrusionlines on 3D terrain, it does not work
200- if ( mapProperties . terrain ) { return [ ] }
201-
202- let extrusionLines = getFeatures ( 'geojson' ) . filter ( feature => (
203- feature . geometry . type === 'LineString' &&
204- feature . properties [ 'fill-extrusion-height' ] &&
205- feature . geometry . coordinates . length !== 1 // don't break line animation
206- ) )
207-
208- extrusionLines = extrusionLines . map ( feature => {
209- const width = feature . properties [ 'fill-extrusion-width' ] || feature . properties [ 'stroke-width' ] || defaultLineWidth
210- const extrusionLine = buffer ( feature , width , { units : 'meters' } )
211- // clone properties hash, else we're writing into the original feature's properties
212- extrusionLine . properties = { ...feature . properties }
213- if ( ! extrusionLine . properties [ 'fill-extrusion-color' ] && feature . properties . stroke ) {
214- extrusionLine . properties [ 'fill-extrusion-color' ] = feature . properties . stroke
215- }
216- extrusionLine . properties [ 'stroke-width' ] = 0
217- extrusionLine . properties [ 'stroke-opacity' ] = 0
218- extrusionLine . properties [ 'fill-opacity' ] = 0
219- return extrusionLine
220- } )
221- return extrusionLines
222- }
0 commit comments