Skip to content

Commit a5cb7c5

Browse files
authored
Merge pull request #20 from hothsys/features/apply-extra-layers
Feature: Apply additional layers to map
2 parents 3d2677b + 9a0a910 commit a5cb7c5

3 files changed

Lines changed: 140 additions & 4 deletions

File tree

css/styles.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ input,textarea,select{font-family:var(--font)}
187187
#sidebar.collapsed~#sidebar-toggle{left:0}
188188

189189
/* MAP TOOLBAR */
190-
#map-toolbar{position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:5;display:flex;gap:5px;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:28px;padding:5px 8px;box-shadow:var(--shadow)}
190+
#map-toolbar{position:absolute;top:12px;left:50%;transform:translateX(-50%) translateZ(0);z-index:5;display:flex;gap:5px;align-items:center;background:var(--surface);border:1px solid var(--border);border-radius:28px;padding:5px 8px;box-shadow:var(--shadow);-webkit-font-smoothing:antialiased}
191191
.tb-btn{background:none;border:none;color:var(--muted);font-size:.73rem;font-weight:500;padding:5px 10px;border-radius:20px;transition:all .15s;white-space:nowrap}
192192
.tb-btn:disabled{opacity:0.35;cursor:not-allowed;pointer-events:none}
193193
.tb-btn:hover{background:var(--surface2);color:var(--text)}

js/map.js

Lines changed: 138 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ function _patchStyleWater(styleObj) {
107107
}
108108
// Apple Maps Dark palette — neutral dark land, distinctly blue water.
109109
// Colors pre-compensated for canvas filter brightness(1.8) contrast(0.9).
110-
if (_mapStyle === 'dark' || _mapStyle === 'terrain3d') {
111-
// Darker water for both Dark Map and 3D Terrain modes
112-
const waterColor = _mapStyle === 'terrain3d' ? '#1a2e3d' : '#131619';
110+
if (_mapStyle === 'dark') {
111+
// Darker water for Dark Map
112+
const waterColor = '#131619';
113113
for (const layer of styleObj.layers) {
114114
if (!layer.paint) layer.paint = {};
115115
if (layer.type === 'fill' && /^water/.test(layer.id)) {
@@ -273,6 +273,120 @@ function toggleLabels() {
273273
}
274274
}
275275

276+
// Apply vivid parks, airport runways, mountain peaks, and optional 3D buildings
277+
function applyExtraLayers() {
278+
const hasOmt = map.getSource && map.getSource('openmaptiles');
279+
if (!hasOmt || ['satellite', 'globe'].includes(_mapStyle)) return;
280+
281+
// Vivid park fill — more saturated green over the base park layer
282+
if (!map.getLayer('matrix-park-vivid')) {
283+
map.addLayer({
284+
id: 'matrix-park-vivid',
285+
type: 'fill',
286+
source: 'openmaptiles',
287+
'source-layer': 'park',
288+
minzoom: 5,
289+
paint: {
290+
'fill-color': '#5aab5a',
291+
'fill-opacity': ['interpolate', ['linear'], ['zoom'], 5, 0.08, 12, 0.18]
292+
}
293+
}, 'park_outline');
294+
}
295+
296+
// Airport runway & taxiway fill — visible from zoom 9+
297+
if (!map.getLayer('matrix-aeroway-fill')) {
298+
map.addLayer({
299+
id: 'matrix-aeroway-fill',
300+
type: 'fill',
301+
source: 'openmaptiles',
302+
'source-layer': 'aeroway',
303+
minzoom: 9,
304+
filter: ['match', ['geometry-type'], ['Polygon', 'MultiPolygon'], true, false],
305+
paint: {
306+
'fill-color': '#d8d4cb',
307+
'fill-opacity': 0.6
308+
}
309+
}, 'aeroway_fill');
310+
}
311+
if (!map.getLayer('matrix-aeroway-runway')) {
312+
map.addLayer({
313+
id: 'matrix-aeroway-runway',
314+
type: 'line',
315+
source: 'openmaptiles',
316+
'source-layer': 'aeroway',
317+
minzoom: 10,
318+
filter: ['==', ['get', 'class'], 'runway'],
319+
paint: {
320+
'line-color': '#c8c4bb',
321+
'line-width': ['interpolate', ['exponential', 1.2], ['zoom'], 10, 1, 18, 12]
322+
}
323+
});
324+
}
325+
326+
// Mountain peak labels — shows summit names at zoom 10+
327+
if (!map.getLayer('matrix-mountain-peaks')) {
328+
map.addLayer({
329+
id: 'matrix-mountain-peaks',
330+
type: 'symbol',
331+
source: 'openmaptiles',
332+
'source-layer': 'poi',
333+
minzoom: 10,
334+
filter: ['==', ['get', 'class'], 'mountain'],
335+
layout: {
336+
'text-field': ['coalesce', ['get', 'name_en'], ['get', 'name']],
337+
'text-font': ['Noto Sans Bold'],
338+
'text-size': 11,
339+
'text-anchor': 'top',
340+
'text-offset': [0, 0.5],
341+
'icon-image': 'mountain_11',
342+
'icon-size': 0.8,
343+
'icon-allow-overlap': false,
344+
'text-allow-overlap': false
345+
},
346+
paint: {
347+
'text-color': _mapStyle === 'dark' ? '#a0b8c0' : '#5a7a8a',
348+
'text-halo-color': _mapStyle === 'dark' ? '#1a2530' : '#ffffff',
349+
'text-halo-width': 1.5
350+
}
351+
});
352+
}
353+
354+
// 3D buildings — only when toggled on, zoom 15+
355+
apply3DBuildings();
356+
}
357+
358+
function apply3DBuildings() {
359+
if (!map.getSource || !map.getSource('openmaptiles')) return;
360+
const shouldShow = buildings3DVisible && !['satellite', 'terrain3d', 'globe'].includes(_mapStyle);
361+
if (shouldShow && !map.getLayer('matrix-buildings-3d')) {
362+
// Insert below the first symbol layer so road/POI labels render on top of buildings
363+
const firstSymbol = map.getStyle()?.layers?.find(l => l.type === 'symbol');
364+
map.addLayer({
365+
id: 'matrix-buildings-3d',
366+
type: 'fill-extrusion',
367+
source: 'openmaptiles',
368+
'source-layer': 'building',
369+
minzoom: 15,
370+
paint: {
371+
'fill-extrusion-color': _mapStyle === 'dark' ? '#2a2e3a' : '#d9d6d0',
372+
'fill-extrusion-height': ['get', 'render_height'],
373+
'fill-extrusion-base': ['get', 'render_min_height'],
374+
'fill-extrusion-opacity': 0.75
375+
}
376+
}, firstSymbol?.id);
377+
} else if (!shouldShow && map.getLayer('matrix-buildings-3d')) {
378+
map.removeLayer('matrix-buildings-3d');
379+
}
380+
const btn = document.getElementById('tb-3d-buildings');
381+
if (btn) btn.style.opacity = buildings3DVisible ? '1' : '.4';
382+
}
383+
384+
function toggle3DBuildings() {
385+
buildings3DVisible = !buildings3DVisible;
386+
localStorage.setItem('matrix-3d-buildings', buildings3DVisible ? 'visible' : 'hidden');
387+
apply3DBuildings();
388+
}
389+
276390
function addPinLayers() {
277391
if (!map.getSource('photo-pins')) {
278392
map.addSource('photo-pins', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } });
@@ -383,6 +497,7 @@ async function _doStyleSwap(style) {
383497
raiseLabelsAboveRoads();
384498
applyLabelScale();
385499
applyLabelVisibility();
500+
applyExtraLayers();
386501
// Re-add pin icons from pixel cache (fast) and refresh clusters
387502
// without rebuilding the Supercluster index (unchanged)
388503
// Update dark-map CSS class after pin icons are re-added with correct compensation
@@ -547,9 +662,26 @@ async function initMap() {
547662
btn.addEventListener('click', toggleLabels);
548663
wrap.appendChild(btn);
549664
navGroup.after(wrap);
665+
666+
// 3D Buildings toggle — inserted before navGroup so it appears above zoom controls
667+
const bldgWrap = document.createElement('div');
668+
bldgWrap.className = 'maplibregl-ctrl maplibregl-ctrl-group';
669+
bldgWrap.id = 'buildings-toggle-wrap';
670+
const bldgBtn = document.createElement('button');
671+
bldgBtn.id = 'tb-3d-buildings';
672+
bldgBtn.type = 'button';
673+
bldgBtn.title = '3D Buildings (zoom 15+)';
674+
bldgBtn.setAttribute('aria-label', 'Toggle 3D buildings');
675+
bldgBtn.style.opacity = buildings3DVisible ? '1' : '.4';
676+
bldgBtn.innerHTML = '<span style="font-size:11px;font-weight:700;line-height:29px;display:block;color:var(--text);opacity:.7;font-family:var(--font)">3D</span>';
677+
bldgBtn.addEventListener('click', toggle3DBuildings);
678+
bldgWrap.appendChild(bldgBtn);
679+
if (['satellite', 'terrain3d', 'globe'].includes(_mapStyle)) bldgWrap.style.display = 'none';
680+
navGroup.before(bldgWrap);
550681
}
551682
applyLabelScale();
552683
applyLabelVisibility();
684+
applyExtraLayers();
553685
_applyTerrainAndProjection();
554686
// Tile loading spinner
555687
const tileSpinner = document.getElementById('tile-spinner');
@@ -751,6 +883,9 @@ function setMapStyle(mode) {
751883
// Labels toggle visibility
752884
const labelsWrap = document.getElementById('labels-toggle-wrap');
753885
if (labelsWrap) labelsWrap.style.visibility = _mapStyle === 'satellite' ? 'hidden' : 'visible';
886+
// Hide 3D buildings toggle in modes where buildings don't make sense
887+
const bldgWrap = document.getElementById('buildings-toggle-wrap');
888+
if (bldgWrap) bldgWrap.style.display = ['satellite', 'terrain3d', 'globe'].includes(_mapStyle) ? 'none' : '';
754889

755890
// Disable Export Video in Globe mode (flyTo animation doesn't translate to globe projection)
756891
const exportBtn = document.getElementById('tb-export-video');

js/state.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ let pinPickerCoords = null;
3535
// map style + labels
3636
let _mapStyle = 'dark'; // 'dark' | 'light' | 'enriched' | 'satellite'
3737
let labelsVisible = (() => { const v = localStorage.getItem('matrix-labels'); return v === null || v === 'visible'; })();
38+
let buildings3DVisible = (() => localStorage.getItem('matrix-3d-buildings') === 'visible')();
3839
// helpers
3940
let toastT;
4041

0 commit comments

Comments
 (0)