diff --git a/tools/brouter-web/baldav-custom.js b/tools/brouter-web/baldav-custom.js index d4417e0..8eb8a97 100644 --- a/tools/brouter-web/baldav-custom.js +++ b/tools/brouter-web/baldav-custom.js @@ -30,6 +30,18 @@ // elevation SVG axis text (was black on dark) "body.bw-dark #elevation-chart text,body.bw-dark #elevation-chart .tick text,body.bw-dark #elevation-chart .axis text{fill:#dfe3e8!important}", "body.bw-dark #elevation-chart .tick line,body.bw-dark #elevation-chart .domain,body.bw-dark #elevation-chart path.grid,body.bw-dark #elevation-chart line{stroke:#3a4048!important}", + // hover tooltip on the elevation chart (.focusbox): its rect is white and our + // .elevation text rule turned the text light — light-on-light, unreadable. Give the + // box a dark ground and keep the text light. Same for the on-line height label. + "body.bw-dark .focusbox rect{fill:rgba(32,36,42,.92)!important;stroke:#667!important}", + "body.bw-dark .focusbox,body.bw-dark .focusbox text{fill:#dfe3e8!important}", + "body.bw-dark .height-focus.label rect{fill:rgba(32,36,42,.9)!important;stroke:#667!important}", + "body.bw-dark .height-focus.label text{fill:#dfe3e8!important}", + // guide lines/markers were near-black (#141414) — invisible on the dark chart + "body.bw-dark .focusLine line,body.bw-dark .height-focus.line{stroke:#aab4be!important}", + "body.bw-dark .height-focus.circle{stroke:#20242a!important}", + // lift the on-map hover box above the route so it doesn't cover the line (both themes) + ".height-focus.label{transform:translateY(-42px)}", // right vertical sidebar tab strip (layers/settings/data/stats icons) "body.bw-dark .leaflet-sidebar-tabs,body.bw-dark .leaflet-sidebar-tabs>ul,body.bw-dark .leaflet-sidebar-tabs li,body.bw-dark .leaflet-sidebar-tabs li a{background:#20242a!important;color:#dfe3e8!important}", "body.bw-dark .leaflet-sidebar-tabs li.active a,body.bw-dark .leaflet-sidebar-tabs li a:hover{background:#2b3038!important;color:#fff!important}", @@ -88,15 +100,129 @@ try { localStorage.setItem("baldav_bw_overlays_v1", "1"); } catch (e) {} return true; } + // --- Route colored by slope, BalDav palette (task: paint the route by gradient, not + // elevation). brouter-web's "Route quality" overlay uses a HotLineQualityProvider; its + // `incline` provider colors by SIGNED incline in degrees (blue→green→red). We repoint it + // at |grade %| with BalDav's discrete bands (green→yellow→red→brown→black, ≥10% black). + // The provider reads hotlineOptions.{min,max,palette} and valueFunction directly at draw + // time (computeLayers → L.hotline), so mutating them is enough. Registered via addInitHook + // BEFORE the control is constructed, so the very first draw already uses our palette. + var BANDS = ["#2e7d32","#7cb342","#aeea00","#ffeb3b","#ffc107","#ff9800","#ff5722","#e53935","#b71c1c","#6d4c41","#000000"]; + function slopePalette() { + // step function over normalized [0,1] == grade 0..10%: band k holds across [k,k+1)% + var p = {}, eps = 0.0009; + for (var k = 0; k < 10; k++) { p[k / 10] = BANDS[k]; p[(k + 1) / 10 - eps] = BANDS[k]; } + p[1] = BANDS[10]; + return p; + } + function slopeValue(cur, prev) { + if (!cur || !prev) return 0; + var d = prev.distanceTo(cur); if (!d) return 0; + return Math.abs(100 * (((cur.alt) || 0) - ((prev.alt) || 0)) / d); // |grade %| + } + // White+black casing under the colored line so it reads on any background. leaflet-hotline + // has only ONE outlineColor, so we render the colored core thin (no built-in outline) and + // add two plain polylines beneath it in a dedicated pane below the hotline pane: a wide + // black one and a narrower white one — black rim, white rim, colored core. + var CASE_PANE = "baldavCasingPane"; + function ensureCasingPane() { + var m = window.BR && BR.__map; + if (!m) return null; + if (!m.getPane(CASE_PANE)) { + m.createPane(CASE_PANE); + var p = m.getPane(CASE_PANE); + if (p) p.style.zIndex = 449; // routingQualityPane is 450 → casings sit just under it + } + return m.getPane(CASE_PANE) ? CASE_PANE : null; + } + function makeCasings(segments) { + var pane = ensureCasingPane(), out = []; + for (var i = 0; segments && i < segments.length; i++) { + var seg = segments[i], r; + if (!seg || !seg.getLatLngs) continue; + if (null !== (r = seg._routing) && void 0 !== r && r.beeline) continue; // skip beelines, as brouter does + var lls = seg.getLatLngs(); + var base = { opacity: 1, interactive: false, lineCap: "round", lineJoin: "round" }; + if (pane) base.pane = pane; + out.push(L.polyline(lls, L.extend({}, base, { color: "#000000", weight: 12 }))); + out.push(L.polyline(lls, L.extend({}, base, { color: "#ffffff", weight: 8 }))); + } + return out; + } + function applySlopeProvider(ctrl) { + try { + var pr = ctrl && ctrl.providers && ctrl.providers.incline && ctrl.providers.incline.provider; + if (!pr || !pr.hotlineOptions) return false; + pr.hotlineOptions.min = 0; + pr.hotlineOptions.max = 10; + pr.hotlineOptions.palette = slopePalette(); + pr.hotlineOptions.weight = 4; // thin colored core over the casings + pr.hotlineOptions.outlineWidth = 0; // no built-in outline; casings provide it + pr.valueFunction = slopeValue; + ctrl._initialProvider = "incline"; + ctrl.selectedProvider = "incline"; + // Prepend black+white casings to whatever hotlines the provider computes (added first + // = under the colored line). Wrap once. + if (!pr.__baldavCased && typeof pr.computeLayers === "function") { + var orig = pr.computeLayers.bind(pr); + pr.computeLayers = function (segments) { + var hotlines = orig(segments); + try { return makeCasings(segments).concat(hotlines); } catch (e) { return hotlines; } + }; + pr.__baldavCased = true; + } + return true; + } catch (e) { return false; } + } + var rqHooked = false, mapHooked = false; + function registerRouteQuality() { + // Capture the map instance as it's created (needed to make the casing pane). + if (!mapHooked && window.L && L.Map && L.Map.addInitHook) { + mapHooked = true; + L.Map.addInitHook(function () { window.BR = window.BR || {}; BR.__map = this; }); + } + if (rqHooked) return; + if (!(window.BR && BR.RoutingPathQuality && BR.RoutingPathQuality.addInitHook)) return; + rqHooked = true; + BR.RoutingPathQuality.addInitHook(function () { applySlopeProvider(this); BR.__quality = this; }); + } + // Best-effort: auto-enable the "Route quality" overlay so the route shows slope out of the + // box. Its checkbox label is localized (locale files), so match a few languages; if it + // isn't found the palette override still applies once the user turns it on (button / key C). + function enableRouteQuality() { + try { if (localStorage.getItem("baldav_bw_rq_v1") === "1") return true; } catch (e) {} + var labels = document.querySelectorAll("#layers-control-wrapper label, .leaflet-control-layers-overlays label"); + if (!labels.length) return false; + var re = /route.?quality|streckenqualit|качеств/i, done = false; + labels.forEach(function (lab) { + if (!re.test((lab.textContent || ""))) return; + var cb = lab.querySelector("input[type=checkbox]") || lab.control || (lab.htmlFor && document.getElementById(lab.htmlFor)); + if (cb && cb.type === "checkbox" && !cb.checked) { cb.click(); } + done = true; + }); + if (done) { try { localStorage.setItem("baldav_bw_rq_v1", "1"); } catch (e) {} } + return done; + } + function init() { + registerRouteQuality(); injectStyle(); apply(); var tries = 0; var iv = setInterval(function () { tries++; var b = addButton(); var o = setDefaultOverlays(); - if ((b && o) || tries > 60) clearInterval(iv); + // If the control got built before our hook (shouldn't, but be safe), patch it live. + if (window.BR && BR.__quality && !BR.__quality.__baldavSlope) { + if (applySlopeProvider(BR.__quality)) BR.__quality.__baldavSlope = true; + } + var q = enableRouteQuality(); + if ((b && o && q) || tries > 60) clearInterval(iv); }, 500); } + // Register the route-quality hook AT SCRIPT-EXECUTION TIME (bw.js has already defined + // BR.RoutingPathQuality by now, and the control is only constructed later on DOM ready) — + // so the slope palette is in place before its first draw. + registerRouteQuality(); if (document.readyState !== "loading") init(); else document.addEventListener("DOMContentLoaded", init); })();