diff --git a/tools/brouter-web-staging/brouter-web/baldav-custom.js b/tools/brouter-web-staging/brouter-web/baldav-custom.js index 2d2786e..77c0674 100644 --- a/tools/brouter-web-staging/brouter-web/baldav-custom.js +++ b/tools/brouter-web-staging/brouter-web/baldav-custom.js @@ -131,6 +131,68 @@ var d = prev.distanceTo(cur); if (!d) return 0; return 100 * (((cur.alt) || 0) - ((prev.alt) || 0)) / d; // SIGNED grade % } + + // --- Dual-sided route coloring: SLOPE offset to the LEFT, SURFACE offset to the RIGHT --- + // (task: see gradient AND surface at once). Surface comes from the per-segment WayTags in + // feature.properties.messages[i][9]; offset is in pixels, recomputed on zoom. + var SURF_COL = { + asphalt: "#455a64", concrete: "#455a64", paved: "#455a64", paving_stones: "#455a64", + compacted: "#6d4c41", ground: "#8d6e63", dirt: "#8d6e63", earth: "#8d6e63", soil: "#8d6e63", + cobblestone: "#78909c", sett: "#78909c", gravel: "#b0bec5", fine_gravel: "#b0bec5", pebblestone: "#b0bec5", + clay: "#a1887f", grass: "#7cb342", grass_paver: "#7cb342", wood: "#a1887f", + sand: "#ffca28", mud: "#ff7043" + }; + function surfColor(wayTags) { + var m = /(?:^|\s)surface=([a-z_]+)/.exec(wayTags || ""); + return (m && SURF_COL[m[1]]) || "#9e9e9e"; // grey = unknown surface + } + function slopeBandColor(gpct) { var i = Math.min(10, Math.floor(Math.abs(gpct || 0))); return gpct < 0 ? DOWN[i] : UP[i]; } + function offsetLL(map, lls, px) { + var pts = [], i; + for (i = 0; i < lls.length; i++) pts.push(map.latLngToLayerPoint(lls[i])); + var out = []; + for (i = 0; i < pts.length; i++) { + var a = pts[Math.max(0, i - 1)], b = pts[Math.min(pts.length - 1, i + 1)]; + var dx = b.x - a.x, dy = b.y - a.y, len = Math.sqrt(dx * dx + dy * dy) || 1; + out.push(map.layerPointToLatLng(L.point(pts[i].x + (-dy / len) * px, pts[i].y + (dx / len) * px))); + } + return out; + } + var dualData = null, dualGroup = null, dualHooked = false; + function buildDual() { + try { + var map = window.BR && BR.__map; + if (!map || !dualData || !dualData.length) return; + if (dualGroup) { map.removeLayer(dualGroup); dualGroup = null; } + var g = L.layerGroup(), OFF = 5; + dualData.forEach(function (seg) { + var lls = seg.lls, msgs = seg.msgs; + if (!lls || lls.length < 2) return; + var L1 = offsetLL(map, lls, -OFF), R1 = offsetLL(map, lls, OFF); + for (var i = 0; i < lls.length - 1; i++) { + var d = lls[i].distanceTo(lls[i + 1]) || 1; + var a0 = lls[i].alt, a1 = lls[i + 1].alt; + var gpct = (a0 == null || a1 == null) ? 0 : 100 * (a1 - a0) / d; + L.polyline([L1[i], L1[i + 1]], { color: slopeBandColor(gpct), weight: 3, opacity: 0.95, interactive: false, lineCap: "butt" }).addTo(g); + var wt = (msgs && msgs[i]) ? msgs[i][9] : ""; + L.polyline([R1[i], R1[i + 1]], { color: surfColor(wt), weight: 3, opacity: 0.95, interactive: false, lineCap: "butt" }).addTo(g); + } + }); + g.addTo(map); dualGroup = g; + } catch (e) {} + } + function captureDual(segments) { + dualData = []; + for (var i = 0; segments && i < segments.length; i++) { + var seg = segments[i], r = seg && seg._routing; + if (r && r.beeline) continue; + if (!seg || !seg.getLatLngs) continue; + var msgs = (seg.feature && seg.feature.properties && seg.feature.properties.messages) || null; + dualData.push({ lls: seg.getLatLngs(), msgs: msgs ? msgs.slice(1) : null }); // drop header row + } + if (!dualHooked && window.BR && BR.__map) { dualHooked = true; BR.__map.on("zoomend", buildDual); } + buildDual(); + } // 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 @@ -175,10 +237,11 @@ // 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; } + // dual-sided: slope offset-left + surface offset-right (managed on the map itself, + // rebuilt on zoom); the overlay keeps only the neutral black/white centre casing. + try { captureDual(segments); } catch (e) {} + try { return makeCasings(segments); } catch (e) { return []; } }; pr.__baldavCased = true; }