@@ -19,36 +19,90 @@ import {
1919import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js' ;
2020
2121let scene , renderer , camera , controls , tiles , gui ;
22+ let layersFolder = null ;
23+ let colorsFolder = null ;
24+
25+ // --- Source Presets ---
26+ // Each provider has different layer names and styling conventions
27+ const SOURCE_PRESETS = {
28+ PMTiles : {
29+ name : 'Protomaps PMTiles' ,
30+ url : 'https://demo-bucket.protomaps.com/v4.pmtiles' ,
31+ requiresApiKey : false ,
32+ // Protomaps layer names (v4 basemap)
33+ layers : {
34+ water : { name : 'water' , enabled : true , color : '#4a90d9' } ,
35+ earth : { name : 'earth' , enabled : true , color : '#f2efe9' } ,
36+ landuse : { name : 'landuse' , enabled : false , color : '#e8e4d8' } ,
37+ landcover : { name : 'landcover' , enabled : false , color : '#d4e8c2' } ,
38+ natural : { name : 'natural' , enabled : false , color : '#c8d9af' } ,
39+ roads : { name : 'roads' , enabled : false , color : '#ffffff' } ,
40+ buildings : { name : 'buildings' , enabled : false , color : '#d9d0c9' } ,
41+ transit : { name : 'transit' , enabled : false , color : '#888888' } ,
42+ boundaries : { name : 'boundaries' , enabled : true , color : '#ff6b6b' } ,
43+ places : { name : 'places' , enabled : true , color : '#333333' } ,
44+ pois : { name : 'pois' , enabled : false , color : '#7d4e24' } ,
45+ } ,
46+ defaultColor : '#cccccc'
47+ } ,
48+ MVT : {
49+ name : 'Mapbox Streets' ,
50+ urlTemplate : 'https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.vector.pbf?access_token=' ,
51+ requiresApiKey : true ,
52+ // Mapbox Streets v8 layer names
53+ layers : {
54+ water : { name : 'water' , enabled : true , color : '#4a90d9' } ,
55+ waterway : { name : 'waterway' , enabled : true , color : '#4a90d9' } ,
56+ landuse : { name : 'landuse' , enabled : false , color : '#e8e4d8' } ,
57+ landuse_overlay : { name : 'landuse_overlay' , enabled : false , color : '#d4e8c2' } ,
58+ park : { name : 'park' , enabled : false , color : '#c8d9af' } ,
59+ natural_label : { name : 'natural_label' , enabled : false , color : '#5d8a3e' } ,
60+ road : { name : 'road' , enabled : false , color : '#ffffff' } ,
61+ building : { name : 'building' , enabled : false , color : '#d9d0c9' } ,
62+ transit : { name : 'transit' , enabled : false , color : '#888888' } ,
63+ boundaries : { name : 'admin' , enabled : true , color : '#ff6b6b' } ,
64+ place_label : { name : 'place_label' , enabled : true , color : '#333333' } ,
65+ poi_label : { name : 'poi_label' , enabled : false , color : '#7d4e24' } ,
66+ } ,
67+ defaultColor : '#cccccc'
68+ }
69+ } ;
2270
23- // const apiKey = localStorage.getItem( 'mapbox_key' ) || prompt( 'Enter Mapbox API Key' );
24- // if ( apiKey ) localStorage.setItem ( 'mapbox_key', apiKey ) ;
71+ // Mapbox API key - stored in localStorage for convenience
72+ let apiKey = localStorage . getItem ( 'mapbox_key' ) || '' ;
2573
26- // --- Dynamic Filter State ---
74+ // --- Application State ---
2775const state = {
28- pluginType : 'Mesh' ,
29- // Layer Toggles
30- showWater : true ,
31- showBuildings : false ,
32- showRoads : false ,
33- showTransit : false ,
34- showLanduse : false ,
35- showAdmin : true ,
36- showLabels : true ,
37- // Property Filters
38- maxAdminLevel : 1 ,
76+ sourceType : 'PMTiles' ,
77+ renderMode : 'Texture' ,
78+ // Layer visibility (populated from preset)
79+ layers : { } ,
80+ // Layer colors (populated from preset)
81+ colors : { } ,
82+ // Filter settings
3983 maxSymbolRank : 3 ,
40- colors : {
41- water : '#201f20' ,
42- landuse : '#caedc1' ,
43- building : '#eeeeee' ,
44- road : '#444444' ,
45- boundaries : '#ff0000' ,
46- poi : '#ffcc00' ,
47- default : '#222222'
48- }
4984} ;
5085
51- // const MVT_URL = `https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.vector.pbf?access_token=${apiKey}`;
86+ // Initialize state from default preset
87+ function initStateFromPreset ( presetName ) {
88+
89+ const preset = SOURCE_PRESETS [ presetName ] ;
90+ state . layers = { } ;
91+ state . colors = { } ;
92+
93+ for ( const key in preset . layers ) {
94+
95+ const layer = preset . layers [ key ] ;
96+ state . layers [ key ] = layer . enabled ;
97+ state . colors [ layer . name ] = layer . color ;
98+
99+ }
100+
101+ state . colors . default = preset . defaultColor ;
102+
103+ }
104+
105+ initStateFromPreset ( state . sourceType ) ;
52106
53107init ( ) ;
54108setupGUI ( ) ;
@@ -79,36 +133,31 @@ function init() {
79133
80134}
81135
82- function mvtFilter ( feature , layerName ) {
136+ function createFilter ( preset ) {
83137
84- const props = feature . properties ;
138+ const layerNameToKey = { } ;
139+ for ( const key in preset . layers ) {
85140
86- // 1. Layer Visibility Checks
87- if ( layerName === 'water' && ! state . showWater ) return false ;
88- if ( layerName === 'building' && ! state . showBuildings ) return false ;
89- if ( layerName === 'road' && ! state . showRoads ) return false ;
90- if ( layerName === 'transit' && ! state . showTransit ) return false ;
91- if ( layerName === 'landuse' && ! state . showLanduse ) return false ;
141+ layerNameToKey [ preset . layers [ key ] . name ] = key ;
92142
93- // 2. Advanced Admin Filtering
94- if ( layerName === 'boundaries' ) {
143+ }
95144
96- if ( ! state . showAdmin ) return false ;
97- return true ;
145+ return function ( feature , layerName ) {
98146
99- }
147+ const key = layerNameToKey [ layerName ] ;
100148
101- // 3. Label Filtering
102- if ( layerName === 'place_label' ) {
149+ // If layer is known, check if enabled
150+ if ( key !== undefined ) {
103151
104- if ( ! state . showLabels ) return false ;
105- return props . symbolrank <= state . maxSymbolRank ;
152+ return state . layers [ key ] === true ;
106153
107- }
154+ }
155+
156+ // Unknown layers: hide by default (log for debugging)
157+ console . log ( 'Unknown layer:' , layerName ) ;
158+ return false ;
108159
109- // Default: Only return true if it's one of our toggled layers
110- const activeLayers = [ 'water' , 'building' , 'road' , 'transit' , 'landuse' ] ;
111- return activeLayers . includes ( layerName ) && state [ `show${ layerName . charAt ( 0 ) . toUpperCase ( ) + layerName . slice ( 1 ) } ` ] ;
160+ } ;
112161
113162}
114163
@@ -121,7 +170,28 @@ function recreateTiles() {
121170
122171 }
123172
124- const PMTILES_URL = 'https://demo-bucket.protomaps.com/v4.pmtiles' ;
173+ const preset = SOURCE_PRESETS [ state . sourceType ] ;
174+
175+ // Check if API key is needed
176+ if ( preset . requiresApiKey && ! apiKey ) {
177+
178+ apiKey = prompt ( `Enter API Key for ${ preset . name } :` ) ;
179+ if ( apiKey ) {
180+
181+ localStorage . setItem ( 'mapbox_key' , apiKey ) ;
182+
183+ } else {
184+
185+ // Fall back to PMTiles if no key provided
186+ state . sourceType = 'PMTiles' ;
187+ initStateFromPreset ( 'PMTiles' ) ;
188+ rebuildGUI ( ) ;
189+ recreateTiles ( ) ;
190+ return ;
191+
192+ }
193+
194+ }
125195
126196 tiles = new TilesRenderer ( ) ;
127197 tiles . registerPlugin ( new UpdateOnChangePlugin ( ) ) ;
@@ -131,22 +201,39 @@ function recreateTiles() {
131201 shape : 'ellipsoid' ,
132202 levels : 15 ,
133203 tileDimension : 512 ,
134- url : PMTILES_URL ,
135204 styles : state . colors ,
136- filter : mvtFilter
205+ filter : createFilter ( preset )
137206 } ;
138207
139- tiles . registerPlugin ( new PMTilesPlugin ( pluginOptions ) ) ;
208+ // Select plugin based on source type and render mode
209+ if ( state . sourceType === 'PMTiles' ) {
210+
211+ pluginOptions . url = preset . url ;
212+
213+ // PMTiles currently only supports Texture mode
214+ if ( state . renderMode === 'Mesh' ) {
215+
216+ console . warn ( 'PMTiles source currently only supports Texture mode. Using Texture.' ) ;
140217
141- // if ( state.pluginType === 'Mesh' ) {
218+ }
142219
143- // tiles.registerPlugin( new MVTTilesMeshPlugin ( pluginOptions ) );
220+ tiles . registerPlugin ( new PMTilesPlugin ( pluginOptions ) ) ;
144221
145- // } else {
222+ } else {
146223
147- // tiles.registerPlugin( new MVTTilesPlugin( pluginOptions ) ) ;
224+ pluginOptions . url = preset . urlTemplate + apiKey ;
148225
149- // }
226+ if ( state . renderMode === 'Mesh' ) {
227+
228+ tiles . registerPlugin ( new MVTTilesMeshPlugin ( pluginOptions ) ) ;
229+
230+ } else {
231+
232+ tiles . registerPlugin ( new MVTTilesPlugin ( pluginOptions ) ) ;
233+
234+ }
235+
236+ }
150237
151238 tiles . group . rotation . x = - Math . PI / 2 ;
152239 tiles . setCamera ( camera ) ;
@@ -156,40 +243,72 @@ function recreateTiles() {
156243
157244}
158245
159- function setupGUI ( ) {
246+ function rebuildGUI ( ) {
160247
161- gui = new GUI ( ) ;
248+ // Remove old folders if they exist
249+ if ( layersFolder ) {
250+
251+ layersFolder . destroy ( ) ;
252+ layersFolder = null ;
162253
163- // Plugin Toggle
164- gui . add ( state , 'pluginType' , [ 'Mesh' , 'Texture' ] ) . name ( 'Renderer Mode' ) . onChange ( recreateTiles ) ;
254+ }
165255
166- // Layers Folder
167- const layers = gui . addFolder ( 'Layers' ) ;
168- const trigger = ( ) => recreateTiles ( ) ;
256+ if ( colorsFolder ) {
169257
170- layers . add ( state , 'showWater' ) . name ( 'Water' ) . onChange ( trigger ) ;
171- layers . add ( state , 'showBuildings' ) . name ( 'Buildings' ) . onChange ( trigger ) ;
172- layers . add ( state , 'showRoads' ) . name ( 'Roads' ) . onChange ( trigger ) ;
173- layers . add ( state , 'showTransit' ) . name ( 'Transit' ) . onChange ( trigger ) ;
174- layers . add ( state , 'showLanduse' ) . name ( 'Landuse' ) . onChange ( trigger ) ;
175- layers . add ( state , 'showAdmin' ) . name ( 'Admin Borders' ) . onChange ( trigger ) ;
176- layers . add ( state , 'showLabels' ) . name ( 'Labels' ) . onChange ( trigger ) ;
258+ colorsFolder . destroy ( ) ;
259+ colorsFolder = null ;
177260
178- // Details Folder
179- const details = gui . addFolder ( 'Filter Settings' ) ;
180- details . add ( state , 'maxAdminLevel' , 0 , 4 , 1 ) . name ( 'Admin Detail' ) . onChange ( trigger ) ;
181- details . add ( state , 'maxSymbolRank' , 1 , 10 , 1 ) . name ( 'Label Density' ) . onChange ( trigger ) ;
261+ }
182262
183- // Style Folder
184- const styleFolder = gui . addFolder ( 'Map Styles' ) ;
185- for ( let key in state . colors ) {
263+ const preset = SOURCE_PRESETS [ state . sourceType ] ;
186264
187- styleFolder . addColor ( state . colors , key )
265+ // Rebuild layers folder
266+ layersFolder = gui . addFolder ( 'Layers' ) ;
267+ for ( const key in preset . layers ) {
268+
269+ const layer = preset . layers [ key ] ;
270+ layersFolder . add ( state . layers , key )
188271 . name ( key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) )
189- . onChange ( ( ) => recreateTiles ( ) ) ;
272+ . onChange ( recreateTiles ) ;
190273
191274 }
192275
276+ // Rebuild colors folder
277+ colorsFolder = gui . addFolder ( 'Colors' ) ;
278+ for ( const key in preset . layers ) {
279+
280+ const layer = preset . layers [ key ] ;
281+ colorsFolder . addColor ( state . colors , layer . name )
282+ . name ( key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) )
283+ . onChange ( recreateTiles ) ;
284+
285+ }
286+
287+ }
288+
289+ function setupGUI ( ) {
290+
291+ gui = new GUI ( ) ;
292+
293+ // Source & Renderer Settings
294+ const sourceFolder = gui . addFolder ( 'Source & Renderer' ) ;
295+ sourceFolder . add ( state , 'sourceType' , Object . keys ( SOURCE_PRESETS ) )
296+ . name ( 'Data Source' )
297+ . onChange ( ( value ) => {
298+
299+ initStateFromPreset ( value ) ;
300+ rebuildGUI ( ) ;
301+ recreateTiles ( ) ;
302+
303+ } ) ;
304+ sourceFolder . add ( state , 'renderMode' , [ 'Mesh' , 'Texture' ] )
305+ . name ( 'Render Mode' )
306+ . onChange ( recreateTiles ) ;
307+ sourceFolder . open ( ) ;
308+
309+ // Initial layer and color folders
310+ rebuildGUI ( ) ;
311+
193312}
194313
195314function onWindowResize ( ) {
0 commit comments