Skip to content

Commit e930f19

Browse files
committed
save refactor
1 parent 4c48d4c commit e930f19

20 files changed

Lines changed: 1373 additions & 923 deletions

example/three/mvt_globe.js

Lines changed: 196 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,90 @@ import {
1919
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
2020

2121
let 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 ---
2775
const 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

53107
init();
54108
setupGUI();
@@ -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

195314
function onWindowResize() {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { VectorTileStyler } from '../../../three/renderer/utils/VectorTileStyler.js';
2+
3+
export interface FeatureIteratorResult {
4+
feature: any;
5+
layerName: string;
6+
layer: any;
7+
geometry: any[];
8+
type: number;
9+
}
10+
11+
export class VectorTileIterator {
12+
13+
styler: VectorTileStyler;
14+
15+
constructor( styler: VectorTileStyler );
16+
17+
iterateFeatures( vectorTile: any ): Generator<FeatureIteratorResult>;
18+
19+
}

0 commit comments

Comments
 (0)