diff --git a/frontend/css/styles.css b/frontend/css/styles.css
index 60e6ebb..d79417f 100644
--- a/frontend/css/styles.css
+++ b/frontend/css/styles.css
@@ -1318,6 +1318,8 @@ select {
/* Left-hand group (bottom-left), opposite the right control stack: SMS + live toggle. */
.rec-sms-btn { left: 8px; bottom: 48px; font-size: 11px; }
.rec-live-btn { left: 8px; bottom: 86px; font-size: 10px; }
+/* Route editor: danger indicator (fords / rough tracks the route passes). */
+.re-danger { color: #ff7043; font-size: 13px; font-weight: 600; min-height: 16px; margin: 2px 0; text-align: center; }
/* Dark map: invert ONLY the tile layer (track, route, puck keep their colours).
User-tuned filter on OSM standard tiles. Applies to every Leaflet map. */
body.dark-map .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(1.5); }
diff --git a/frontend/js/gps.js b/frontend/js/gps.js
index 4634b25..b9f8dcf 100644
--- a/frontend/js/gps.js
+++ b/frontend/js/gps.js
@@ -1780,6 +1780,50 @@ GPS.parseRouteGPX = function(xmlString) {
return { name, points };
};
+// Parse a BRouter GeoJSON response (format=geojson / OsmTrack.formatAsGeoJson). Returns
+// { name, points:[{lat,lon,ele}], dangers:[{lat,lon,type,tag}] }. Danger comes from the
+// per-segment messages table (WayTags/NodeTags): fords (water), rough/abandoned tracks,
+// and (informational) ferries — so the rider sees the "ад" BEFORE riding it. Geometry is
+// identical to the GPX, so this can replace parseRouteGPX where tags are wanted.
+GPS.parseRouteGeoJson = function(jsonString) {
+ let d;
+ try { d = (typeof jsonString === "string") ? JSON.parse(jsonString) : jsonString; } catch { return null; }
+ let feat = d && d.features && d.features[0];
+ if (!feat) return null;
+ let coords = (feat.geometry && feat.geometry.coordinates) || [];
+ let points = coords.map(c => {
+ let p = { lon: c[0], lat: c[1] };
+ if (c.length > 2 && c[2] != null && isFinite(c[2])) p.ele = c[2];
+ return p;
+ });
+ let name = (feat.properties && feat.properties.name) || "";
+ let dangers = [];
+ let msgs = feat.properties && feat.properties.messages;
+ if (Array.isArray(msgs) && msgs.length > 1) {
+ let hdr = msgs[0];
+ let li = hdr.indexOf("Longitude"), ai = hdr.indexOf("Latitude");
+ let wi = hdr.indexOf("WayTags"), ni = hdr.indexOf("NodeTags");
+ for (let r = 1; r < msgs.length; r++) {
+ let row = msgs[r];
+ let wt = (wi >= 0 ? row[wi] : "") || "";
+ let nt = (ni >= 0 ? row[ni] : "") || "";
+ let both = wt + " " + nt;
+ let type = null;
+ if (/\bford=(yes|stepping_stones)\b/.test(both)) type = "ford";
+ else if (/\btracktype=grade[45]\b/.test(wt) || /\bsurface=(mud|sand)\b/.test(wt)
+ || /\btrail_visibility=(bad|horrible|no)\b/.test(wt)
+ || /\bsac_scale=(demanding_mountain_hiking|alpine_hiking|demanding_alpine_hiking|difficult_alpine_hiking)\b/.test(wt)
+ || /\bmtb:scale=[3-6]\b/.test(wt)) type = "rough";
+ else if (/\broute=ferry\b/.test(wt)) type = "ferry";
+ if (type && li >= 0 && ai >= 0) {
+ let lon = parseFloat(row[li]) / 1e6, lat = parseFloat(row[ai]) / 1e6;
+ if (isFinite(lat) && isFinite(lon)) dangers.push({ lat, lon, type, tag: (wt || nt).trim() });
+ }
+ }
+ }
+ return { name, points, dangers };
+};
+
// Fill GPS dropouts along roads: for each REAL signal loss (a long time gap with a large
// chord), route the SHORTEST road path between the pre/post-gap points and splice the routed
// points in (flagged estimated). Deliberately conservative so normal slow/dense riding is
diff --git a/frontend/js/main.js b/frontend/js/main.js
index 75d0202..66641c9 100644
--- a/frontend/js/main.js
+++ b/frontend/js/main.js
@@ -3852,6 +3852,12 @@ function slopeLegendHtml() {
return `подъём ${strip(ROUTE_SLOPE_UP)} крутой спуск ${strip(ROUTE_SLOPE_DOWN)} крутой`;
}
+// Which BRouter profile to route with: the adventure variant (fords/grade5/bad trails
+// allowed) when the rider opted in, else the default trekking (avoids them).
+function trekProfile() {
+ return localStorage.getItem("baldav_allow_adventure") === "1" ? "trekking-adventure" : "trekking";
+}
+
// Draw a route as a feature group: a wide colored casing (per segment — by slope
// or by elevation, per baldav_route_color_mode) UNDER a thin identity-color line,
// when the route has elevations; otherwise just the plain colored line. The casing
@@ -4207,6 +4213,8 @@ async function showRouteEditor(container, routeId) {
let displayLayer = null; // the visible route (casing+slope+thin line) via drawRoutePolyline
let legMode = []; // "road" | "straight", length = points.length - 1
let legGeom = []; // routed [{lat,lon,ele}] per road leg, or null
+ let legDangers = []; // [{lat,lon,type}] per road leg (fords/rough from BRouter tags)
+ let dangerLayer = null; // danger markers overlay
let defaultMode = "road";
let routeTimer = null;
let dirty = new Set();
@@ -4223,8 +4231,10 @@ async function showRouteEditor(container, routeId) {
тап карты — точка · тап линии — вставить точку · долгий тап — дорога/прямая · тап точки — удалить
+
+
@@ -4249,8 +4259,8 @@ async function showRouteEditor(container, routeId) {
function normalizeLegs() {
let need = Math.max(0, points.length - 1);
- while (legMode.length < need) { legMode.push(defaultMode); legGeom.push(null); }
- legMode.length = need; legGeom.length = need;
+ while (legMode.length < need) { legMode.push(defaultMode); legGeom.push(null); legDangers.push(null); }
+ legMode.length = need; legGeom.length = need; legDangers.length = need;
}
// Drawn/saved geometry for a leg: routed pts for a routed road leg (carry ele), else the
// straight chord (also the fallback when a road leg couldn't be routed).
@@ -4281,6 +4291,27 @@ async function showRouteEditor(container, routeId) {
let mode = localStorage.getItem("baldav_route_color_mode") || "slope";
displayLayer = drawRoutePolyline(map, fullGeom(), color, mode);
}
+ drawDangers();
+ }
+ // Mark fords (water) and rough/abandoned tracks the route passes, so the rider sees the
+ // "ад" BEFORE riding it. Counter in the stats row. Fed by BRouter WayTags per leg.
+ function drawDangers() {
+ if (dangerLayer) { map.removeLayer(dangerLayer); dangerLayer = null; }
+ let all = [];
+ for (let d of legDangers) if (Array.isArray(d)) all = all.concat(d);
+ let fords = all.filter(x => x.type === "ford").length;
+ let rough = all.filter(x => x.type === "rough").length;
+ let dEl = document.querySelector("#re-danger");
+ if (dEl) dEl.textContent = (fords || rough) ? `⚠ брод: ${fords} · грунт: ${rough}` : "";
+ if (!all.length) return;
+ let g = L.layerGroup();
+ for (let d of all) {
+ let col = d.type === "ford" ? "#00bcd4" : (d.type === "rough" ? "#ff9800" : "#9c27b0");
+ let label = d.type === "ford" ? "брод" : (d.type === "rough" ? "грунт/тропа" : "паром");
+ L.circleMarker([d.lat, d.lon], { radius: 7, color: "#fff", weight: 2, fillColor: col, fillOpacity: 1 })
+ .bindTooltip(label, { direction: "top" }).addTo(g);
+ }
+ g.addTo(map); dangerLayer = g;
}
// Fat transparent hit target per leg (tap = insert, long-press = toggle road/straight).
// pointer-events:stroke (CSS .re-hit) makes the invisible fat stroke clickable.
@@ -4309,10 +4340,13 @@ async function showRouteEditor(container, routeId) {
try { ridden = await rideRoute(a.lat, a.lon, b.lat, b.lon); } catch (e) { /* fall back */ }
if (ridden && ridden.length >= 2) { legGeom[i] = ridden.map(p => ({ lat: p.lat, lon: p.lon })); continue; }
try {
- let gpx = await BRouter.route([[a.lon, a.lat], [b.lon, b.lat]], "trekking", nogos);
- let rp = (GPS.parseRouteGPX(gpx) || {}).points || [];
+ // GeoJSON carries per-segment WayTags → detect fords / rough tracks on this leg.
+ let gj = await BRouter.routeGeoJson([[a.lon, a.lat], [b.lon, b.lat]], trekProfile(), nogos);
+ let parsed = GPS.parseRouteGeoJson(gj) || {};
+ let rp = parsed.points || [];
legGeom[i] = (rp.length >= 2) ? rp.map(p => ({ lat: p.lat, lon: p.lon, ele: p.ele })) : null; // keep ele for slope
- } catch (e) { legGeom[i] = null; } // silent fallback to straight (no road / no segments)
+ legDangers[i] = (rp.length >= 2) ? (parsed.dangers || []) : null;
+ } catch (e) { legGeom[i] = null; legDangers[i] = null; } // silent fallback to straight
}
redraw(); updateStats();
}
@@ -4472,6 +4506,20 @@ async function showRouteEditor(container, routeId) {
redraw(); updateStats(); if (defaultMode === "road") scheduleRoute();
};
+ // Broды/сложные тропы: избегать (default) vs разрешить (adventure). Re-routes all road
+ // legs with the chosen profile; danger markers show what the route then passes.
+ let advBtn = document.querySelector("#re-adventure");
+ let advOn = localStorage.getItem("baldav_allow_adventure") === "1";
+ let syncAdv = () => { advBtn.textContent = advOn ? "Броды: разрешить" : "Броды: избегать"; advBtn.classList.toggle("active", advOn); };
+ syncAdv();
+ advBtn.onclick = () => {
+ advOn = !advOn;
+ localStorage.setItem("baldav_allow_adventure", advOn ? "1" : "0");
+ syncAdv();
+ for (let i = 0; i < legMode.length; i++) if (legMode[i] === "road") { legGeom[i] = null; legDangers[i] = null; markDirty(i); }
+ scheduleRoute();
+ };
+
document.querySelector("#re-save").onclick = async () => {
if (points.length < 2) { showToast("Нужно минимум 2 точки"); return; }
let btn = document.querySelector("#re-save");
diff --git a/frontend/js/router.js b/frontend/js/router.js
index 1d8be2f..38c5715 100644
--- a/frontend/js/router.js
+++ b/frontend/js/router.js
@@ -57,6 +57,42 @@ window.BRouter = {
}
},
+ async _routeServerGeoJson(pts, profile, nogos) {
+ let lonlats = pts.map(p => p[0] + "," + p[1]).join("|");
+ for (let base of this._servers) {
+ try {
+ let url = base + "/brouter?lonlats=" + encodeURIComponent(lonlats) + "&profile=" + profile + "&alternativeidx=0&format=geojson" + this._nogoParam(nogos);
+ let gj = await window.__TAURI__.core.invoke("http_request", { url, method: "GET" });
+ if (gj && gj.indexOf("FeatureCollection") >= 0) return gj;
+ } catch (e) { /* try next server */ }
+ }
+ return null;
+ },
+
+ // Like route() but returns the GeoJSON string (per-segment WayTags) for danger detection.
+ // Parse with GPS.parseRouteGeoJson. Same engine/RouteServer fallback as route().
+ async routeGeoJson(points, profile, nogos) {
+ let pts = (points || []).map(p => [p[0], p[1]]);
+ profile = profile || "trekking";
+ nogos = (nogos || []).filter(n => n && n.lat != null && n.lon != null);
+ let isAndroid = /Android/i.test(navigator.userAgent || "");
+ if (!isAndroid) {
+ let gj = await this._routeServerGeoJson(pts, profile, nogos);
+ if (gj) return gj;
+ throw new Error("brouter: RouteServer unreachable");
+ }
+ try {
+ let raw = await window.__TAURI__.core.invoke("brouter_route", { lonlats: JSON.stringify(pts), profile, nogos: JSON.stringify(nogos), format: "geojson" });
+ let res = JSON.parse(raw);
+ if (res.ok && res.geojson) return res.geojson;
+ throw new Error(res.error || "engine error");
+ } catch (e) {
+ let gj = await this._routeServerGeoJson(pts, profile, nogos);
+ if (gj) return gj;
+ throw new Error("brouter: " + (e.message || e));
+ }
+ },
+
// --- Segment (.rd5) delivery (Stage 2) — Kotlin downloads into files/brouter/segments4 ---
// Kick off a tile download (e.g. "E35_N45"); returns immediately. Poll downloadStatus.
diff --git a/src-tauri/gen/android/app/src/main/assets/brouter/profiles2/trekking-adventure.brf b/src-tauri/gen/android/app/src/main/assets/brouter/profiles2/trekking-adventure.brf
new file mode 100644
index 0000000..73e06f1
--- /dev/null
+++ b/src-tauri/gen/android/app/src/main/assets/brouter/profiles2/trekking-adventure.brf
@@ -0,0 +1,449 @@
+# *** The trekking profile is for slow travel
+# *** and avoiding car traffic, but still with
+# *** a focus on approaching your destination
+# *** efficiently.
+
+---context:global # following code refers to global config
+
+# Bike profile
+assign validForBikes = true
+
+# Use the following switches to change behaviour
+assign allow_steps = true # %allow_steps% | Set false to disallow steps | boolean
+assign allow_ferries = true # %allow_ferries% | Set false to disallow ferries | boolean
+assign ignore_cycleroutes = false # %ignore_cycleroutes% | Set true for better elevation results | boolean
+assign stick_to_cycleroutes = false # %stick_to_cycleroutes% | Set true to just follow cycleroutes | boolean
+assign use_proposed_cycleroutes = false # %use_proposed_cycleroutes% | Set to true to consider proposed and regular cycleroutes as equals | boolean
+assign avoid_unsafe = false # %avoid_unsafe% | Set true to avoid standard highways | boolean
+
+assign add_beeline = false # %add_beeline% | Enable beeline on distant start/end points | boolean
+
+assign consider_noise = false # %consider_noise% | Activate to prefer a low-noise route | boolean
+assign consider_river = false # %consider_river% | Activate to prefer a route along rivers, lakes, etc. | boolean
+assign consider_forest = false # %consider_forest% | Activate to prefer a route in forest or parks | boolean
+assign consider_town = false # %consider_town% | Activate to bypass cities / big towns as far as possible | boolean
+assign consider_traffic = false # %consider_traffic% | Activate to consider traffic estimates | boolean
+
+
+
+# Change elevation parameters
+assign consider_elevation = true # %consider_elevation% | Set true to favor a route with few elevation meters | boolean
+
+assign downhillcost = 60 # %downhillcost% | Cost for going downhill | number
+assign downhillcutoff = 1.5 # %downhillcutoff% | Gradients below this value in percents are not counted. | number
+assign uphillcost = 0 # %uphillcost% | Cost for going uphill | number
+assign uphillcutoff = 1.5 # %uphillcutoff% | Gradients below this value in percents are not counted. | number
+
+assign downhillcost = if consider_elevation then downhillcost else 0
+assign uphillcost = if consider_elevation then uphillcost else 0
+
+# Kinematic model parameters (travel time computation)
+assign totalMass = 90 # %totalMass% | Mass (in kg) of the bike + biker, for travel time computation | number
+assign maxSpeed = 45 # %maxSpeed% | Absolute maximum speed (in km/h), for travel time computation | number
+assign S_C_x = 0.225 # %S_C_x% | Drag coefficient times the reference area (in m^2), for travel time computation | number
+assign C_r = 0.01 # %C_r% | Rolling resistance coefficient (dimensionless), for travel time computation | number
+assign bikerPower = 100 # %bikerPower% | Average power (in W) provided by the biker, for travel time computation | number
+
+# Turn instructions settings
+assign turnInstructionMode = 1 # %turnInstructionMode% | Mode for the generated turn instructions | [0=none, 1=auto-choose, 2=locus-style, 3=osmand-style, 4=comment-style, 5=gpsies-style, 6=orux-style, 7=locus-old-style]
+assign turnInstructionCatchingRange = 40 # %turnInstructionCatchingRange% | Within this distance (in m) several turning instructions are combined into one and the turning angles are better approximated to the general direction | number
+assign turnInstructionRoundabouts = true # %turnInstructionRoundabouts% | Set "false" to avoid generating special turning instructions for roundabouts | boolean
+assign considerTurnRestrictions = true # %considerTurnRestrictions% | Set true to take turn restrictions into account | boolean
+
+# with engine defaults, long parts of routing to via points may be removed
+assign correctMisplacedViaPoints = false # %correctMisplacedViaPoints% | Set to true to remove detours due to via points (going back and forth on the same route) | boolean
+assign correctMisplacedViaPointsDistance = 400 # %correctMisplacedViaPointsDistance% | Only remove detours shorter than this (one-way) distance (in m) (engine defaults to 0 which removes detours whatever their length) | number
+
+assign processUnusedTags = false # %processUnusedTags% | Set true to output unused tags in data tab | boolean
+
+assign unpavedPenalty = 1.0 # %unpavedPenalty% | Extra penalty multiplier applied to the non-probablyGood cost of track/road/path/footway ways depending on tracktype grade | number
+
+assign allow_adventure = true # %allow_adventure% | Set true to ALLOW fords, grade5 tracks and bad-visibility trails (adventure mode). Default false avoids them. | boolean
+
+---context:way # following code refers to way-tags
+
+# classifier constants
+assign classifier_none = 1
+assign classifier_ferry = 2
+
+#
+# pre-calculate some logical expressions
+#
+
+assign any_cycleroute =
+ if not use_proposed_cycleroutes then
+ if route_bicycle_icn=yes then true
+ else if route_bicycle_ncn=yes then true
+ else if route_bicycle_rcn=yes then true
+ else if route_bicycle_lcn=yes then true
+ else false
+ else
+ if route_bicycle_icn=yes|proposed then true
+ else if route_bicycle_ncn=yes|proposed then true
+ else if route_bicycle_rcn=yes|proposed then true
+ else if route_bicycle_lcn=yes|proposed then true
+ else false
+
+assign nodeaccessgranted =
+ if any_cycleroute then true
+ else lcn=yes
+
+assign is_ldcr =
+ if ignore_cycleroutes then false
+ else any_cycleroute
+
+# set isbike considering access, local cycle route or the presence of a cycleway on the highway
+assign hasbikerouteoraccess =
+ or bicycle_road=yes or cyclestreet=yes or bicycle=yes|permissive|designated lcn=yes
+
+assign hascycleway = not
+ and ( or cycleway= cycleway=no|none ) and ( or cycleway:left= cycleway:left=no ) and ( or cycleway:right= cycleway:right=no ) ( or cycleway:both= cycleway:both=no )
+
+assign isbike = or hasbikerouteoraccess hascycleway
+
+assign ispaved = or surface=paved|asphalt|concrete|paving_stones|sett smoothness=excellent|good
+assign isunpaved = not or ispaved or ( and surface= smoothness= ) or surface=fine_gravel|cobblestone smoothness=intermediate|bad
+assign probablyGood = or ispaved and ( or isbike highway=footway ) not isunpaved
+
+
+#
+# this is the cost (in Meter) for a 90-degree turn
+# The actual cost is calculated as turncost*cos(angle)
+# (Suppressing turncost while following longdistance-cycleways
+# makes them a little bit more magnetic)
+#
+assign turncost = if is_ldcr then 0
+ else if junction=roundabout then 0
+ else 90
+
+
+#
+# for any change in initialclassifier, initialcost is added once
+#
+assign initialclassifier =
+ if route=ferry then classifier_ferry
+ else classifier_none
+
+
+#
+# calculate the initial cost
+# this is added to the total cost each time the costfactor
+# changed
+#
+# Adventure penalty (added into costfactor below): keep the router off fords, abandoned
+# (grade5) tracks and bad/invisible trails unless the rider opts into adventure mode
+# (%allow_adventure%). 10000 matches the profile's "excluded way" costfactor.
+assign adventurepenalty =
+ if allow_adventure then 0
+ else add ( if ford=yes|stepping_stones then 10000 else 0 )
+ ( if or tracktype=grade5 trail_visibility=bad|horrible|no then 5000 else 0 )
+
+assign initialcost =
+ if ( equal initialclassifier classifier_ferry ) then 10000
+ else 0
+
+#
+# implicit access here just from the motorroad tag
+# (implicit access rules from highway tag handled elsewhere)
+#
+assign defaultaccess =
+ if access= then not motorroad=yes
+ else if access=private|no then false
+ else true
+
+#
+# calculate logical bike access
+#
+assign bikeaccess =
+ if bicycle= then
+ (
+ if bicycle_road=yes then true
+ else if vehicle= then ( if highway=footway then false else defaultaccess )
+ else not vehicle=private|no
+ )
+ else not bicycle=private|no|dismount|use_sidepath
+
+#
+# calculate logical foot access
+#
+assign footaccess =
+ if bicycle=dismount then true
+ else if foot= then defaultaccess
+ else not foot=private|no|use_sidepath
+
+#
+# if not bike-, but foot-access, just a moderate penalty,
+# otherwise access is forbidden
+#
+assign accesspenalty =
+ if bikeaccess then 0
+ else if footaccess then 4
+ else if any_cycleroute then 15
+ else if bicycle=use_sidepath then 25
+ else 10000
+
+#
+# handle one-ways. On primary roads, wrong-oneways should
+# be close to forbidden, while on other ways we just add
+# 4 to the costfactor (making it at least 5 - you are allowed
+# to push your bike)
+
+# are we against legal direction on a oneway?
+assign badoneway =
+ if reversedirection=yes then
+ if oneway:bicycle=yes then true
+ else if oneway= then junction=roundabout
+ else oneway=yes|true|1
+ else oneway=-1
+
+assign onewaypenalty =
+ if ( badoneway ) then
+ (
+ if (
+ and hascycleway
+ or and cycleway:left=lane|track|shared_lane|share_busway
+ cycleway:left:oneway=no|-1
+ or and cycleway:right=lane|track|shared_lane|share_busway
+ cycleway:right:oneway=no|-1
+ or and cycleway:both=lane|track|shared_lane|share_busway
+ #or cycleway:both:oneway=no|-1
+ or cycleway:left:oneway=no|-1
+ cycleway:right:oneway=no|-1
+ or cycleway=opposite|opposite_lane|opposite_track
+ or cycleway:left=opposite|opposite_lane|opposite_track
+ cycleway:right=opposite|opposite_lane|opposite_track
+ ) then 0
+ else if ( oneway:bicycle=no ) then 0
+ else if ( not footaccess ) then 100
+ else if ( junction=roundabout|circular ) then 60
+ else if ( highway=primary|primary_link ) then 50
+ else if ( highway=secondary|secondary_link ) then 30
+ else if ( highway=tertiary|tertiary_link ) then 20
+ else 4.0
+ )
+ else 0.0
+
+# add estimate tags
+assign traffic_penalty
+ switch consider_traffic
+ switch estimated_traffic_class= 0
+ switch estimated_traffic_class=1|2 0.2
+ switch estimated_traffic_class=3 0.4
+ switch estimated_traffic_class=4 0.6
+ switch estimated_traffic_class=5 0.8
+ switch estimated_traffic_class=6|7 1 99 0
+
+
+assign noise_penalty
+ switch consider_noise
+ switch estimated_noise_class= 0
+ switch estimated_noise_class=1 0.3
+ switch estimated_noise_class=2 0.5
+ switch estimated_noise_class=3 0.8
+ switch estimated_noise_class=4 1.4
+ switch estimated_noise_class=5 1.7
+ switch estimated_noise_class=6 2 0 0
+
+assign no_river_penalty
+ switch consider_river
+ switch estimated_river_class= 2
+ switch estimated_river_class=1 1.3
+ switch estimated_river_class=2 1
+ switch estimated_river_class=3 0.7
+ switch estimated_river_class=4 0.4
+ switch estimated_river_class=5 0.1
+ switch estimated_river_class=6 0 99 0
+
+assign no_forest_penalty
+ switch consider_forest
+ switch estimated_forest_class= 1
+ switch estimated_forest_class=1 0.5
+ switch estimated_forest_class=2 0.4
+ switch estimated_forest_class=3 0.25
+ switch estimated_forest_class=4 0.15
+ switch estimated_forest_class=5 0.1
+ switch estimated_forest_class=6 0 99 0
+
+assign town_penalty
+ switch consider_town
+ switch estimated_town_class= 0
+ switch estimated_town_class=1 0.5
+ switch estimated_town_class=2 0.9
+ switch estimated_town_class=3 1.2
+ switch estimated_town_class=4 1.3
+ switch estimated_town_class=5 1.4
+ switch estimated_town_class=6 1.6 99 0
+
+#
+# calculate the cost-factor, which is the factor
+# by which the distance of a way-segment is multiplied
+# to calculate the cost of that segment. The costfactor
+# must be >=1 and it's supposed to be close to 1 for
+# the type of way the routing profile is searching for
+#
+assign isresidentialorliving = or highway=residential|living_street living_street=yes
+assign costfactor
+
+ #
+ # exclude rivers, rails etc.
+ #
+ if ( and highway= not route=ferry ) then 10000
+
+ #
+ # exclude motorways and proposed, abandoned under construction roads
+ #
+ else if ( highway=motorway|motorway_link ) then 10000
+ else if ( highway=proposed|abandoned|construction ) then 10000
+
+ #
+ # all other exclusions below (access, steps, ferries,..)
+ # should not be deleted by the decoder, to be available
+ # in voice-hint-processing
+ #
+ else min 9999
+
+ add town_penalty
+ add no_forest_penalty
+ add no_river_penalty
+ add noise_penalty
+ add traffic_penalty
+ add adventurepenalty
+
+ #
+ # apply oneway-and access-penalties
+ #
+ add max onewaypenalty accesspenalty
+
+ #
+ # steps and ferries are special. Note this is handled
+ # before the cycleroute-switch, to be able
+ # to really exclude them be setting cost to infinity
+ #
+ if ( highway=steps ) then ( if allow_steps then 40 else 10000 )
+ else if ( route=ferry ) then ( if allow_ferries then 5.67 else 10000 )
+
+ #
+ # handle long-distance cycle-routes.
+ #
+ else if ( is_ldcr ) then 1 # always treated as perfect (=1)
+ else
+ add ( if stick_to_cycleroutes then 0.5 else 0.05 ) # everything else somewhat up
+
+ #
+ # some other highway types
+ #
+ if ( highway=pedestrian ) then ( if isbike then ( if hascycleway then 1.1 else 2.2 ) else 3 )
+ else if ( highway=bridleway ) then 5
+ else if ( highway=cycleway ) then 1
+ else if ( isresidentialorliving ) then ( if isunpaved then 1.5 else 1.1 )
+ else if ( highway=service ) then ( if isunpaved then 1.6 else 1.3 )
+
+ #
+ # tracks and track-like ways are rated mainly be tracktype/grade
+ # But note that if no tracktype is given (mainly for road/path/footway)
+ # it can be o.k. if there's any other hint for quality
+ #
+ else if ( highway=track|road|path|footway ) then
+ (
+ if ( tracktype=grade1 ) then ( if probablyGood then 1.0 else add 1.0 multiply 0.3 unpavedPenalty )
+ else if ( tracktype=grade2 ) then ( if probablyGood then 1.1 else add 1.1 multiply 0.9 unpavedPenalty )
+ else if ( tracktype=grade3 ) then ( if probablyGood then 1.5 else add 1.5 multiply 1.5 unpavedPenalty )
+ else if ( tracktype=grade4 ) then ( if probablyGood then 2.0 else add 2.0 multiply 3.0 unpavedPenalty )
+ else if ( tracktype=grade5 ) then ( if probablyGood then 3.0 else add 3.0 multiply 2.0 unpavedPenalty )
+ else ( if probablyGood then 1.0 else add 1.0 multiply 4.0 unpavedPenalty )
+ )
+
+ #
+ # When avoiding unsafe ways, avoid highways without a bike hint
+ #
+ else add ( if ( and avoid_unsafe not isbike ) then 2 else 0 )
+
+ #
+ # actuals roads are o.k. if we have a bike hint
+ #
+ if ( highway=trunk|trunk_link ) then ( if isbike then 1.5 else 10 )
+ else if ( highway=primary|primary_link ) then ( if isbike then 1.2 else 3 )
+ else if ( highway=secondary|secondary_link ) then ( if isbike then 1.1 else 1.6 )
+ else if ( highway=tertiary|tertiary_link ) then ( if isbike then 1.0 else 1.4 )
+ else if ( highway=unclassified ) then ( if isbike then 1.0 else 1.3 )
+
+ #
+ # default for any other highway type not handled above
+ #
+ else 2.0
+
+
+# way priorities used for voice hint generation
+
+assign priorityclassifier =
+
+ if ( highway=motorway ) then 30
+ else if ( highway=motorway_link ) then 29
+ else if ( highway=trunk ) then 28
+ else if ( highway=trunk_link ) then 27
+ else if ( highway=primary ) then 26
+ else if ( highway=primary_link ) then 25
+ else if ( highway=secondary ) then 24
+ else if ( highway=secondary_link ) then 23
+ else if ( highway=tertiary ) then 22
+ else if ( highway=tertiary_link ) then 21
+ else if ( highway=unclassified ) then 20
+ else if ( isresidentialorliving ) then 6
+ else if ( highway=service ) then 6
+ else if ( highway=cycleway ) then 6
+ else if ( or bicycle=designated bicycle_road=yes ) then 6
+ else if ( highway=track ) then if tracktype=grade1 then 6 else 4
+ else if ( highway=bridleway|road|path|footway ) then 4
+ else if ( highway=steps ) then 2
+ else if ( highway=pedestrian ) then 2
+ else 0
+
+# some more classifying bits used for voice hint generation...
+
+assign isbadoneway = not equal onewaypenalty 0
+assign isgoodoneway = if reversedirection=yes then oneway=-1
+ else if oneway= then junction=roundabout else oneway=yes|true|1
+assign isroundabout = junction=roundabout
+assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link
+assign isgoodforcars = if greater priorityclassifier 6 then true
+ else if ( or isresidentialorliving highway=service ) then true
+ else if ( and highway=track tracktype=grade1 ) then true
+ else false
+
+# ... encoded into a bitmask
+
+assign classifiermask add isbadoneway
+ add multiply isgoodoneway 2
+ add multiply isroundabout 4
+ add multiply islinktype 8
+ multiply isgoodforcars 16
+
+# include `smoothness=` tags in the response's WayTags for track analysis
+assign dummyUsage = smoothness=
+
+---context:node # following code refers to node tags
+
+assign defaultaccess =
+ if ( access= ) then true # add default barrier restrictions here!
+ else if ( access=private|no ) then false
+ else true
+
+assign bikeaccess =
+ if nodeaccessgranted=yes then true
+ else if bicycle= then
+ (
+ if vehicle= then defaultaccess
+ else not vehicle=private|no
+ )
+ else not bicycle=private|no|dismount
+
+assign footaccess =
+ if bicycle=dismount then true
+ else if foot= then defaultaccess
+ else not foot=private|no
+
+assign initialcost =
+ add ( if allow_adventure then 0 else if ford=yes|stepping_stones then 100000 else 0 )
+ ( if or highway=traffic_signals and highway=crossing crossing=traffic_signals then 20
+ else if bikeaccess then 0
+ else ( if footaccess then 100 else 1000000 ) )
diff --git a/src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt b/src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt
index 12cf948..8ba62ac 100644
--- a/src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt
+++ b/src-tauri/gen/android/app/src/main/java/dev/baldav/app/MainActivity.kt
@@ -391,6 +391,7 @@ class MainActivity : TauriActivity() {
try {
val req = org.json.JSONObject(File(appDataDir, "brouter_request.json").readText())
val profile = req.getString("profile")
+ val format = req.optString("format", "gpx")
val arr = req.getJSONArray("lonlats")
val pts = ArrayList()
for (i in 0 until arr.length()) {
@@ -406,9 +407,9 @@ class MainActivity : TauriActivity() {
}
val profilePath = File(filesDir, "brouter/profiles2/$profile.brf").absolutePath
val segmentsDir = File(filesDir, "brouter/segments4")
- val gpx = routeBrouter(segmentsDir, profilePath, pts, nogos)
+ val routed = routeBrouter(segmentsDir, profilePath, pts, nogos, format)
out.put("ok", true)
- out.put("gpx", gpx)
+ if (format == "geojson") out.put("geojson", routed) else out.put("gpx", routed)
} catch (e: Exception) {
Log.e("BalDav", "BRouter route failed", e)
out.put("ok", false)
@@ -422,7 +423,7 @@ class MainActivity : TauriActivity() {
// RoutingContext pointing at the profile (.brf; the engine parses it via ProfileCache
// and loads lookups.dat from the same dir), waypoints as OsmNodeNamed (ilon/ilat are
// micro-degrees), run the engine, return the track as GPX.
- private fun routeBrouter(segmentsDir: File, profilePath: String, pts: List, nogos: List = emptyList()): String {
+ private fun routeBrouter(segmentsDir: File, profilePath: String, pts: List, nogos: List = emptyList(), format: String = "gpx"): String {
val rc = btools.router.RoutingContext()
rc.memoryclass = 128
rc.localFunction = profilePath
@@ -457,7 +458,7 @@ class MainActivity : TauriActivity() {
val err = re.getErrorMessage()
if (err != null && err.isNotEmpty()) throw RuntimeException(err)
val track = re.getFoundTrack() ?: throw RuntimeException("no track found")
- return track.formatAsGpx()
+ return if (format == "geojson") track.formatAsGeoJson() else track.formatAsGpx()
}
// Send the location SMS. If SEND_SMS was granted, send it straight over the cellular network
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 6da0dde..b7b4260 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -628,7 +628,7 @@ fn write_app_root_file(app: &tauri::AppHandle, name: &str, contents: &str) {
// FileObserver watches, then poll for brouter_result_.json. `lonlats` is a JSON
// array of [lon,lat] pairs; returns the Kotlin result JSON ({ok,gpx}|{ok:false,error}).
#[tauri::command]
-fn brouter_route(app: tauri::AppHandle, lonlats: String, profile: String, nogos: Option) -> Result {
+fn brouter_route(app: tauri::AppHandle, lonlats: String, profile: String, nogos: Option, format: Option) -> Result {
let data_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
std::fs::create_dir_all(&data_dir).ok();
let id = std::time::SystemTime::now()
@@ -636,11 +636,14 @@ fn brouter_route(app: tauri::AppHandle, lonlats: String, profile: String, nogos:
.as_millis().to_string();
// nogos is a JSON array string [{lat,lon,radius}] from JS; default to empty array.
let nogos_json = nogos.filter(|s| !s.trim().is_empty()).unwrap_or_else(|| "[]".to_string());
+ // format: "gpx" (default) or "geojson" (carries per-segment WayTags for danger detection).
+ let fmt = format.filter(|s| !s.trim().is_empty()).unwrap_or_else(|| "gpx".to_string());
let req = format!(
- "{{\"profile\":{},\"lonlats\":{},\"nogos\":{}}}",
+ "{{\"profile\":{},\"lonlats\":{},\"nogos\":{},\"format\":{}}}",
serde_json::to_string(&profile).map_err(|e| e.to_string())?,
lonlats,
- nogos_json
+ nogos_json,
+ serde_json::to_string(&fmt).map_err(|e| e.to_string())?
);
let result_name = format!("brouter_result_{}.json", id);
// Write request + command to app_data_dir AND app root (where Kotlin watches).
diff --git a/tools/brouter-web-staging/profiles2/trekking-adventure.brf b/tools/brouter-web-staging/profiles2/trekking-adventure.brf
new file mode 100644
index 0000000..73e06f1
--- /dev/null
+++ b/tools/brouter-web-staging/profiles2/trekking-adventure.brf
@@ -0,0 +1,449 @@
+# *** The trekking profile is for slow travel
+# *** and avoiding car traffic, but still with
+# *** a focus on approaching your destination
+# *** efficiently.
+
+---context:global # following code refers to global config
+
+# Bike profile
+assign validForBikes = true
+
+# Use the following switches to change behaviour
+assign allow_steps = true # %allow_steps% | Set false to disallow steps | boolean
+assign allow_ferries = true # %allow_ferries% | Set false to disallow ferries | boolean
+assign ignore_cycleroutes = false # %ignore_cycleroutes% | Set true for better elevation results | boolean
+assign stick_to_cycleroutes = false # %stick_to_cycleroutes% | Set true to just follow cycleroutes | boolean
+assign use_proposed_cycleroutes = false # %use_proposed_cycleroutes% | Set to true to consider proposed and regular cycleroutes as equals | boolean
+assign avoid_unsafe = false # %avoid_unsafe% | Set true to avoid standard highways | boolean
+
+assign add_beeline = false # %add_beeline% | Enable beeline on distant start/end points | boolean
+
+assign consider_noise = false # %consider_noise% | Activate to prefer a low-noise route | boolean
+assign consider_river = false # %consider_river% | Activate to prefer a route along rivers, lakes, etc. | boolean
+assign consider_forest = false # %consider_forest% | Activate to prefer a route in forest or parks | boolean
+assign consider_town = false # %consider_town% | Activate to bypass cities / big towns as far as possible | boolean
+assign consider_traffic = false # %consider_traffic% | Activate to consider traffic estimates | boolean
+
+
+
+# Change elevation parameters
+assign consider_elevation = true # %consider_elevation% | Set true to favor a route with few elevation meters | boolean
+
+assign downhillcost = 60 # %downhillcost% | Cost for going downhill | number
+assign downhillcutoff = 1.5 # %downhillcutoff% | Gradients below this value in percents are not counted. | number
+assign uphillcost = 0 # %uphillcost% | Cost for going uphill | number
+assign uphillcutoff = 1.5 # %uphillcutoff% | Gradients below this value in percents are not counted. | number
+
+assign downhillcost = if consider_elevation then downhillcost else 0
+assign uphillcost = if consider_elevation then uphillcost else 0
+
+# Kinematic model parameters (travel time computation)
+assign totalMass = 90 # %totalMass% | Mass (in kg) of the bike + biker, for travel time computation | number
+assign maxSpeed = 45 # %maxSpeed% | Absolute maximum speed (in km/h), for travel time computation | number
+assign S_C_x = 0.225 # %S_C_x% | Drag coefficient times the reference area (in m^2), for travel time computation | number
+assign C_r = 0.01 # %C_r% | Rolling resistance coefficient (dimensionless), for travel time computation | number
+assign bikerPower = 100 # %bikerPower% | Average power (in W) provided by the biker, for travel time computation | number
+
+# Turn instructions settings
+assign turnInstructionMode = 1 # %turnInstructionMode% | Mode for the generated turn instructions | [0=none, 1=auto-choose, 2=locus-style, 3=osmand-style, 4=comment-style, 5=gpsies-style, 6=orux-style, 7=locus-old-style]
+assign turnInstructionCatchingRange = 40 # %turnInstructionCatchingRange% | Within this distance (in m) several turning instructions are combined into one and the turning angles are better approximated to the general direction | number
+assign turnInstructionRoundabouts = true # %turnInstructionRoundabouts% | Set "false" to avoid generating special turning instructions for roundabouts | boolean
+assign considerTurnRestrictions = true # %considerTurnRestrictions% | Set true to take turn restrictions into account | boolean
+
+# with engine defaults, long parts of routing to via points may be removed
+assign correctMisplacedViaPoints = false # %correctMisplacedViaPoints% | Set to true to remove detours due to via points (going back and forth on the same route) | boolean
+assign correctMisplacedViaPointsDistance = 400 # %correctMisplacedViaPointsDistance% | Only remove detours shorter than this (one-way) distance (in m) (engine defaults to 0 which removes detours whatever their length) | number
+
+assign processUnusedTags = false # %processUnusedTags% | Set true to output unused tags in data tab | boolean
+
+assign unpavedPenalty = 1.0 # %unpavedPenalty% | Extra penalty multiplier applied to the non-probablyGood cost of track/road/path/footway ways depending on tracktype grade | number
+
+assign allow_adventure = true # %allow_adventure% | Set true to ALLOW fords, grade5 tracks and bad-visibility trails (adventure mode). Default false avoids them. | boolean
+
+---context:way # following code refers to way-tags
+
+# classifier constants
+assign classifier_none = 1
+assign classifier_ferry = 2
+
+#
+# pre-calculate some logical expressions
+#
+
+assign any_cycleroute =
+ if not use_proposed_cycleroutes then
+ if route_bicycle_icn=yes then true
+ else if route_bicycle_ncn=yes then true
+ else if route_bicycle_rcn=yes then true
+ else if route_bicycle_lcn=yes then true
+ else false
+ else
+ if route_bicycle_icn=yes|proposed then true
+ else if route_bicycle_ncn=yes|proposed then true
+ else if route_bicycle_rcn=yes|proposed then true
+ else if route_bicycle_lcn=yes|proposed then true
+ else false
+
+assign nodeaccessgranted =
+ if any_cycleroute then true
+ else lcn=yes
+
+assign is_ldcr =
+ if ignore_cycleroutes then false
+ else any_cycleroute
+
+# set isbike considering access, local cycle route or the presence of a cycleway on the highway
+assign hasbikerouteoraccess =
+ or bicycle_road=yes or cyclestreet=yes or bicycle=yes|permissive|designated lcn=yes
+
+assign hascycleway = not
+ and ( or cycleway= cycleway=no|none ) and ( or cycleway:left= cycleway:left=no ) and ( or cycleway:right= cycleway:right=no ) ( or cycleway:both= cycleway:both=no )
+
+assign isbike = or hasbikerouteoraccess hascycleway
+
+assign ispaved = or surface=paved|asphalt|concrete|paving_stones|sett smoothness=excellent|good
+assign isunpaved = not or ispaved or ( and surface= smoothness= ) or surface=fine_gravel|cobblestone smoothness=intermediate|bad
+assign probablyGood = or ispaved and ( or isbike highway=footway ) not isunpaved
+
+
+#
+# this is the cost (in Meter) for a 90-degree turn
+# The actual cost is calculated as turncost*cos(angle)
+# (Suppressing turncost while following longdistance-cycleways
+# makes them a little bit more magnetic)
+#
+assign turncost = if is_ldcr then 0
+ else if junction=roundabout then 0
+ else 90
+
+
+#
+# for any change in initialclassifier, initialcost is added once
+#
+assign initialclassifier =
+ if route=ferry then classifier_ferry
+ else classifier_none
+
+
+#
+# calculate the initial cost
+# this is added to the total cost each time the costfactor
+# changed
+#
+# Adventure penalty (added into costfactor below): keep the router off fords, abandoned
+# (grade5) tracks and bad/invisible trails unless the rider opts into adventure mode
+# (%allow_adventure%). 10000 matches the profile's "excluded way" costfactor.
+assign adventurepenalty =
+ if allow_adventure then 0
+ else add ( if ford=yes|stepping_stones then 10000 else 0 )
+ ( if or tracktype=grade5 trail_visibility=bad|horrible|no then 5000 else 0 )
+
+assign initialcost =
+ if ( equal initialclassifier classifier_ferry ) then 10000
+ else 0
+
+#
+# implicit access here just from the motorroad tag
+# (implicit access rules from highway tag handled elsewhere)
+#
+assign defaultaccess =
+ if access= then not motorroad=yes
+ else if access=private|no then false
+ else true
+
+#
+# calculate logical bike access
+#
+assign bikeaccess =
+ if bicycle= then
+ (
+ if bicycle_road=yes then true
+ else if vehicle= then ( if highway=footway then false else defaultaccess )
+ else not vehicle=private|no
+ )
+ else not bicycle=private|no|dismount|use_sidepath
+
+#
+# calculate logical foot access
+#
+assign footaccess =
+ if bicycle=dismount then true
+ else if foot= then defaultaccess
+ else not foot=private|no|use_sidepath
+
+#
+# if not bike-, but foot-access, just a moderate penalty,
+# otherwise access is forbidden
+#
+assign accesspenalty =
+ if bikeaccess then 0
+ else if footaccess then 4
+ else if any_cycleroute then 15
+ else if bicycle=use_sidepath then 25
+ else 10000
+
+#
+# handle one-ways. On primary roads, wrong-oneways should
+# be close to forbidden, while on other ways we just add
+# 4 to the costfactor (making it at least 5 - you are allowed
+# to push your bike)
+
+# are we against legal direction on a oneway?
+assign badoneway =
+ if reversedirection=yes then
+ if oneway:bicycle=yes then true
+ else if oneway= then junction=roundabout
+ else oneway=yes|true|1
+ else oneway=-1
+
+assign onewaypenalty =
+ if ( badoneway ) then
+ (
+ if (
+ and hascycleway
+ or and cycleway:left=lane|track|shared_lane|share_busway
+ cycleway:left:oneway=no|-1
+ or and cycleway:right=lane|track|shared_lane|share_busway
+ cycleway:right:oneway=no|-1
+ or and cycleway:both=lane|track|shared_lane|share_busway
+ #or cycleway:both:oneway=no|-1
+ or cycleway:left:oneway=no|-1
+ cycleway:right:oneway=no|-1
+ or cycleway=opposite|opposite_lane|opposite_track
+ or cycleway:left=opposite|opposite_lane|opposite_track
+ cycleway:right=opposite|opposite_lane|opposite_track
+ ) then 0
+ else if ( oneway:bicycle=no ) then 0
+ else if ( not footaccess ) then 100
+ else if ( junction=roundabout|circular ) then 60
+ else if ( highway=primary|primary_link ) then 50
+ else if ( highway=secondary|secondary_link ) then 30
+ else if ( highway=tertiary|tertiary_link ) then 20
+ else 4.0
+ )
+ else 0.0
+
+# add estimate tags
+assign traffic_penalty
+ switch consider_traffic
+ switch estimated_traffic_class= 0
+ switch estimated_traffic_class=1|2 0.2
+ switch estimated_traffic_class=3 0.4
+ switch estimated_traffic_class=4 0.6
+ switch estimated_traffic_class=5 0.8
+ switch estimated_traffic_class=6|7 1 99 0
+
+
+assign noise_penalty
+ switch consider_noise
+ switch estimated_noise_class= 0
+ switch estimated_noise_class=1 0.3
+ switch estimated_noise_class=2 0.5
+ switch estimated_noise_class=3 0.8
+ switch estimated_noise_class=4 1.4
+ switch estimated_noise_class=5 1.7
+ switch estimated_noise_class=6 2 0 0
+
+assign no_river_penalty
+ switch consider_river
+ switch estimated_river_class= 2
+ switch estimated_river_class=1 1.3
+ switch estimated_river_class=2 1
+ switch estimated_river_class=3 0.7
+ switch estimated_river_class=4 0.4
+ switch estimated_river_class=5 0.1
+ switch estimated_river_class=6 0 99 0
+
+assign no_forest_penalty
+ switch consider_forest
+ switch estimated_forest_class= 1
+ switch estimated_forest_class=1 0.5
+ switch estimated_forest_class=2 0.4
+ switch estimated_forest_class=3 0.25
+ switch estimated_forest_class=4 0.15
+ switch estimated_forest_class=5 0.1
+ switch estimated_forest_class=6 0 99 0
+
+assign town_penalty
+ switch consider_town
+ switch estimated_town_class= 0
+ switch estimated_town_class=1 0.5
+ switch estimated_town_class=2 0.9
+ switch estimated_town_class=3 1.2
+ switch estimated_town_class=4 1.3
+ switch estimated_town_class=5 1.4
+ switch estimated_town_class=6 1.6 99 0
+
+#
+# calculate the cost-factor, which is the factor
+# by which the distance of a way-segment is multiplied
+# to calculate the cost of that segment. The costfactor
+# must be >=1 and it's supposed to be close to 1 for
+# the type of way the routing profile is searching for
+#
+assign isresidentialorliving = or highway=residential|living_street living_street=yes
+assign costfactor
+
+ #
+ # exclude rivers, rails etc.
+ #
+ if ( and highway= not route=ferry ) then 10000
+
+ #
+ # exclude motorways and proposed, abandoned under construction roads
+ #
+ else if ( highway=motorway|motorway_link ) then 10000
+ else if ( highway=proposed|abandoned|construction ) then 10000
+
+ #
+ # all other exclusions below (access, steps, ferries,..)
+ # should not be deleted by the decoder, to be available
+ # in voice-hint-processing
+ #
+ else min 9999
+
+ add town_penalty
+ add no_forest_penalty
+ add no_river_penalty
+ add noise_penalty
+ add traffic_penalty
+ add adventurepenalty
+
+ #
+ # apply oneway-and access-penalties
+ #
+ add max onewaypenalty accesspenalty
+
+ #
+ # steps and ferries are special. Note this is handled
+ # before the cycleroute-switch, to be able
+ # to really exclude them be setting cost to infinity
+ #
+ if ( highway=steps ) then ( if allow_steps then 40 else 10000 )
+ else if ( route=ferry ) then ( if allow_ferries then 5.67 else 10000 )
+
+ #
+ # handle long-distance cycle-routes.
+ #
+ else if ( is_ldcr ) then 1 # always treated as perfect (=1)
+ else
+ add ( if stick_to_cycleroutes then 0.5 else 0.05 ) # everything else somewhat up
+
+ #
+ # some other highway types
+ #
+ if ( highway=pedestrian ) then ( if isbike then ( if hascycleway then 1.1 else 2.2 ) else 3 )
+ else if ( highway=bridleway ) then 5
+ else if ( highway=cycleway ) then 1
+ else if ( isresidentialorliving ) then ( if isunpaved then 1.5 else 1.1 )
+ else if ( highway=service ) then ( if isunpaved then 1.6 else 1.3 )
+
+ #
+ # tracks and track-like ways are rated mainly be tracktype/grade
+ # But note that if no tracktype is given (mainly for road/path/footway)
+ # it can be o.k. if there's any other hint for quality
+ #
+ else if ( highway=track|road|path|footway ) then
+ (
+ if ( tracktype=grade1 ) then ( if probablyGood then 1.0 else add 1.0 multiply 0.3 unpavedPenalty )
+ else if ( tracktype=grade2 ) then ( if probablyGood then 1.1 else add 1.1 multiply 0.9 unpavedPenalty )
+ else if ( tracktype=grade3 ) then ( if probablyGood then 1.5 else add 1.5 multiply 1.5 unpavedPenalty )
+ else if ( tracktype=grade4 ) then ( if probablyGood then 2.0 else add 2.0 multiply 3.0 unpavedPenalty )
+ else if ( tracktype=grade5 ) then ( if probablyGood then 3.0 else add 3.0 multiply 2.0 unpavedPenalty )
+ else ( if probablyGood then 1.0 else add 1.0 multiply 4.0 unpavedPenalty )
+ )
+
+ #
+ # When avoiding unsafe ways, avoid highways without a bike hint
+ #
+ else add ( if ( and avoid_unsafe not isbike ) then 2 else 0 )
+
+ #
+ # actuals roads are o.k. if we have a bike hint
+ #
+ if ( highway=trunk|trunk_link ) then ( if isbike then 1.5 else 10 )
+ else if ( highway=primary|primary_link ) then ( if isbike then 1.2 else 3 )
+ else if ( highway=secondary|secondary_link ) then ( if isbike then 1.1 else 1.6 )
+ else if ( highway=tertiary|tertiary_link ) then ( if isbike then 1.0 else 1.4 )
+ else if ( highway=unclassified ) then ( if isbike then 1.0 else 1.3 )
+
+ #
+ # default for any other highway type not handled above
+ #
+ else 2.0
+
+
+# way priorities used for voice hint generation
+
+assign priorityclassifier =
+
+ if ( highway=motorway ) then 30
+ else if ( highway=motorway_link ) then 29
+ else if ( highway=trunk ) then 28
+ else if ( highway=trunk_link ) then 27
+ else if ( highway=primary ) then 26
+ else if ( highway=primary_link ) then 25
+ else if ( highway=secondary ) then 24
+ else if ( highway=secondary_link ) then 23
+ else if ( highway=tertiary ) then 22
+ else if ( highway=tertiary_link ) then 21
+ else if ( highway=unclassified ) then 20
+ else if ( isresidentialorliving ) then 6
+ else if ( highway=service ) then 6
+ else if ( highway=cycleway ) then 6
+ else if ( or bicycle=designated bicycle_road=yes ) then 6
+ else if ( highway=track ) then if tracktype=grade1 then 6 else 4
+ else if ( highway=bridleway|road|path|footway ) then 4
+ else if ( highway=steps ) then 2
+ else if ( highway=pedestrian ) then 2
+ else 0
+
+# some more classifying bits used for voice hint generation...
+
+assign isbadoneway = not equal onewaypenalty 0
+assign isgoodoneway = if reversedirection=yes then oneway=-1
+ else if oneway= then junction=roundabout else oneway=yes|true|1
+assign isroundabout = junction=roundabout
+assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link
+assign isgoodforcars = if greater priorityclassifier 6 then true
+ else if ( or isresidentialorliving highway=service ) then true
+ else if ( and highway=track tracktype=grade1 ) then true
+ else false
+
+# ... encoded into a bitmask
+
+assign classifiermask add isbadoneway
+ add multiply isgoodoneway 2
+ add multiply isroundabout 4
+ add multiply islinktype 8
+ multiply isgoodforcars 16
+
+# include `smoothness=` tags in the response's WayTags for track analysis
+assign dummyUsage = smoothness=
+
+---context:node # following code refers to node tags
+
+assign defaultaccess =
+ if ( access= ) then true # add default barrier restrictions here!
+ else if ( access=private|no ) then false
+ else true
+
+assign bikeaccess =
+ if nodeaccessgranted=yes then true
+ else if bicycle= then
+ (
+ if vehicle= then defaultaccess
+ else not vehicle=private|no
+ )
+ else not bicycle=private|no|dismount
+
+assign footaccess =
+ if bicycle=dismount then true
+ else if foot= then defaultaccess
+ else not foot=private|no
+
+assign initialcost =
+ add ( if allow_adventure then 0 else if ford=yes|stepping_stones then 100000 else 0 )
+ ( if or highway=traffic_signals and highway=crossing crossing=traffic_signals then 20
+ else if bikeaccess then 0
+ else ( if footaccess then 100 else 1000000 ) )