maplibre-preview 1.4.3 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/README.md +13 -0
- data/bin/maplibre-preview +1 -1
- data/docs/README_RU.md +13 -0
- data/lib/maplibre-preview/public/js/filters.js +14 -2
- data/lib/maplibre-preview/public/js/tilegrid.js +4 -5
- data/lib/maplibre-preview/version.rb +1 -1
- data/lib/maplibre-preview/views/maplibre_layout.slim +190 -18
- data/lib/maplibre-preview/views/maplibre_map.slim +661 -66
- data/spec/maplibre_preview_spec.rb +43 -0
- metadata +2 -2
|
@@ -12,33 +12,84 @@ ruby:
|
|
|
12
12
|
- if style_name || external_style_url || options[:style_url]
|
|
13
13
|
.layer-controls-wrapper
|
|
14
14
|
.layer-controls id="layer-controls"
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
15
|
+
.control-section-wrapper.map-settings-wrapper id="map-settings-wrapper"
|
|
16
|
+
.control-section.map-settings-section id="map-settings-section"
|
|
17
|
+
.control-section-header
|
|
18
|
+
.control-section-title Map Settings
|
|
19
|
+
button.control-section-toggle-inline id="map-settings-toggle-inline" onclick="toggleControlSection('map-settings')" title="Expand Map Settings"
|
|
20
|
+
span.toggle-icon ▶
|
|
21
|
+
.control-section-body
|
|
22
|
+
.mode-switcher id="settings-mode-switcher"
|
|
23
|
+
button.mode-button.active id="settings-mode-view" onclick="switchSettingsMode(this, 'view')" View
|
|
24
|
+
button.mode-button id="settings-mode-debug" onclick="switchSettingsMode(this, 'debug')" Debug
|
|
25
|
+
|
|
26
|
+
#settings-view-panel.control-panel.settings-panel.active
|
|
27
|
+
.setting-group
|
|
28
|
+
.setting-label
|
|
29
|
+
span Basemap opacity
|
|
30
|
+
span.setting-value id="basemap-opacity-value" 80%
|
|
31
|
+
input.setting-range id="basemap-opacity-slider" type="range" min="0" max="100" step="5" value="80" oninput="setBasemapOpacity(this.value)"
|
|
32
|
+
.setting-group.terrain-setting id="terrain-exaggeration-setting" style="display: none;"
|
|
33
|
+
.setting-label
|
|
34
|
+
span Terrain exaggeration
|
|
35
|
+
span.setting-value id="terrain-exaggeration-value" 1.0x
|
|
36
|
+
input.setting-range id="terrain-exaggeration-slider" type="range" min="0" max="3" step="0.1" value="1" oninput="setTerrainExaggeration(this.value)"
|
|
37
|
+
button.control-button onclick="toggleAntialias()" id="antialias-btn" Antialias
|
|
38
|
+
|
|
39
|
+
#settings-debug-panel.control-panel.settings-panel
|
|
40
|
+
button.control-button onclick="toggleMapCache()" id="map-cache-btn" Cache
|
|
41
|
+
button.control-button onclick="toggleTileGrid()" id="tilegrid-mode-btn" Tile Boundaries
|
|
42
|
+
button.control-button onclick="toggleCollisionBoxes()" id="collision-boxes-btn" Collision Boxes
|
|
43
|
+
button.control-button onclick="toggleOverdrawInspector()" id="overdraw-inspector-btn" Overdraw
|
|
44
|
+
button.control-button onclick="toggleTileFade()" id="tile-fade-btn" Raster Fade
|
|
45
|
+
button.control-button onclick="toggleHoverMode()" id="hover-mode-btn" Hover Mode
|
|
46
|
+
button.control-button onclick="toggleProfileMode()" id="profile-mode-btn" style="display: none;" Elevation Profile
|
|
47
|
+
button.control-section-toggle-external id="map-settings-toggle" onclick="toggleControlSection('map-settings')" title="Collapse Map Settings"
|
|
48
|
+
span.toggle-icon ◀
|
|
49
|
+
|
|
50
|
+
.control-section-wrapper.style-controls-wrapper id="style-controls-wrapper"
|
|
51
|
+
.control-section.style-controls-section id="style-controls-section"
|
|
52
|
+
.control-section-header
|
|
53
|
+
.control-section-title Style Controls
|
|
54
|
+
button.control-section-toggle-inline id="style-controls-toggle-inline" onclick="toggleControlSection('style-controls')" title="Expand Style Controls"
|
|
55
|
+
span.toggle-icon ▶
|
|
56
|
+
.control-section-body
|
|
57
|
+
.mode-switcher id="style-mode-switcher"
|
|
58
|
+
button.mode-button.active id="mode-filters" onclick="switchMode(this, 'filters')" Filters
|
|
59
|
+
button.mode-button id="mode-layers" onclick="switchMode(this, 'layers')" Layers
|
|
60
|
+
|
|
61
|
+
#filters-panel.control-panel.style-panel.active
|
|
62
|
+
.control-panel-header
|
|
63
|
+
button.control-button onclick="toggleAllFilters()" Toggle All Filters
|
|
64
|
+
button.control-button onclick="toggleBasemap()" id="basemap-filters-btn" Show/Hide Basemap
|
|
65
|
+
.control-panel-content
|
|
66
|
+
#filter-buttons
|
|
67
|
+
|
|
68
|
+
#layers-panel.control-panel.style-panel
|
|
69
|
+
.control-panel-header
|
|
70
|
+
button.control-button onclick="toggleAllLayers()" Toggle All Layers
|
|
71
|
+
button.control-button onclick="toggleBasemap()" id="basemap-layers-btn" Show/Hide Basemap
|
|
72
|
+
.control-panel-content
|
|
73
|
+
#layer-buttons
|
|
74
|
+
button.control-section-toggle-external id="style-controls-toggle" onclick="toggleControlSection('style-controls')" title="Collapse Style Controls"
|
|
75
|
+
span.toggle-icon ◀
|
|
38
76
|
|
|
39
77
|
#map-container
|
|
40
78
|
#map.map-layer data-style-url="#{style_url}"
|
|
41
79
|
|
|
80
|
+
#style-parameters-panel.style-parameters-overlay.collapsed style="display: none;"
|
|
81
|
+
button.style-parameters-header type="button" onclick="toggleStyleParametersPanel()" id="style-parameters-toggle" aria-expanded="false"
|
|
82
|
+
span.style-parameters-title Style parameters
|
|
83
|
+
span.style-parameters-summary
|
|
84
|
+
span#style-parameters-count 0
|
|
85
|
+
span params
|
|
86
|
+
span.style-parameters-toggle-icon ▲
|
|
87
|
+
.style-parameters-body
|
|
88
|
+
#style-parameter-fields.style-parameter-fields
|
|
89
|
+
.style-parameter-actions
|
|
90
|
+
button.control-button type="button" onclick="applyStyleParameters()" id="style-parameters-apply" Apply
|
|
91
|
+
button.control-button type="button" onclick="resetStyleParameters()" id="style-parameters-reset" Reset
|
|
92
|
+
|
|
42
93
|
#version-info.version-info
|
|
43
94
|
a.version-info-version href="https://github.com/artyomb/maplibre-preview" target="_blank" v#{MapLibrePreview::VERSION}
|
|
44
95
|
|
|
@@ -77,11 +128,17 @@ ruby:
|
|
|
77
128
|
javascript:
|
|
78
129
|
const mapEl = document.getElementById('map');
|
|
79
130
|
const style_url = mapEl?.dataset?.styleUrl || null;
|
|
80
|
-
let
|
|
131
|
+
let showBasemap = true, currentMode = 'filters';
|
|
81
132
|
let hoverMode = 'click';
|
|
82
133
|
let layerStates = {};
|
|
83
134
|
let map = null;
|
|
84
135
|
let antialiasEnabled = localStorage.getItem('antialiasEnabled') !== 'false';
|
|
136
|
+
let mapCacheDisabled = localStorage.getItem('mapCacheDisabled') === 'true';
|
|
137
|
+
let basemapOpacity = Number.parseFloat(localStorage.getItem('basemapOpacity') || '0.8');
|
|
138
|
+
let terrainExaggeration = Number.parseFloat(localStorage.getItem('terrainExaggeration') || '1');
|
|
139
|
+
let collisionBoxesEnabled = localStorage.getItem('collisionBoxesEnabled') === 'true';
|
|
140
|
+
let overdrawInspectorEnabled = localStorage.getItem('overdrawInspectorEnabled') === 'true';
|
|
141
|
+
let tileFadeEnabled = localStorage.getItem('tileFadeEnabled') !== 'false';
|
|
85
142
|
let styleLoaded = false, resourcesLoaded = 0, totalResources = 0;
|
|
86
143
|
let tilesLoaded = 0, tilesTotal = 0;
|
|
87
144
|
let currentStyle = null;
|
|
@@ -93,19 +150,295 @@ javascript:
|
|
|
93
150
|
let currentProfile = null;
|
|
94
151
|
let contourManager = null;
|
|
95
152
|
let tileGridManager = null;
|
|
153
|
+
let originalStyle = null;
|
|
154
|
+
let styleParameterDefinitions = new Map();
|
|
155
|
+
let styleParameterValues = {};
|
|
156
|
+
let parameterizedUrlPrefixes = new Set();
|
|
96
157
|
|
|
97
158
|
const toDomId = (prefix, id) => `${prefix}-${String(id).replace(/[^a-zA-Z0-9_-]/g, '_')}`;
|
|
159
|
+
const noCacheRequestOptions = () => mapCacheDisabled ? {cache: 'no-store'} : undefined;
|
|
160
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
161
|
+
const PAGE_STYLE_PARAMS = new Set(['style', 'style_url']);
|
|
162
|
+
|
|
163
|
+
const normalizeQueryParams = (value) => {
|
|
164
|
+
if (Array.isArray(value)) return value.map(String).filter(Boolean);
|
|
165
|
+
if (typeof value === 'string') return value.split(',').map(item => item.trim()).filter(Boolean);
|
|
166
|
+
return [];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const inferParameterInputType = (name) => /(^|_|-)(time|date|datetime)($|_|-)/i.test(name) ? 'datetime-local' : 'text';
|
|
170
|
+
|
|
171
|
+
const parameterInputToQueryValue = (name, value) => {
|
|
172
|
+
if (!value) return '';
|
|
173
|
+
if (inferParameterInputType(name) !== 'datetime-local') return value;
|
|
174
|
+
|
|
175
|
+
const parsed = new Date(value);
|
|
176
|
+
return Number.isNaN(parsed.getTime()) ? value : String(Math.floor(parsed.getTime() / 1000));
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const queryValueToParameterInput = (name, value) => {
|
|
180
|
+
if (!value || inferParameterInputType(name) !== 'datetime-local') return value || '';
|
|
181
|
+
|
|
182
|
+
const parsed = /^\d+$/.test(String(value)) ? new Date(Number(value) * 1000) : new Date(value);
|
|
183
|
+
if (Number.isNaN(parsed.getTime())) return '';
|
|
184
|
+
|
|
185
|
+
const pad = number => String(number).padStart(2, '0');
|
|
186
|
+
return `${parsed.getFullYear()}-${pad(parsed.getMonth() + 1)}-${pad(parsed.getDate())}T${pad(parsed.getHours())}:${pad(parsed.getMinutes())}`;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const styleParameterStorageKey = (name) => `maplibre-preview:style-parameter:${style_url || 'default'}:${name}`;
|
|
190
|
+
|
|
191
|
+
const mergeStyleParameterDefinition = (name, sourceName = null) => {
|
|
192
|
+
if (!name || PAGE_STYLE_PARAMS.has(name)) return;
|
|
193
|
+
|
|
194
|
+
const current = styleParameterDefinitions.get(name) || {name, sources: new Set()};
|
|
195
|
+
sourceName && current.sources.add(sourceName);
|
|
196
|
+
styleParameterDefinitions.set(name, current);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const sourceDeclaredParameters = (sourceDef) => [
|
|
200
|
+
...normalizeQueryParams(sourceDef?.query_params),
|
|
201
|
+
...normalizeQueryParams(sourceDef?.queryParams)
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const urlPrefixFromTemplate = (template) => {
|
|
205
|
+
if (!template || typeof template !== 'string') return null;
|
|
206
|
+
const tokenIndex = template.search(/\{[^}]+\}/);
|
|
207
|
+
const prefix = tokenIndex >= 0 ? template.slice(0, tokenIndex) : template;
|
|
208
|
+
return prefix ? new URL(prefix, window.location.href).toString() : null;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const rememberParameterizedUrl = (template) => {
|
|
212
|
+
const prefix = urlPrefixFromTemplate(template);
|
|
213
|
+
prefix && parameterizedUrlPrefixes.add(prefix);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const appendStyleParametersToUrl = (resourceUrl, values = styleParameterValues, parameterNames = Object.keys(values)) => {
|
|
217
|
+
if (!resourceUrl || !parameterNames.length) return resourceUrl;
|
|
218
|
+
|
|
219
|
+
const tokens = [];
|
|
220
|
+
const tokenizedUrl = String(resourceUrl).replace(/\{[^}]+\}/g, match => {
|
|
221
|
+
const placeholder = `__MLP_TOKEN_${tokens.length}__`;
|
|
222
|
+
tokens.push([placeholder, match]);
|
|
223
|
+
return placeholder;
|
|
224
|
+
});
|
|
225
|
+
const url = new URL(tokenizedUrl, window.location.href);
|
|
226
|
+
parameterNames.forEach(name => {
|
|
227
|
+
const value = values[name];
|
|
228
|
+
value === undefined || value === null || value === ''
|
|
229
|
+
? url.searchParams.delete(name)
|
|
230
|
+
: url.searchParams.set(name, String(value));
|
|
231
|
+
});
|
|
232
|
+
return tokens.reduce((result, [placeholder, token]) => result.replace(placeholder, token), url.toString());
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const collectStyleSourceParameters = (style) => {
|
|
236
|
+
Object.entries(style?.sources || {}).forEach(([sourceName, sourceDef]) => {
|
|
237
|
+
if (sourceName === 'preview-basemap') return;
|
|
238
|
+
|
|
239
|
+
const params = sourceDeclaredParameters(sourceDef);
|
|
240
|
+
params.forEach(name => mergeStyleParameterDefinition(name, sourceName));
|
|
241
|
+
|
|
242
|
+
if (params.length) {
|
|
243
|
+
[sourceDef.url, sourceDef.meta_url, sourceDef.data].forEach(rememberParameterizedUrl);
|
|
244
|
+
(sourceDef.tiles || []).forEach(rememberParameterizedUrl);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const fetchSourceMetadata = async (url) => {
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch(appendStyleParametersToUrl(url), noCacheRequestOptions());
|
|
252
|
+
return response.ok ? response.json() : null;
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.warn('Could not inspect source metadata:', url, e);
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const collectSourceMetadataParameters = async (style) => {
|
|
260
|
+
const entries = Object.entries(style?.sources || {}).filter(([, sourceDef]) => sourceDef?.url || sourceDef?.meta_url);
|
|
261
|
+
|
|
262
|
+
await Promise.all(entries.map(async ([sourceName, sourceDef]) => {
|
|
263
|
+
const metadataUrls = [sourceDef.meta_url];
|
|
264
|
+
if (sourceDef.type !== 'image') metadataUrls.push(sourceDef.url);
|
|
265
|
+
|
|
266
|
+
for (const metadataUrl of metadataUrls.filter(Boolean)) {
|
|
267
|
+
const metadata = await fetchSourceMetadata(metadataUrl);
|
|
268
|
+
if (!metadata) continue;
|
|
269
|
+
|
|
270
|
+
const params = normalizeQueryParams(metadata.query_params || metadata.queryParams);
|
|
271
|
+
params.forEach(name => mergeStyleParameterDefinition(name, sourceName));
|
|
272
|
+
(metadata.tiles || []).forEach(rememberParameterizedUrl);
|
|
273
|
+
}
|
|
274
|
+
}));
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const getInitialStyleParameterValue = (name) => {
|
|
278
|
+
const pageParams = new URLSearchParams(window.location.search);
|
|
279
|
+
const urlValue = pageParams.get(name);
|
|
280
|
+
if (urlValue !== null) return urlValue;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
return localStorage.getItem(styleParameterStorageKey(name)) || '';
|
|
284
|
+
} catch (e) {
|
|
285
|
+
return '';
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const pageProvidedStyleParameterValues = () => {
|
|
290
|
+
const values = {};
|
|
291
|
+
new URLSearchParams(window.location.search).forEach((value, key) => {
|
|
292
|
+
if (!PAGE_STYLE_PARAMS.has(key)) values[key] = value;
|
|
293
|
+
});
|
|
294
|
+
return values;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const renderStyleParameterControls = () => {
|
|
298
|
+
const panel = document.getElementById('style-parameters-panel');
|
|
299
|
+
const fields = document.getElementById('style-parameter-fields');
|
|
300
|
+
const count = document.getElementById('style-parameters-count');
|
|
301
|
+
if (!panel || !fields) return;
|
|
302
|
+
|
|
303
|
+
const definitions = [...styleParameterDefinitions.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
304
|
+
panel.style.display = definitions.length ? 'block' : 'none';
|
|
305
|
+
count && (count.textContent = String(definitions.length));
|
|
306
|
+
fields.innerHTML = '';
|
|
307
|
+
|
|
308
|
+
definitions.forEach(definition => {
|
|
309
|
+
const row = document.createElement('label');
|
|
310
|
+
row.className = 'style-parameter-row';
|
|
311
|
+
row.htmlFor = `style-param-${definition.name}`;
|
|
312
|
+
|
|
313
|
+
const label = document.createElement('span');
|
|
314
|
+
label.className = 'style-parameter-label';
|
|
315
|
+
label.textContent = definition.name;
|
|
316
|
+
|
|
317
|
+
const input = document.createElement('input');
|
|
318
|
+
input.className = 'style-parameter-input';
|
|
319
|
+
input.id = `style-param-${definition.name}`;
|
|
320
|
+
input.type = inferParameterInputType(definition.name);
|
|
321
|
+
input.value = queryValueToParameterInput(definition.name, styleParameterValues[definition.name]);
|
|
322
|
+
input.dataset.parameterName = definition.name;
|
|
323
|
+
input.title = [...definition.sources].join(', ');
|
|
324
|
+
|
|
325
|
+
row.appendChild(label);
|
|
326
|
+
row.appendChild(input);
|
|
327
|
+
fields.appendChild(row);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
layoutBottomOverlays();
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const toggleStyleParametersPanel = () => {
|
|
334
|
+
const panel = document.getElementById('style-parameters-panel');
|
|
335
|
+
const toggle = document.getElementById('style-parameters-toggle');
|
|
336
|
+
if (!panel) return;
|
|
337
|
+
|
|
338
|
+
const isCollapsed = panel.classList.toggle('collapsed');
|
|
339
|
+
toggle?.setAttribute('aria-expanded', String(!isCollapsed));
|
|
340
|
+
layoutBottomOverlays();
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const initializeStyleParameters = async (style) => {
|
|
344
|
+
styleParameterDefinitions = new Map();
|
|
345
|
+
styleParameterValues = {};
|
|
346
|
+
parameterizedUrlPrefixes = new Set();
|
|
347
|
+
|
|
348
|
+
collectStyleSourceParameters(style);
|
|
349
|
+
styleParameterDefinitions.forEach((definition, name) => {
|
|
350
|
+
styleParameterValues[name] = getInitialStyleParameterValue(name);
|
|
351
|
+
});
|
|
352
|
+
await collectSourceMetadataParameters(style);
|
|
353
|
+
|
|
354
|
+
styleParameterDefinitions.forEach((definition, name) => {
|
|
355
|
+
styleParameterValues[name] ??= getInitialStyleParameterValue(name);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
renderStyleParameterControls();
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const getSourceParameterNames = (sourceName, sourceDef) => {
|
|
362
|
+
const names = new Set(sourceDeclaredParameters(sourceDef));
|
|
363
|
+
styleParameterDefinitions.forEach((definition, name) => {
|
|
364
|
+
definition.sources.has(sourceName) && names.add(name);
|
|
365
|
+
});
|
|
366
|
+
return [...names];
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const applyStyleParametersToStyle = (style) => {
|
|
370
|
+
const modifiedStyle = JSON.parse(JSON.stringify(style));
|
|
371
|
+
|
|
372
|
+
Object.entries(modifiedStyle.sources || {}).forEach(([sourceName, sourceDef]) => {
|
|
373
|
+
if (sourceName === 'preview-basemap') return;
|
|
374
|
+
|
|
375
|
+
const parameterNames = getSourceParameterNames(sourceName, sourceDef);
|
|
376
|
+
if (!parameterNames.length) return;
|
|
377
|
+
|
|
378
|
+
typeof sourceDef.url === 'string' && (sourceDef.url = appendStyleParametersToUrl(sourceDef.url, styleParameterValues, parameterNames));
|
|
379
|
+
typeof sourceDef.meta_url === 'string' && (sourceDef.meta_url = appendStyleParametersToUrl(sourceDef.meta_url, styleParameterValues, parameterNames));
|
|
380
|
+
typeof sourceDef.data === 'string' && (sourceDef.data = appendStyleParametersToUrl(sourceDef.data, styleParameterValues, parameterNames));
|
|
381
|
+
Array.isArray(sourceDef.tiles) && (sourceDef.tiles = sourceDef.tiles.map(tileUrl => appendStyleParametersToUrl(tileUrl, styleParameterValues, parameterNames)));
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return modifiedStyle;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const shouldPatchRequestUrl = (resourceUrl) => {
|
|
388
|
+
if (!Object.values(styleParameterValues).some(value => value !== undefined && value !== null && value !== '')) return false;
|
|
389
|
+
const absolute = new URL(resourceUrl, window.location.href).toString();
|
|
390
|
+
return absolute.includes('/rb_tiles/') || [...parameterizedUrlPrefixes].some(prefix => absolute.startsWith(prefix));
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
const patchRequestUrl = (resourceUrl) => shouldPatchRequestUrl(resourceUrl) ? appendStyleParametersToUrl(resourceUrl) : resourceUrl;
|
|
394
|
+
|
|
395
|
+
const BOTTOM_OVERLAY_IDS = ['style-parameters-panel', 'loading-indicator', 'profile-overlay'];
|
|
396
|
+
const BOTTOM_OVERLAY_BASE_OFFSET = 20;
|
|
397
|
+
const BOTTOM_OVERLAY_GAP = 12;
|
|
398
|
+
|
|
399
|
+
const isVisibleBottomOverlay = (element) => {
|
|
400
|
+
if (!element) return false;
|
|
401
|
+
|
|
402
|
+
const style = window.getComputedStyle(element);
|
|
403
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && element.getClientRects().length > 0;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const layoutBottomOverlays = () => {
|
|
407
|
+
window.requestAnimationFrame(() => {
|
|
408
|
+
let bottomOffset = BOTTOM_OVERLAY_BASE_OFFSET;
|
|
409
|
+
|
|
410
|
+
BOTTOM_OVERLAY_IDS.forEach(id => {
|
|
411
|
+
const element = document.getElementById(id);
|
|
412
|
+
if (!element) return;
|
|
413
|
+
|
|
414
|
+
if (!isVisibleBottomOverlay(element)) {
|
|
415
|
+
element.style.bottom = '';
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
element.style.bottom = `${bottomOffset}px`;
|
|
420
|
+
bottomOffset += element.offsetHeight + BOTTOM_OVERLAY_GAP;
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
};
|
|
98
424
|
|
|
99
425
|
const getRealTerrainElevation = (lngLat) => {
|
|
100
426
|
const elevation = map.queryTerrainElevation(lngLat);
|
|
101
427
|
if (elevation == null) return null;
|
|
102
428
|
|
|
103
|
-
const exaggeration = currentStyle?.terrain?.exaggeration || 1.0;
|
|
429
|
+
const exaggeration = terrainExaggeration || currentStyle?.terrain?.exaggeration || 1.0;
|
|
104
430
|
return elevation / exaggeration;
|
|
105
431
|
};
|
|
106
432
|
|
|
107
|
-
const showLoading = () =>
|
|
108
|
-
|
|
433
|
+
const showLoading = () => {
|
|
434
|
+
document.getElementById('loading-indicator').style.display = 'block';
|
|
435
|
+
layoutBottomOverlays();
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const hideLoading = () => {
|
|
439
|
+
document.getElementById('loading-indicator').style.display = 'none';
|
|
440
|
+
layoutBottomOverlays();
|
|
441
|
+
};
|
|
109
442
|
|
|
110
443
|
const updateLoadingProgress = () => {
|
|
111
444
|
let progress = 0;
|
|
@@ -136,7 +469,7 @@ javascript:
|
|
|
136
469
|
const target = isStringArg ? null : (arg1?.currentTarget || arg1);
|
|
137
470
|
currentMode = mode;
|
|
138
471
|
|
|
139
|
-
document.querySelectorAll('.mode-button').forEach(btn => btn.classList.remove('active'));
|
|
472
|
+
document.querySelectorAll('#style-mode-switcher .mode-button').forEach(btn => btn.classList.remove('active'));
|
|
140
473
|
(target || document.getElementById(`mode-${mode}`))?.classList.add('active');
|
|
141
474
|
|
|
142
475
|
document.getElementById('filters-panel').classList.toggle('active', mode === 'filters');
|
|
@@ -147,6 +480,17 @@ javascript:
|
|
|
147
480
|
updateBasemapButton();
|
|
148
481
|
};
|
|
149
482
|
|
|
483
|
+
const switchSettingsMode = (arg1, arg2) => {
|
|
484
|
+
const isStringArg = typeof arg1 === 'string';
|
|
485
|
+
const mode = isStringArg ? arg1 : arg2;
|
|
486
|
+
const target = isStringArg ? null : (arg1?.currentTarget || arg1);
|
|
487
|
+
document.querySelectorAll('#settings-mode-switcher .mode-button').forEach(btn => btn.classList.remove('active'));
|
|
488
|
+
(target || document.getElementById(`settings-mode-${mode}`))?.classList.add('active');
|
|
489
|
+
|
|
490
|
+
document.querySelectorAll('.settings-panel').forEach(panel => panel.classList.remove('active'));
|
|
491
|
+
document.getElementById(`settings-${mode}-panel`)?.classList.add('active');
|
|
492
|
+
};
|
|
493
|
+
|
|
150
494
|
const applyLayerMode = () => {
|
|
151
495
|
Object.keys(layerStates).forEach(layerId => {
|
|
152
496
|
if (map.getLayer(layerId)) {
|
|
@@ -156,8 +500,7 @@ javascript:
|
|
|
156
500
|
};
|
|
157
501
|
|
|
158
502
|
const applyBasemapVisibility = () => {
|
|
159
|
-
|
|
160
|
-
map?.getLayer?.('preview-basemap-layer') && map.setLayoutProperty('preview-basemap-layer', 'visibility', isVisible ? 'visible' : 'none');
|
|
503
|
+
map?.getLayer?.('preview-basemap-layer') && map.setLayoutProperty('preview-basemap-layer', 'visibility', showBasemap ? 'visible' : 'none');
|
|
161
504
|
};
|
|
162
505
|
|
|
163
506
|
const checkStyleResources = (style) => {
|
|
@@ -196,7 +539,13 @@ javascript:
|
|
|
196
539
|
zoom,
|
|
197
540
|
attributionControl: false,
|
|
198
541
|
validateStyle: false,
|
|
199
|
-
antialias: antialiasEnabled
|
|
542
|
+
antialias: antialiasEnabled,
|
|
543
|
+
canvasContextAttributes: {antialias: antialiasEnabled},
|
|
544
|
+
fadeDuration: tileFadeEnabled ? 300 : 0,
|
|
545
|
+
transformRequest: (url) => ({
|
|
546
|
+
url: patchRequestUrl(url),
|
|
547
|
+
...noCacheRequestOptions()
|
|
548
|
+
})
|
|
200
549
|
});
|
|
201
550
|
};
|
|
202
551
|
|
|
@@ -222,7 +571,7 @@ javascript:
|
|
|
222
571
|
type: 'raster',
|
|
223
572
|
source: 'preview-basemap',
|
|
224
573
|
layout: {visibility: 'visible'},
|
|
225
|
-
paint: {'raster-opacity':
|
|
574
|
+
paint: {'raster-opacity': basemapOpacity, 'raster-fade-duration': tileFadeEnabled ? 300 : 0}
|
|
226
575
|
});
|
|
227
576
|
|
|
228
577
|
return modifiedStyle;
|
|
@@ -244,19 +593,21 @@ javascript:
|
|
|
244
593
|
popup.setLngLat(e.lngLat).setHTML(tooltips.join('<br>')).addTo(map);
|
|
245
594
|
};
|
|
246
595
|
|
|
247
|
-
const initializeMap = () => {
|
|
596
|
+
const initializeMap = async () => {
|
|
248
597
|
showLoading();
|
|
249
598
|
const emptyStyle = {version: 8, sources: {}, layers: []};
|
|
250
599
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
.then(response => response.json())
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
600
|
+
try {
|
|
601
|
+
originalStyle = style_url
|
|
602
|
+
? await fetch(appendStyleParametersToUrl(style_url, pageProvidedStyleParameterValues(), Object.keys(pageProvidedStyleParameterValues())), noCacheRequestOptions()).then(response => response.json())
|
|
603
|
+
: emptyStyle;
|
|
604
|
+
} catch (error) {
|
|
605
|
+
console.error('Style loading error:', error);
|
|
606
|
+
originalStyle = emptyStyle;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
await initializeStyleParameters(originalStyle);
|
|
610
|
+
createMapWithStyle(addBasemapToStyle(applyStyleParametersToStyle(originalStyle)));
|
|
260
611
|
};
|
|
261
612
|
|
|
262
613
|
const createMapWithStyle = (style) => {
|
|
@@ -319,13 +670,7 @@ javascript:
|
|
|
319
670
|
|
|
320
671
|
const onStyleReady = () => {
|
|
321
672
|
const hasTerrain = currentStyle?.terrain;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
map.setProjection({type: projectionType});
|
|
326
|
-
} catch (e) {
|
|
327
|
-
console.warn('Projection setting failed:', e);
|
|
328
|
-
}
|
|
673
|
+
applyDefaultProjection();
|
|
329
674
|
|
|
330
675
|
try {
|
|
331
676
|
map.addControl(new maplibregl.GlobeControl(), 'top-right');
|
|
@@ -336,14 +681,20 @@ javascript:
|
|
|
336
681
|
if (currentStyle?.terrain) {
|
|
337
682
|
const terrainSourceName = currentStyle.terrain.source;
|
|
338
683
|
map.addControl(new maplibregl.TerrainControl({
|
|
339
|
-
source: terrainSourceName
|
|
684
|
+
source: terrainSourceName,
|
|
685
|
+
exaggeration: terrainExaggeration
|
|
340
686
|
}), 'top-right');
|
|
341
687
|
|
|
342
688
|
initializeContourManager();
|
|
689
|
+
applyTerrainExaggeration();
|
|
343
690
|
}
|
|
344
691
|
|
|
345
692
|
initializeTileGridManager();
|
|
693
|
+
applyBasemapOpacity();
|
|
694
|
+
applyDebugRenderSettings();
|
|
695
|
+
applyTileFadeSetting();
|
|
346
696
|
updateTerrainIndicator();
|
|
697
|
+
updateMapSettingsControls();
|
|
347
698
|
};
|
|
348
699
|
|
|
349
700
|
const setupMapInteractions = () => {
|
|
@@ -415,20 +766,20 @@ javascript:
|
|
|
415
766
|
|
|
416
767
|
const setupPerformanceMonitoring = () => {
|
|
417
768
|
[startPerformanceMonitoring, () => requestAnimationFrame(countFrame),
|
|
418
|
-
updateHoverModeButton, updateBasemapButton, updateAntialiasButton
|
|
769
|
+
updateHoverModeButton, updateBasemapButton, updateAntialiasButton, updateMapCacheButton,
|
|
770
|
+
updateMapSettingsControls].forEach(fn => fn());
|
|
419
771
|
};
|
|
420
772
|
|
|
421
773
|
const toggleBasemap = () => {
|
|
422
|
-
|
|
774
|
+
showBasemap = !showBasemap;
|
|
423
775
|
applyBasemapVisibility();
|
|
424
776
|
updateBasemapButton();
|
|
425
777
|
};
|
|
426
778
|
|
|
427
779
|
const updateBasemapButton = () => {
|
|
428
|
-
const isVisible = currentMode === 'filters' ? showBasemapFilters : showBasemapLayers;
|
|
429
780
|
document.querySelectorAll('button[onclick="toggleBasemap()"]').forEach(btn => {
|
|
430
|
-
btn.textContent =
|
|
431
|
-
btn.className = `control-button ${
|
|
781
|
+
btn.textContent = showBasemap ? 'Hide Basemap' : 'Show Basemap';
|
|
782
|
+
btn.className = `control-button ${showBasemap ? 'active' : 'inactive'}`;
|
|
432
783
|
});
|
|
433
784
|
};
|
|
434
785
|
|
|
@@ -436,6 +787,7 @@ javascript:
|
|
|
436
787
|
const terrainRow = document.getElementById('terrain-row');
|
|
437
788
|
const terrainElement = document.getElementById('terrain-status-value');
|
|
438
789
|
const profileBtn = document.getElementById('profile-mode-btn');
|
|
790
|
+
const terrainSetting = document.getElementById('terrain-exaggeration-setting');
|
|
439
791
|
|
|
440
792
|
if (!terrainRow || !terrainElement) return;
|
|
441
793
|
|
|
@@ -445,9 +797,11 @@ javascript:
|
|
|
445
797
|
terrainElement.textContent = 'is detected';
|
|
446
798
|
terrainElement.className = 'metric-value success';
|
|
447
799
|
profileBtn && (profileBtn.style.display = 'block');
|
|
800
|
+
terrainSetting && (terrainSetting.style.display = 'flex');
|
|
448
801
|
} else {
|
|
449
802
|
terrainRow.style.display = 'none';
|
|
450
803
|
profileBtn && (profileBtn.style.display = 'none');
|
|
804
|
+
terrainSetting && (terrainSetting.style.display = 'none');
|
|
451
805
|
}
|
|
452
806
|
};
|
|
453
807
|
|
|
@@ -594,6 +948,7 @@ javascript:
|
|
|
594
948
|
[header, stats, chart].forEach(el => overlay.appendChild(el));
|
|
595
949
|
document.getElementById('map-container').appendChild(overlay);
|
|
596
950
|
setTimeout(() => drawSimpleProfileChart(profile), 10);
|
|
951
|
+
layoutBottomOverlays();
|
|
597
952
|
};
|
|
598
953
|
|
|
599
954
|
const drawSimpleProfileChart = (profile) => {
|
|
@@ -671,6 +1026,7 @@ javascript:
|
|
|
671
1026
|
|
|
672
1027
|
const hideProfile = () => {
|
|
673
1028
|
document.getElementById('profile-overlay')?.remove();
|
|
1029
|
+
layoutBottomOverlays();
|
|
674
1030
|
map.getLayer('profile-line') && (map.removeLayer('profile-line'), map.removeSource('profile-line'));
|
|
675
1031
|
map.getLayer('temporary-line') && (map.removeLayer('temporary-line'), map.removeSource('temporary-line'));
|
|
676
1032
|
hideMapMarker();
|
|
@@ -704,7 +1060,7 @@ javascript:
|
|
|
704
1060
|
});
|
|
705
1061
|
|
|
706
1062
|
if (map.getLayer('preview-basemap-layer')) {
|
|
707
|
-
|
|
1063
|
+
showBasemap = newState;
|
|
708
1064
|
}
|
|
709
1065
|
|
|
710
1066
|
updateLayerButtons();
|
|
@@ -720,7 +1076,7 @@ javascript:
|
|
|
720
1076
|
}
|
|
721
1077
|
|
|
722
1078
|
if (layerId === 'preview-basemap-layer') {
|
|
723
|
-
|
|
1079
|
+
showBasemap = layerStates[layerId];
|
|
724
1080
|
updateBasemapButton();
|
|
725
1081
|
}
|
|
726
1082
|
|
|
@@ -871,19 +1227,247 @@ javascript:
|
|
|
871
1227
|
btn.className = `control-button ${antialiasEnabled ? 'active' : 'inactive'}`;
|
|
872
1228
|
};
|
|
873
1229
|
|
|
874
|
-
|
|
1230
|
+
const toggleMapCache = () => {
|
|
1231
|
+
mapCacheDisabled = !mapCacheDisabled;
|
|
1232
|
+
localStorage.setItem('mapCacheDisabled', mapCacheDisabled.toString());
|
|
1233
|
+
window.location.reload();
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
const updateMapCacheButton = () => {
|
|
1237
|
+
const btn = document.getElementById('map-cache-btn');
|
|
1238
|
+
if (!btn) return;
|
|
1239
|
+
|
|
1240
|
+
btn.textContent = `Cache: ${mapCacheDisabled ? 'OFF' : 'ON'}`;
|
|
1241
|
+
btn.className = `control-button ${mapCacheDisabled ? 'cache-off' : 'active'}`;
|
|
1242
|
+
btn.title = mapCacheDisabled ? 'Browser cache is disabled for map requests' : 'Browser cache is enabled for map requests';
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
const getDefaultProjectionType = () => {
|
|
1246
|
+
return currentStyle?.terrain ? 'mercator' : 'globe';
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
const applyDefaultProjection = () => {
|
|
1250
|
+
if (!map) return;
|
|
1251
|
+
|
|
1252
|
+
try {
|
|
1253
|
+
map.setProjection({type: getDefaultProjectionType()});
|
|
1254
|
+
} catch (e) {
|
|
1255
|
+
console.warn('Projection setting failed:', e);
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
const setBasemapOpacity = (value) => {
|
|
1260
|
+
basemapOpacity = clamp(Number.parseFloat(value) / 100, 0, 1);
|
|
1261
|
+
localStorage.setItem('basemapOpacity', basemapOpacity.toString());
|
|
1262
|
+
applyBasemapOpacity();
|
|
1263
|
+
updateBasemapOpacityControl();
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
const applyBasemapOpacity = () => {
|
|
1267
|
+
if (map?.getLayer?.('preview-basemap-layer')) {
|
|
1268
|
+
map.setPaintProperty('preview-basemap-layer', 'raster-opacity', basemapOpacity);
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
const updateBasemapOpacityControl = () => {
|
|
1273
|
+
const slider = document.getElementById('basemap-opacity-slider');
|
|
1274
|
+
const value = document.getElementById('basemap-opacity-value');
|
|
1275
|
+
const percent = Math.round(clamp(basemapOpacity, 0, 1) * 100);
|
|
1276
|
+
slider && (slider.value = percent);
|
|
1277
|
+
value && (value.textContent = `${percent}%`);
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
const setTerrainExaggeration = (value) => {
|
|
1281
|
+
terrainExaggeration = clamp(Number.parseFloat(value), 0, 3);
|
|
1282
|
+
localStorage.setItem('terrainExaggeration', terrainExaggeration.toString());
|
|
1283
|
+
applyTerrainExaggeration();
|
|
1284
|
+
updateTerrainExaggerationControl();
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
const applyTerrainExaggeration = () => {
|
|
1288
|
+
if (!map || !currentStyle?.terrain?.source) return;
|
|
1289
|
+
|
|
1290
|
+
try {
|
|
1291
|
+
map.setTerrain({
|
|
1292
|
+
source: currentStyle.terrain.source,
|
|
1293
|
+
exaggeration: terrainExaggeration
|
|
1294
|
+
});
|
|
1295
|
+
} catch (e) {
|
|
1296
|
+
console.warn('Terrain exaggeration setting failed:', e);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
const updateTerrainExaggerationControl = () => {
|
|
1301
|
+
const slider = document.getElementById('terrain-exaggeration-slider');
|
|
1302
|
+
const value = document.getElementById('terrain-exaggeration-value');
|
|
1303
|
+
slider && (slider.value = terrainExaggeration);
|
|
1304
|
+
value && (value.textContent = `${terrainExaggeration.toFixed(1)}x`);
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
const toggleCollisionBoxes = () => {
|
|
1308
|
+
collisionBoxesEnabled = !collisionBoxesEnabled;
|
|
1309
|
+
localStorage.setItem('collisionBoxesEnabled', collisionBoxesEnabled.toString());
|
|
1310
|
+
applyDebugRenderSettings();
|
|
1311
|
+
updateDebugRenderButtons();
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1314
|
+
const toggleOverdrawInspector = () => {
|
|
1315
|
+
overdrawInspectorEnabled = !overdrawInspectorEnabled;
|
|
1316
|
+
localStorage.setItem('overdrawInspectorEnabled', overdrawInspectorEnabled.toString());
|
|
1317
|
+
applyDebugRenderSettings();
|
|
1318
|
+
updateDebugRenderButtons();
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
const applyDebugRenderSettings = () => {
|
|
1322
|
+
if (!map) return;
|
|
1323
|
+
map.showCollisionBoxes = collisionBoxesEnabled;
|
|
1324
|
+
map.showOverdrawInspector = overdrawInspectorEnabled;
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
const updateDebugRenderButtons = () => {
|
|
1328
|
+
const collisionBtn = document.getElementById('collision-boxes-btn');
|
|
1329
|
+
const overdrawBtn = document.getElementById('overdraw-inspector-btn');
|
|
1330
|
+
if (collisionBtn) {
|
|
1331
|
+
collisionBtn.textContent = `Collision Boxes: ${collisionBoxesEnabled ? 'ON' : 'OFF'}`;
|
|
1332
|
+
collisionBtn.className = `control-button ${collisionBoxesEnabled ? 'active' : 'inactive'}`;
|
|
1333
|
+
}
|
|
1334
|
+
if (overdrawBtn) {
|
|
1335
|
+
overdrawBtn.textContent = `Overdraw: ${overdrawInspectorEnabled ? 'ON' : 'OFF'}`;
|
|
1336
|
+
overdrawBtn.className = `control-button ${overdrawInspectorEnabled ? 'active' : 'inactive'}`;
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
const toggleTileFade = () => {
|
|
1341
|
+
tileFadeEnabled = !tileFadeEnabled;
|
|
1342
|
+
localStorage.setItem('tileFadeEnabled', tileFadeEnabled.toString());
|
|
1343
|
+
applyTileFadeSetting();
|
|
1344
|
+
updateTileFadeButton();
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
const applyTileFadeSetting = () => {
|
|
1348
|
+
if (!map?.getStyle()?.layers) return;
|
|
1349
|
+
const fadeDuration = tileFadeEnabled ? 300 : 0;
|
|
1350
|
+
|
|
1351
|
+
map.getStyle().layers.forEach(layer => {
|
|
1352
|
+
if (layer.type === 'raster' && map.getLayer(layer.id)) {
|
|
1353
|
+
map.setPaintProperty(layer.id, 'raster-fade-duration', fadeDuration);
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
const updateTileFadeButton = () => {
|
|
1359
|
+
const btn = document.getElementById('tile-fade-btn');
|
|
1360
|
+
if (!btn) return;
|
|
1361
|
+
btn.textContent = `Raster Fade: ${tileFadeEnabled ? 'ON' : 'OFF'}`;
|
|
1362
|
+
btn.className = `control-button ${tileFadeEnabled ? 'active' : 'inactive'}`;
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
const updateMapSettingsControls = () => {
|
|
1366
|
+
updateBasemapOpacityControl();
|
|
1367
|
+
updateTerrainExaggerationControl();
|
|
1368
|
+
updateDebugRenderButtons();
|
|
1369
|
+
updateTileFadeButton();
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
const syncStyleParameterUrlState = () => {
|
|
1373
|
+
const url = new URL(window.location.href);
|
|
1374
|
+
Object.entries(styleParameterValues).forEach(([name, value]) => {
|
|
1375
|
+
value === undefined || value === null || value === ''
|
|
1376
|
+
? url.searchParams.delete(name)
|
|
1377
|
+
: url.searchParams.set(name, String(value));
|
|
1378
|
+
});
|
|
1379
|
+
window.history.replaceState({}, '', url.toString());
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
const readStyleParameterFormValues = () => {
|
|
1383
|
+
document.querySelectorAll('.style-parameter-input').forEach(input => {
|
|
1384
|
+
const name = input.dataset.parameterName;
|
|
1385
|
+
if (!name) return;
|
|
1386
|
+
styleParameterValues[name] = parameterInputToQueryValue(name, input.value);
|
|
1387
|
+
|
|
1388
|
+
try {
|
|
1389
|
+
styleParameterValues[name]
|
|
1390
|
+
? localStorage.setItem(styleParameterStorageKey(name), styleParameterValues[name])
|
|
1391
|
+
: localStorage.removeItem(styleParameterStorageKey(name));
|
|
1392
|
+
} catch (e) {
|
|
1393
|
+
console.warn('Could not persist style parameter:', name, e);
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
const resetRuntimeStateBeforeReload = () => {
|
|
1399
|
+
stopPerformanceMonitoring();
|
|
1400
|
+
tileGridManager?.cleanup();
|
|
1401
|
+
contourManager?.cleanup();
|
|
1402
|
+
document.getElementById('filter-buttons') && (document.getElementById('filter-buttons').innerHTML = '');
|
|
1403
|
+
document.getElementById('layer-buttons') && (document.getElementById('layer-buttons').innerHTML = '');
|
|
1404
|
+
Object.keys(layerStates).forEach(key => delete layerStates[key]);
|
|
1405
|
+
Object.keys(layerIdToDomId).forEach(key => delete layerIdToDomId[key]);
|
|
1406
|
+
filters = null;
|
|
1407
|
+
contourManager = null;
|
|
1408
|
+
tileGridManager = null;
|
|
1409
|
+
window.tileGridManager = null;
|
|
1410
|
+
currentStyle = null;
|
|
1411
|
+
styleLoaded = false;
|
|
1412
|
+
resourcesLoaded = 0;
|
|
1413
|
+
totalResources = 0;
|
|
1414
|
+
tilesLoaded = 0;
|
|
1415
|
+
tilesTotal = 0;
|
|
1416
|
+
profilePoints = [];
|
|
1417
|
+
currentProfile = null;
|
|
1418
|
+
profileLine = null;
|
|
1419
|
+
hideProfile();
|
|
1420
|
+
hideElevationTooltip();
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
const reloadStyleWithParameters = () => {
|
|
1424
|
+
if (!originalStyle) return;
|
|
1425
|
+
|
|
1426
|
+
showLoading();
|
|
1427
|
+
resetRuntimeStateBeforeReload();
|
|
1428
|
+
map?.remove();
|
|
1429
|
+
map = null;
|
|
1430
|
+
createMapWithStyle(addBasemapToStyle(applyStyleParametersToStyle(originalStyle)));
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
const applyStyleParameters = () => {
|
|
1434
|
+
readStyleParameterFormValues();
|
|
1435
|
+
syncStyleParameterUrlState();
|
|
1436
|
+
renderStyleParameterControls();
|
|
1437
|
+
reloadStyleWithParameters();
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
const resetStyleParameters = () => {
|
|
1441
|
+
Object.keys(styleParameterValues).forEach(name => {
|
|
1442
|
+
styleParameterValues[name] = '';
|
|
1443
|
+
try {
|
|
1444
|
+
localStorage.removeItem(styleParameterStorageKey(name));
|
|
1445
|
+
} catch (e) {
|
|
1446
|
+
console.warn('Could not clear style parameter:', name, e);
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
syncStyleParameterUrlState();
|
|
1450
|
+
renderStyleParameterControls();
|
|
1451
|
+
reloadStyleWithParameters();
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
const toggleControlSection = (sectionName) => {
|
|
1455
|
+
const wrapper = document.getElementById(`${sectionName}-wrapper`);
|
|
1456
|
+
const section = document.getElementById(`${sectionName}-section`);
|
|
1457
|
+
const externalToggle = document.getElementById(`${sectionName}-toggle`);
|
|
1458
|
+
const inlineToggle = document.getElementById(`${sectionName}-toggle-inline`);
|
|
1459
|
+
if (!wrapper || !section) return;
|
|
875
1460
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const controls = document.getElementById('layer-controls');
|
|
879
|
-
if (!wrapper || !controls) return;
|
|
1461
|
+
const isCollapsed = section.classList.toggle('collapsed');
|
|
1462
|
+
wrapper.classList.toggle('collapsed', isCollapsed);
|
|
880
1463
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1464
|
+
const sectionTitle = section.querySelector('.control-section-title')?.textContent || 'section';
|
|
1465
|
+
externalToggle && (externalToggle.title = `Collapse ${sectionTitle}`);
|
|
1466
|
+
inlineToggle && (inlineToggle.title = `Expand ${sectionTitle}`);
|
|
884
1467
|
};
|
|
885
1468
|
|
|
886
1469
|
window.switchMode = switchMode;
|
|
1470
|
+
window.switchSettingsMode = switchSettingsMode;
|
|
887
1471
|
window.toggleBasemap = toggleBasemap;
|
|
888
1472
|
window.toggleHoverMode = toggleHoverMode;
|
|
889
1473
|
window.toggleAllFilters = () => filters?.toggleAllFilters();
|
|
@@ -891,9 +1475,20 @@ javascript:
|
|
|
891
1475
|
window.togglePerformancePanel = togglePerformancePanel;
|
|
892
1476
|
window.toggleProfileMode = toggleProfileMode;
|
|
893
1477
|
window.toggleAntialias = toggleAntialias;
|
|
894
|
-
window.
|
|
1478
|
+
window.toggleMapCache = toggleMapCache;
|
|
1479
|
+
window.setBasemapOpacity = setBasemapOpacity;
|
|
1480
|
+
window.setTerrainExaggeration = setTerrainExaggeration;
|
|
1481
|
+
window.toggleCollisionBoxes = toggleCollisionBoxes;
|
|
1482
|
+
window.toggleOverdrawInspector = toggleOverdrawInspector;
|
|
1483
|
+
window.toggleTileFade = toggleTileFade;
|
|
1484
|
+
window.applyStyleParameters = applyStyleParameters;
|
|
1485
|
+
window.resetStyleParameters = resetStyleParameters;
|
|
1486
|
+
window.toggleStyleParametersPanel = toggleStyleParametersPanel;
|
|
1487
|
+
window.toggleControlSection = toggleControlSection;
|
|
1488
|
+
window.toggleLayerControls = () => toggleControlSection('style-controls');
|
|
895
1489
|
window.hideProfile = hideProfile;
|
|
896
1490
|
window.toggleTileGrid = toggleTileGrid;
|
|
897
1491
|
window.tileGridManager = null;
|
|
1492
|
+
window.addEventListener('resize', layoutBottomOverlays);
|
|
898
1493
|
|
|
899
1494
|
initializeMap();
|