Compare commits
12 commits
4a77132317
...
8a236bae3d
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a236bae3d | |||
| f91281a218 | |||
| 61cfe7c0ee | |||
| e12e19132d | |||
| d25cb0de49 | |||
| 640429a209 | |||
| 9d510931ad | |||
| 85aa87edb0 | |||
| 0d5a5986ac | |||
| ac37f36a06 | |||
| 80ddff9b72 | |||
| 27c60a460a |
14 changed files with 1904 additions and 76 deletions
|
|
@ -34,9 +34,9 @@
|
|||
- **OTA обновления** — проверка BUILD_NUMBER на роутере (LAN) или VPS, скачивание через браузер.
|
||||
- **Ручные маршруты** — рисование трека на карте тапами, elevation из прошлых треков или open-meteo API.
|
||||
- **Сохранённые маршруты** — импорт GPX-маршрутов, нарисованных заранее (например, в конструкторе Яндекс.Карт: формат `<rtept>`), выбор активного, наложение цветной линией поверх карты во время записи И на экране просмотра прошлого трека — для визуальной навигации. Управление на отдельной странице (импорт/переименование/цвет/удаление, один активный за раз), переключение линии кнопкой-тогглом. Цвета «проезженных дорог» настраиваются раздельно для вело/пеших/бега.
|
||||
- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`<ele>` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM).
|
||||
- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон по НАПРАВЛЕНИЮ движения, полосами по 1%: подъём зелёный→жёлтый→красный→коричневый→чёрный, спуск зелёный→cyan→синий→navy (≥10% крайний), или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`<ele>` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; уклон считается по дистанционному окну ~30м (одна ячейка DEM), чтобы ступенька SRTM не давала ложный крутой уклон на одном коротком сегменте. **Поправка высот по проездам**: где катал >=N раз, SRTM-высота в превью сдвигается вверх/вниз на медиану (измеренная−SRTM) по ячейкам ~20м (SRTM остаётся базой, недеструктивно; `get_elev_correction`).
|
||||
- **Редактор маршрутов** — создание («Новый маршрут») и правка сохранённого маршрута прямо в приложении: тап по карте — добавить точку в конец, **тап по линии — вставить точку** (широкая зона попадания), долгий тап по линии — переключить отрезок дорога/прямая, тащить точку — двигать, тап по точке — удалить. Роутинг **по-сегментный**: каждый отрезок между опорными точками отдельно либо по дорогам (BRouter, профиль trekking, высоты из BRouter), либо прямой; по дорогам по умолчанию, где дороги/сегментов нет — молча прямая; кнопка **«🚲 По дорогам»** задаёт режим сразу всем. Маршрут в редакторе рисуется **оковкой с раскраской уклон/высота** как в детали; тумблеры день/ночь, уклон/высота и кнопка «моё место» на карте. Сохраняются и **опорные точки** (5-6, для правки — при повторном открытии столько же), и плотная дорожная геометрия с высотами (для показа и профиля уклона).
|
||||
- **Оффлайн-роутинг (BRouter)** — движок BRouter встроен в приложение (Android): маршруты строятся по дорогам без сети, сегменты `.rd5` региона качаются с домашнего зеркала прямо в приложении (экран дорог → «Сегменты»). Используется в редакторе маршрутов («По дорогам») и для заполнения GPS-пропусков. На десктопе/дома движка нет — те же вызовы уходят на домашний BRouter-RouteServer по HTTP.
|
||||
- **Оффлайн-роутинг (BRouter)** — движок BRouter встроен в приложение (Android): маршруты строятся по дорогам без сети, сегменты `.rd5` региона качаются с домашнего зеркала прямо в приложении (экран дорог → «Сегменты»). Используется в редакторе маршрутов («По дорогам») и для заполнения GPS-пропусков. На десктопе/дома движка нет — те же вызовы уходят на домашний BRouter-RouteServer по HTTP. Профиль `trekking` по умолчанию **избегает бродов** (`ford`), заброшенных грунтовок (`tracktype=grade5`) и троп с плохой видимостью (`trail_visibility=bad|horrible|no`) - штраф в costfactor. В редакторе тумблер **«Броды: избегать/разрешить»** переключает на `trekking-adventure` (allow_adventure). **Показ опасности**: дорожные ноги строятся через geojson (`format=geojson` даёт `WayTags`/`NodeTags` на сегмент; on-device `formatAsGeoJson`), броды и грунт/тропы помечаются на карте + счётчик «⚠ брод/грунт» - видно ДО поездки (`GPS.parseRouteGeoJson`).
|
||||
- **Заполнение GPS-пропусков по дороге** — на детали трека кнопка **«🧩»** прокладывает дорожный путь (BRouter, профиль shortest) через каждый GPS-дропаут (разрыв по времени + реальная хорда) и показывает **предпросмотр только достроенных пропусков** (голубым, с бело-чёрной оковкой; недостроенные — красным пунктиром) плюс панель «Достроено N (+X км), M не удалось» с кнопками **Сохранить/Отмена**. Сохранение — **обратимый патч на месте**: достроенные точки помечаются `estimated` и добавляются в трек (оригинал цел), статистика пересчитывается; повторный тап предлагает **«Убрать достройку»** (удаляет только достроенное). Достраиваются только реальные длинные потери сигнала (по умолчанию разрыв > 60с и хорда > 150м); пропуск не достраивается, если дорога заметно длиннее хорды (крюк, > 2×) или почти совпадает с прямой (мостить нечего) — чтобы не перерисовывать нормальные участки объездами.
|
||||
- **Непроезжие места (nogo)** — красные круги, которые роутинг ОБЪЕЗЖАЕТ. Ставятся кнопкой **⛔**: в планировщике — тапом по карте/линии, на карте записи — «здесь нет проезда» (текущее место). Показываются на картах планировщика, записи и детали трека; тап по кругу — убрать. Избегание работает и через домашний RouteServer, и встроенным движком офлайн (BRouter nogopoints). Web-BRouter умеет nogo сам.
|
||||
- **Объезд с возвратом на маршрут (на ходу)** — на карте записи кнопка **«↪»**: если активный маршрут ведёт через непроезжее место, строит от текущей позиции объезд обратно НА маршрут за местом затыка (отступ ~1.5 км вперёд, растёт при неудаче до конца маршрута), избегая nogo и разворота на 180° (учитывает курс). Показывается ярким навигационным оверлеем (не меняет сохранённый маршрут и не прерывает запись); долгий тап по кнопке убирает оверлей.
|
||||
|
|
|
|||
91
docs/plans/2026-10-26-elevation-slope-brouter.md
Normal file
91
docs/plans/2026-10-26-elevation-slope-brouter.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# План: точность высот/уклона в предпросмотре + безопасность маршрутов (brouter)
|
||||
|
||||
## Context
|
||||
Пять замечаний по предпросмотру маршрута и маршрутизации (baldav):
|
||||
1. Режим «уклон» врёт: рисует крутой подъём там, где его нет (и наоборот). Высоты из DEM
|
||||
квантуются по ячейкам 30м, точки внутри ячейки имеют одну высоту, на границе - скачок, и вся
|
||||
Δ падает на один короткий сегмент = ложный крутой уклон. (Второе - «не рисует где есть» - предел
|
||||
разрешения SRTM 30м, не чинится без более точного DEM.)
|
||||
2. Спуски красить иначе: сейчас уклон красится по МОДУЛЮ (вверх и вниз одинаково). Спуски по
|
||||
направлению движения должны уходить из зелёного в голубой -> navy.
|
||||
3. brouter трижды завёл к бродам/ледяной воде (ford - теги OSM в данных brouter, не SRTM). Нужно:
|
||||
избегать бродов/сложных троп/крутых спусков по умолчанию, ПОКАЗЫВАТЬ оставшиеся опасные участки
|
||||
на предпросмотре заранее, и дать опцию «разрешить (испытать удачу)». Полный объём.
|
||||
4. Кнопка объезда ↪ на записи срабатывает всегда и ведёт «к первому вейпоинту»: нет проверки, что
|
||||
впереди реально есть затык, а цель выбирается по глобально ближайшей точке маршрута (ломается на
|
||||
петлях и вдали от маршрута).
|
||||
5. Уточнять высоты по многим проездам. Решение пользователя: база - SRTM, медиана высот из треков
|
||||
берётся ТОЛЬКО как поправка вверх/вниз к SRTM (не замена), там где катал.
|
||||
|
||||
Общее: раскраска уклон/высота и расчёт уклона - в ОДНИХ функциях, используются в предпросмотре
|
||||
маршрута, детали записанного трека, редакторе и на экране записи. Правка общих функций чинит везде;
|
||||
проверить надо каждое место (WT4: починил A - проверь B и C в той же области).
|
||||
|
||||
## Критичные файлы и функции
|
||||
- `frontend/js/gps.js`: `GPS.routeElevStats` (расчёт `grades[]`/`smoothed[]`), `GPS.filterAndSmoothAlt`
|
||||
(сглаживание высот - сейчас по ТОЧКАМ, окно 5), `GPS.fillGaps`/`get_ride_graph` (сетка ~20м - база для п.5).
|
||||
- `frontend/js/main.js`: `routeSlopeColor` + `ROUTE_SLOPE_BANDS` + `slopeLegendHtml` (п.2),
|
||||
`drawRoutePolyline`/`showRouteDetail` (предпросмотр), `rejoinReroute` (п.4), `fetchElevations` (п.5).
|
||||
- `src-tauri/gen/android/app/src/main/assets/brouter/profiles2/trekking.brf` (п.3, избегание) +
|
||||
копия в `tools/brouter-web-staging/profiles2/trekking.brf` (единый источник - править обе).
|
||||
- `MainActivity.kt` `routeBrouter` (п.3, вытащить теги путей из `OsmTrack.messageList` рядом с GPX).
|
||||
- `src-tauri/src/lib.rs` `get_ride_graph`/новый агрегат высот (п.5).
|
||||
|
||||
## Подход по пунктам
|
||||
|
||||
### П.1 - уклон по дистанционному окну (вместо ступенек ячейки)
|
||||
В `GPS.routeElevStats` считать `grades[i]` как центральную разность высоты по РАССТОЯНИЮ окном
|
||||
~30м (одна ячейка DEM), а не между соседними точками: для точки i найти вперёд j и назад k так,
|
||||
чтобы суммарная дистанция >= W (конфиг `gradeWindowM`, дефолт 30), grade = (smoothed[j]-smoothed[k])/dist.
|
||||
Так одиночная ступенька ячейки размазывается по всей ячейке. `maxGrade` и профиль - по тем же
|
||||
сглаженным. «Не рисует где есть» - записать как предел разрешения SRTM; реально улучшает п.5.
|
||||
|
||||
### П.2 - направленная раскраска спусков
|
||||
`routeSlopeColor(gradePct)` сделать знакочувствительной: подъём (>=0) - тёплый веер (как сейчас
|
||||
green->yellow->red->brown->black), спуск (<0) - холодный веер green->cyan->blue->navy по модулю.
|
||||
Два массива полос + обновить `slopeLegendHtml` (две шкалы: подъём/спуск). Направление = порядок
|
||||
точек (он же направление движения в треке/маршруте).
|
||||
|
||||
### П.3 - brouter: избегать + показывать + опция
|
||||
- Профиль `trekking.brf`: добавить штрафы (через `assign` в `---context:way`) на `ford=yes`,
|
||||
пересечение waterway без моста, `tracktype=grade4|grade5`, `trail_visibility=bad|horrible`,
|
||||
высокий `sac_scale`/`mtb:scale`; крутой спуск уже есть через `downhillcost`. Параметр
|
||||
`%allow_adventure%` (дефолт false) - при true штрафы снимаются («испытать удачу»).
|
||||
- Показ опасного: в `routeBrouter` рядом с GPX вернуть JSON тегов сегментов из `OsmTrack.messageList`
|
||||
(btools даёт теги путей); нужен короткий спайк - проверить, что messageList заполняется и какие
|
||||
поля. На превью (`drawRoutePolyline`/`showRouteDetail`) пометить участки ford/вода/крутой спуск
|
||||
отдельным стилем + счётчик «броды: N, крутые спуски: M». Тумблер «разрешить броды/сложные тропы»
|
||||
(localStorage + `%allow_adventure%` в вызов BRouter).
|
||||
- Если messageList тегов не отдаёт - запасной путь: крутые спуски берём из уклона (уже есть), броды -
|
||||
пометить честно как «не выяснено без тегов» и решить отдельно. Спайк решает.
|
||||
|
||||
### П.4 - объезд ↪ только при реальном затыке впереди
|
||||
В `rejoinReroute`: (а) спроецировать текущую позицию на маршрут -> индекс прогресса (не глобально
|
||||
ближайший - ломается на петлях), учитывать направление движения; (б) объезд строить ТОЛЬКО если
|
||||
есть nogo на маршруте ВПЕРЕДИ в пределах окна (напр. до +1.5км по маршруту); иначе toast «впереди
|
||||
затыка нет» и ничего не строить. Цель rejoin - первая точка маршрута за nogo + запас.
|
||||
|
||||
### П.5 - поправка высот по проездам (база SRTM + медиана как сдвиг)
|
||||
- Rust (`lib.rs`, рядом с `get_ride_graph`): агрегат по сетке ~20-30м из `gps_points.altitude`:
|
||||
на ячейку median(alt) и count. Новый command, напр. `get_elev_correction`.
|
||||
- JS (`fetchElevations`/`routeElevStats`): после получения SRTM-высот, где у ячейки count>=порога,
|
||||
скорректировать: `elev = SRTM + smooth(median_ridden - SRTM)`. SRTM остаётся базой и формой; треки
|
||||
только двигают вверх/вниз где катал. Порог проездов и сглаживание сдвига - в конфиг.
|
||||
- GPS-высота шумная -> median по многим точкам/проездам + отбрасывание выбросов; барометр DJI
|
||||
(отдельный TODO) улучшит позже.
|
||||
|
||||
## Порядок и коммиты
|
||||
Пункты независимы. Делать по одному, коммит на каждый (commit-before-test), сборка через
|
||||
`./build.sh -d -i android`, проверка на телефоне; follow-up фиксы - отдельными коммитами.
|
||||
Рекомендуемый порядок: 2 (быстрый, визуальный) -> 1 -> 4 -> 5 -> 3 (самый большой, со спайком).
|
||||
|
||||
## Проверка (на устройстве + где можно на ПК)
|
||||
- П.1/П.2: открыть предпросмотр реального маршрута с известным рельефом; переключить уклон/высота;
|
||||
убедиться, что крутой цвет только на реально крутом, спуски холодные. Вёрстку/цвета можно глянуть
|
||||
на ПК в dev (reference_verify_record_screen). Проверить ВСЕ места раскраски: превью маршрута,
|
||||
деталь записанного трека, редактор, экран записи.
|
||||
- П.3: построить маршрут через известный брод/грунтовку -> по умолчанию объезжает; включить
|
||||
«разрешить» -> ведёт через, но помечено на превью. Спайк messageList - логом из routeBrouter.
|
||||
- П.4: на записи вдали от затыка нажать ↪ -> «впереди затыка нет»; поставить nogo на маршруте
|
||||
впереди -> объезд строится за него, не к старту.
|
||||
- П.5: взять ячейки, где много проездов, сравнить профиль до/после коррекции; где не катал - SRTM.
|
||||
74
docs/session-logs/2026-10-26_elevation-slope-brouter.md
Normal file
74
docs/session-logs/2026-10-26_elevation-slope-brouter.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# 2026-10-26 - Точность высот/уклона в предпросмотре + безопасность маршрутов (brouter)
|
||||
|
||||
Работали по утверждённому плану `docs/plans/` (финальный вариант - см. ниже «План»). Пять пунктов
|
||||
по предпросмотру маршрута и маршрутизации. Телефон в ходе работы отключился - часть проверок на
|
||||
устройстве ждёт переподключения (отмечено).
|
||||
|
||||
## Что сделано (по пунктам плана)
|
||||
|
||||
### П.2 - направленная раскраска спусков (коммит 27c60a4)
|
||||
`routeSlopeColor` стала знакочувствительной: подъём - тёплый веер (green->yellow->red->brown->black),
|
||||
спуск по направлению движения - холодный (green->cyan->blue->navy). Легенда показывает обе шкалы.
|
||||
Один общий `routeSlopeColor`/`drawRoutePolyline` -> чинит везде (превью, деталь трека, редактор,
|
||||
оверлей на записи). Проверка на устройстве: ЖДЁТ (нужен реальный маршрут с рельефом).
|
||||
|
||||
### П.1 - уклон по дистанционному окну (коммит 80ddff9)
|
||||
`GPS.routeElevStats`: `grades[i]` теперь центральная разность высоты по РАССТОЯНИЮ окном ~30м
|
||||
(config `gradeWindowM`), а не между соседними точками. Ступенька ячейки DEM (30м) размазывается по
|
||||
ячейке - уходит ложный крутой уклон на одном коротком сегменте. `maxGrade` - по тем же.
|
||||
«Не рисует где есть» - предел разрешения SRTM 30м, не чинится без более точного DEM (улучшает П.5).
|
||||
Проверка: ЖДЁТ устройство.
|
||||
|
||||
### П.4 - объезд ↪ только при реальном затыке впереди (коммит ac37f36)
|
||||
`rejoinReroute`: (а) прогресс = ближайшая точка маршрута к позиции; (б) направление по компасу; (в)
|
||||
объезд строится ТОЛЬКО если nogo реально на маршруте впереди в окне ~2.5км, иначе toast «впереди
|
||||
затыка нет». Цель - первая точка за nogo + 300м. Раньше строил всегда и прыгал «к первому вейпоинту»
|
||||
(глобально ближайшая точка на петле). Проверка: ЖДЁТ устройство (поставить nogo впереди/без nogo).
|
||||
|
||||
### П.5 - поправка высот по проездам: база SRTM + медиана как сдвиг (коммит 0d5a598)
|
||||
Rust `get_elev_correction(minPasses)`: по ячейке ~20м медиана (`altitude_gps_m` − `altitude_srtm_m`)
|
||||
по >= N разным сессиям-проездам. SRTM остаётся базой; это только сдвиг вверх/вниз там, где катал.
|
||||
JS `applyRiddenElevCorrection` применяет сглаженную дельту к высотам маршрута в превью
|
||||
(`showRouteDetail`), недеструктивно (копия; нет покрытия -> без изменений). Порог проездов -
|
||||
`localStorage baldav_elev_corr_passes` (деф. 3). Проверка: ЖДЁТ (ячейки с многими проездами).
|
||||
|
||||
### П.3 - brouter: ИЗБЕГАТЬ (Part A, коммит 85aa87e). Part B - следующим заходом.
|
||||
Профиль `trekking.brf` (asset + staging синхронно): штраф на броды (`ford=yes|stepping_stones`,
|
||||
node и way), `tracktype=grade5`, `trail_visibility=bad|horrible|no`; тумблер `%allow_adventure%`
|
||||
(деф. false = избегать). Теги подтверждены в lookups.dat. Синтаксис сверен с грамматикой BRouter,
|
||||
но локального brouter-jar нет -> ПРОВЕРКА СИНТАКСИСА только маршрутизацией на устройстве (ошибка
|
||||
профиля = routeBrouter бросает ошибку = безопасный отказ, не кривой маршрут).
|
||||
|
||||
## П.3 Part B - СДЕЛАНО (коммит 109879a) + фикс avoidance
|
||||
Спайк пройден: brouter отдаёт теги путей. В geojson (`format=geojson`) в `properties.messages`
|
||||
есть колонки `WayTags`/`NodeTags` на сегмент; on-device btools (`brouter-1.6.3-all.jar`) имеет
|
||||
`formatAsGeoJson`. Реализовано:
|
||||
- `brouter_route` (Rust) + `routeBrouter` (Kotlin) - параметр `format`; при `geojson` возвращается
|
||||
`formatAsGeoJson` (GPX-путь не тронут).
|
||||
- `BRouter.routeGeoJson` + `GPS.parseRouteGeoJson` - геометрия + опасности (ford / grade5|плохая
|
||||
тропа / паром) из тегов.
|
||||
- Редактор маршрута: дорожные ноги строятся через geojson, броды (cyan) и грунт/тропы (orange)
|
||||
помечаются на карте + счётчик «⚠ брод/грунт»; тумблер «Броды: избегать/разрешить»
|
||||
(trekking / trekking-adventure) + ре-роутинг.
|
||||
- Профиль `trekking-adventure.brf` (allow_adventure=true), asset+staging+стенд .18.
|
||||
|
||||
**Фикс avoidance (коммит 640429a):** штраф за брод сидел в way-`initialcost` и НЕ менял cost
|
||||
(проверено на .18: avoid и adventure давали одинаковый cost). Перенёс в `costfactor`.
|
||||
|
||||
**Проверено на домашнем портале 192.168.1.18 (реальный брод 47.5387,40.3164):**
|
||||
- `trekking` (avoid): 6199м в обход, fords=0; `trekking-adventure`: 1418м через брод, fords=1.
|
||||
- `GPS.parseRouteGeoJson` на реальном ответе .18: 42 точки, 1 брод на 47.53867,40.31637 (твой).
|
||||
- Профиль ПАРСИТСЯ (синтаксис валиден). Профили задеплоены на .18 (бэкап trekking.brf.bak-*).
|
||||
|
||||
## НЕ проверено (телефон отключился)
|
||||
- На УСТРОЙСТВЕ: П.1/П.2/П.4/П.5 + редактор (маркеры опасности, тумблер adventure) + on-device
|
||||
geojson-путь routeBrouter. Собрано (build 0.1.531), компилируется. Ставить при переподключении.
|
||||
- 2-й брод (47.4926,39.8610) - тестовые точки его не пересекли, тест неинформативен (не провал).
|
||||
|
||||
## Критичные места (для продолжения)
|
||||
- `frontend/js/gps.js`: `routeElevStats` (grades окно), `filterAndSmoothAlt`.
|
||||
- `frontend/js/main.js`: `routeSlopeColor`/`ROUTE_SLOPE_UP`/`ROUTE_SLOPE_DOWN`/`slopeLegendHtml`,
|
||||
`drawRoutePolyline`, `rejoinReroute`, `loadElevCorrection`/`applyRiddenElevCorrection`.
|
||||
- `src-tauri/src/lib.rs`: `get_elev_correction`.
|
||||
- `.../assets/brouter/profiles2/trekking.brf` (+ `tools/brouter-web-staging/profiles2/trekking.brf`).
|
||||
- `MainActivity.kt` `routeBrouter` (для Part B - messageList).
|
||||
|
|
@ -1318,6 +1318,9 @@ 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; }
|
||||
.rec-share-btn { left: 8px; bottom: 124px; font-size: 11px; }
|
||||
/* 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); }
|
||||
|
|
|
|||
|
|
@ -1780,6 +1780,55 @@ 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";
|
||||
// likely (untagged) water crossing: a track/path/footway with a high BRouter
|
||||
// river-class estimate and no bridge/tunnel. Catches fords OSM never tagged ford=yes
|
||||
// (verified: a real ford showed estimated_river_class=5 on a track, no ford tag).
|
||||
else if (/\bhighway=(track|path|footway)\b/.test(wt) && /\bestimated_river_class=(5|6|7|8|9|10)\b/.test(wt)
|
||||
&& !/\bbridge=/.test(wt) && !/\btunnel=/.test(wt)) type = "wetcross";
|
||||
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
|
||||
|
|
@ -2453,14 +2502,30 @@ GPS.routeElevStats = function(points) {
|
|||
for (let i = 1; i < n; i++) {
|
||||
cumKm[i] = cumKm[i-1] + GPS.distance(points[i-1].lat, points[i-1].lon, points[i].lat, points[i].lon) / 1000;
|
||||
}
|
||||
// Grade over a DISTANCE window (~one DEM cell), not between adjacent points. DEM
|
||||
// elevations are quantised to 30m cells: dense points inside a cell share one value
|
||||
// and the whole rise lands on the single boundary segment → a false steep spike.
|
||||
// A windowed central difference spreads that step across the cell so the colour
|
||||
// reflects the real grade. Window W metres (config gradeWindowM, default 30).
|
||||
const W = GPS.config.gradeWindowM ?? 30;
|
||||
const cumM = cumKm.map(k => k * 1000);
|
||||
const grades = new Array(n).fill(0);
|
||||
let maxGrade = 0;
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
const dm = GPS.distance(points[i].lat, points[i].lon, points[i+1].lat, points[i+1].lon);
|
||||
const de = (smoothed[i] != null && smoothed[i+1] != null) ? (smoothed[i+1] - smoothed[i]) : 0;
|
||||
const g = dm > 1 ? (de / dm) * 100 : 0;
|
||||
const center = (cumM[i] + cumM[i+1]) / 2;
|
||||
let a = i, b = i + 1;
|
||||
while (a > 0 && center - cumM[a] < W / 2) a--;
|
||||
while (b < n - 1 && cumM[b] - center < W / 2) b++;
|
||||
// pull window ends in off any null-elevation points
|
||||
while (a < i && smoothed[a] == null) a++;
|
||||
while (b > i + 1 && smoothed[b] == null) b--;
|
||||
const span = cumM[b] - cumM[a];
|
||||
let g = 0;
|
||||
if (span > 1 && smoothed[a] != null && smoothed[b] != null) {
|
||||
g = ((smoothed[b] - smoothed[a]) / span) * 100;
|
||||
}
|
||||
grades[i] = g;
|
||||
if (dm > 10 && Math.abs(g) > Math.abs(maxGrade)) maxGrade = g;
|
||||
if (span > 10 && Math.abs(g) > Math.abs(maxGrade)) maxGrade = g;
|
||||
}
|
||||
let ascent = 0, descent = 0, ref = null;
|
||||
for (let i = 0; i < n; i++) {
|
||||
|
|
|
|||
|
|
@ -3603,25 +3603,54 @@ function _offsetLatLon(lat, lon, brDeg, d) {
|
|||
// sits on the route (<=150 m), else the current position's nearest route point. Target = a
|
||||
// route point ~1.5 km further along; on failure step further (up to the route end).
|
||||
// Returns { points:[{lat,lon}], target:{lat,lon} } or null.
|
||||
// Reroute back onto the active route PAST a blockage ahead. Only acts when a nogo
|
||||
// actually sits on the route ahead of the current progress point (in the travel
|
||||
// direction) — otherwise returns {noBlockage:true} and builds nothing (pressing it
|
||||
// with no blockage used to route to a bogus far point). Returns {points,target} on
|
||||
// success, null if a detour couldn't be built, {noBlockage:true} if nothing to do.
|
||||
async function rejoinReroute(cur, bearingDeg, routePts, nogos) {
|
||||
if (!cur || !routePts || routePts.length < 2) return null;
|
||||
let cum = [0];
|
||||
for (let i = 1; i < routePts.length; i++) cum[i] = cum[i-1] + GPS.distance(routePts[i-1].lat, routePts[i-1].lon, routePts[i].lat, routePts[i].lon);
|
||||
let nearestRouteDist = (pt) => { let d = Infinity; for (let p of routePts) { let dd = GPS.distance(pt.lat, pt.lon, p.lat, p.lon); if (dd < d) d = dd; } return d; };
|
||||
// reference point of the blockage
|
||||
let ref = cur;
|
||||
if (nogos && nogos.length) {
|
||||
let best = null, bestD = Infinity;
|
||||
for (let n of nogos) if (nearestRouteDist(n) <= 150) { let dc = GPS.distance(cur.lat, cur.lon, n.lat, n.lon); if (dc < bestD) { bestD = dc; best = n; } }
|
||||
if (best) ref = best;
|
||||
let inNogo = (p) => nogos && nogos.some(n => GPS.distance(p.lat, p.lon, n.lat, n.lon) <= (n.radius || 25) + 15);
|
||||
|
||||
// current progress = nearest route point to the current position
|
||||
let curIdx = 0, curD = Infinity;
|
||||
for (let i = 0; i < routePts.length; i++) { let d = GPS.distance(cur.lat, cur.lon, routePts[i].lat, routePts[i].lon); if (d < curD) { curD = d; curIdx = i; } }
|
||||
|
||||
// travel direction along the route: increasing index unless the compass bearing
|
||||
// clearly points the other way (handles routes ridden in reverse / loops).
|
||||
let step = 1;
|
||||
if (bearingDeg != null && !isNaN(bearingDeg)) {
|
||||
let ja = Math.min(routePts.length - 1, curIdx + 1), jb = Math.max(0, curIdx - 1);
|
||||
if (ja !== jb) {
|
||||
let fb = GPS.bearing(routePts[jb].lat, routePts[jb].lon, routePts[ja].lat, routePts[ja].lon);
|
||||
let diff = Math.abs(((bearingDeg - fb + 540) % 360) - 180); // 0 = aligned w/ increasing idx, 180 = opposite
|
||||
if (diff > 90) step = -1; // travelling against increasing index → forward is decreasing idx
|
||||
}
|
||||
}
|
||||
let refIdx = 0, refD = Infinity;
|
||||
for (let i = 0; i < routePts.length; i++) { let d = GPS.distance(ref.lat, ref.lon, routePts[i].lat, routePts[i].lon); if (d < refD) { refD = d; refIdx = i; } }
|
||||
|
||||
// blockage: first route point within a nogo AHEAD, within a lookahead window
|
||||
const LOOKAHEAD = 2500;
|
||||
let blocked = null;
|
||||
for (let i = curIdx; i >= 0 && i < routePts.length; i += step) {
|
||||
if (Math.abs(cum[i] - cum[curIdx]) > LOOKAHEAD) break;
|
||||
if (inNogo(routePts[i])) { blocked = i; break; }
|
||||
}
|
||||
if (blocked == null) return { noBlockage: true };
|
||||
|
||||
// rejoin target: first route point clear of every nogo AND >= margin past the blockage
|
||||
const CLEAR_MARGIN = 300;
|
||||
let ti = blocked;
|
||||
while (ti >= 0 && ti < routePts.length) {
|
||||
if (!inNogo(routePts[ti]) && Math.abs(cum[ti] - cum[blocked]) >= CLEAR_MARGIN) break;
|
||||
ti += step;
|
||||
}
|
||||
if (ti < 0 || ti >= routePts.length) ti = step > 0 ? routePts.length - 1 : 0;
|
||||
|
||||
let helper = (bearingDeg != null && !isNaN(bearingDeg)) ? _offsetLatLon(cur.lat, cur.lon, bearingDeg, 40) : null;
|
||||
let skip = 1500;
|
||||
for (let attempt = 0; attempt < 8; attempt++) {
|
||||
let want = cum[refIdx] + skip, ti = refIdx;
|
||||
while (ti < routePts.length - 1 && cum[ti] < want) ti++;
|
||||
let jump = Math.max(1, Math.round(routePts.length / 50));
|
||||
for (let attempt = 0; attempt < 6; attempt++) {
|
||||
let target = routePts[ti];
|
||||
let straight = GPS.distance(cur.lat, cur.lon, target.lat, target.lon);
|
||||
let wps = helper ? [[cur.lon, cur.lat], [helper.lon, helper.lat], [target.lon, target.lat]] : [[cur.lon, cur.lat], [target.lon, target.lat]];
|
||||
|
|
@ -3633,8 +3662,9 @@ async function rejoinReroute(cur, bearingDeg, routePts, nogos) {
|
|||
if (rlen <= Math.max(500, straight * 3)) return { points: rp.map(p => ({ lat: p.lat, lon: p.lon })), target: { lat: target.lat, lon: target.lon } };
|
||||
}
|
||||
} catch (e) { /* try a further rejoin point */ }
|
||||
if (ti >= routePts.length - 1) break;
|
||||
skip += 1500;
|
||||
let next = ti + step * jump;
|
||||
if (next < 0 || next >= routePts.length) break;
|
||||
ti = next;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -3797,22 +3827,35 @@ function showShareModal(session, stats, points) {
|
|||
}
|
||||
|
||||
// Slope tint by absolute grade in %, in 1%-wide discrete bands (hard boundaries
|
||||
// kept — one colour per segment): green -> yellow -> red -> brown -> black.
|
||||
// <1%=0 .. 9–10%=9, >=10% = black. Both up- and downhill coloured by magnitude.
|
||||
let ROUTE_SLOPE_BANDS = [
|
||||
// Slope is coloured by DIRECTION of travel (point order = travel direction):
|
||||
// uphill climbs through a WARM fan (green -> yellow -> red -> brown -> black),
|
||||
// downhill drops through a COOL fan (green -> cyan -> blue -> navy). Flat (<1%)
|
||||
// is green either way. 1% bands: <1%=0 .. 9–10%=9, >=10% = last.
|
||||
let ROUTE_SLOPE_UP = [
|
||||
"#2e7d32", "#7cb342", "#aeea00", "#ffeb3b", "#ffc107",
|
||||
"#ff9800", "#ff5722", "#e53935", "#b71c1c", "#6d4c41", "#000000",
|
||||
];
|
||||
let ROUTE_SLOPE_DOWN = [
|
||||
"#2e7d32", "#2e9e5b", "#26a69a", "#00bcd4", "#29b6f6",
|
||||
"#1e88e5", "#1976d2", "#1565c0", "#0d47a1", "#0a357a", "#001f5c",
|
||||
];
|
||||
function routeSlopeColor(gradePct) {
|
||||
let idx = Math.min(10, Math.floor(Math.abs(gradePct || 0)));
|
||||
return ROUTE_SLOPE_BANDS[idx];
|
||||
let g = gradePct || 0;
|
||||
let idx = Math.min(10, Math.floor(Math.abs(g)));
|
||||
return g < 0 ? ROUTE_SLOPE_DOWN[idx] : ROUTE_SLOPE_UP[idx];
|
||||
}
|
||||
// Compact colour strip for the slope legend (every 1% band, 0 → 10%+).
|
||||
// Compact colour strips for the slope legend (every 1% band, 0 → 10%+), both fans.
|
||||
function slopeLegendHtml() {
|
||||
let cells = ROUTE_SLOPE_BANDS
|
||||
.map(c => `<span style="display:inline-block;width:11px;height:10px;background:${c};vertical-align:middle"></span>`)
|
||||
let strip = arr => arr
|
||||
.map(c => `<span style="display:inline-block;width:9px;height:10px;background:${c};vertical-align:middle"></span>`)
|
||||
.join("");
|
||||
return `уклон 0% ${cells} 10%+`;
|
||||
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
|
||||
|
|
@ -3959,10 +4002,51 @@ async function showRoutesScreen(container) {
|
|||
// Route preview: map (topo casing + tappable altitude/grade points) + elevation
|
||||
// profile chart + ascent/descent/max-grade — so you can see how hilly a hand-drawn
|
||||
// route is BEFORE riding it. Reuses fetchElevations / GPS.routeElevStats / Chart.js.
|
||||
// Elevation correction grid from ridden tracks (Rust get_elev_correction): per ~20m
|
||||
// cell, the median (measured − SRTM) offset over >= N passes. Cached per session.
|
||||
let _elevCorr = null;
|
||||
async function loadElevCorrection(force) {
|
||||
if (_elevCorr && !force) return _elevCorr;
|
||||
let minPasses = parseInt(localStorage.getItem("baldav_elev_corr_passes")) || 3;
|
||||
let raw = await window.__TAURI__.core.invoke("get_elev_correction", { minPasses });
|
||||
let arr = JSON.parse(raw) || [];
|
||||
let dLat = 0.00018, dLon = 0.00027, cells = new Map();
|
||||
for (let [lat, lon, delta] of arr) cells.set(Math.round(lat / dLat) + "," + Math.round(lon / dLon), delta);
|
||||
_elevCorr = { cells, dLat, dLon };
|
||||
return _elevCorr;
|
||||
}
|
||||
// SRTM stays the base; nudge each point up/down by the SMOOTHED ridden offset where
|
||||
// you've ridden enough. No coverage -> delta 0 -> unchanged. Non-destructive (returns
|
||||
// a copy), so the stored route keeps its original elevations.
|
||||
async function applyRiddenElevCorrection(pts) {
|
||||
let corr;
|
||||
try { corr = await loadElevCorrection(); } catch { return pts; }
|
||||
if (!corr || !corr.cells.size) return pts;
|
||||
let deltas = pts.map(p => {
|
||||
if (p.ele == null) return 0;
|
||||
let ci = Math.round(p.lat / corr.dLat), cj = Math.round(p.lon / corr.dLon);
|
||||
let v0 = corr.cells.get(ci + "," + cj);
|
||||
if (v0 != null) return v0;
|
||||
for (let di = -1; di <= 1; di++) for (let dj = -1; dj <= 1; dj++) {
|
||||
let v = corr.cells.get((ci + di) + "," + (cj + dj));
|
||||
if (v != null) return v;
|
||||
}
|
||||
return 0; // no ridden coverage here
|
||||
});
|
||||
let half = 3;
|
||||
return pts.map((p, i) => {
|
||||
if (p.ele == null) return p;
|
||||
let sum = 0, cnt = 0;
|
||||
for (let j = Math.max(0, i - half); j <= Math.min(pts.length - 1, i + half); j++) { sum += deltas[j]; cnt++; }
|
||||
let d = cnt ? sum / cnt : 0;
|
||||
return d ? { ...p, ele: p.ele + d } : p;
|
||||
});
|
||||
}
|
||||
|
||||
async function showRouteDetail(container, routeId) {
|
||||
let route = await DB.getRoute(routeId);
|
||||
if (!route || !route.points || route.points.length < 2) { showToast("Маршрут пуст"); showRoutesScreen(container); return; }
|
||||
let pts = route.points;
|
||||
let pts = await applyRiddenElevCorrection(route.points);
|
||||
let st = GPS.routeElevStats(pts);
|
||||
let mode = localStorage.getItem("baldav_route_color_mode") || "slope";
|
||||
let legendHtml = mode === "slope"
|
||||
|
|
@ -4129,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();
|
||||
|
|
@ -4145,8 +4231,10 @@ async function showRouteEditor(container, routeId) {
|
|||
</div>
|
||||
<div class="re-bottom">
|
||||
<div class="re-hint">тап карты — точка · тап линии — вставить точку · долгий тап — дорога/прямая · тап точки — удалить</div>
|
||||
<div id="re-danger" class="re-danger"></div>
|
||||
<div class="re-btns">
|
||||
<button id="re-brouter" class="btn-secondary active" title="Все отрезки: по дорогам / прямые">🚲 По дорогам</button>
|
||||
<button id="re-adventure" class="btn-secondary" title="Броды и сложные тропы: избегать / разрешить">Броды: избегать</button>
|
||||
<button id="re-undo" class="btn-secondary">Отменить</button>
|
||||
<button id="re-save" class="btn-primary">Сохранить</button>
|
||||
</div>
|
||||
|
|
@ -4171,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).
|
||||
|
|
@ -4203,6 +4291,36 @@ 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 wet = all.filter(x => x.type === "wetcross").length;
|
||||
let rough = all.filter(x => x.type === "rough").length;
|
||||
let dEl = document.querySelector("#re-danger");
|
||||
if (dEl) {
|
||||
let bits = [];
|
||||
if (fords) bits.push(`брод: ${fords}`);
|
||||
if (wet) bits.push(`возм.брод: ${wet}`);
|
||||
if (rough) bits.push(`грунт: ${rough}`);
|
||||
dEl.textContent = bits.length ? "⚠ " + bits.join(" · ") : "";
|
||||
}
|
||||
if (!all.length) return;
|
||||
// ford = confirmed OSM ford (solid blue); wetcross = likely untagged ford from a high
|
||||
// BRouter river-class on a track (light cyan — a guess); rough = orange; ferry = purple.
|
||||
let COL = { ford: "#0288d1", wetcross: "#4dd0e1", rough: "#ff9800", ferry: "#9c27b0" };
|
||||
let LBL = { ford: "брод", wetcross: "возм. брод", rough: "грунт/тропа", ferry: "паром" };
|
||||
let g = L.layerGroup();
|
||||
for (let d of all) {
|
||||
L.circleMarker([d.lat, d.lon], { radius: 7, color: "#fff", weight: 2, fillColor: COL[d.type] || "#9c27b0", fillOpacity: 1 })
|
||||
.bindTooltip(LBL[d.type] || d.type, { 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.
|
||||
|
|
@ -4231,10 +4349,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();
|
||||
}
|
||||
|
|
@ -4394,6 +4515,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");
|
||||
|
|
@ -4740,6 +4875,36 @@ async function sendLocationSms() {
|
|||
} catch (e) { appAlert("Ошибка отправки: " + e); }
|
||||
}
|
||||
|
||||
// "Отдать смотрящему": hand the viewer the active route + ridden roads + track-so-far. Sent to
|
||||
// the server's RAM relay (NOT stored on its disk); the viewer pulls it once into their own
|
||||
// localStorage. One-shot; press again to resend (e.g. after the track grew).
|
||||
async function shareRideContext() {
|
||||
let url = (localStorage.getItem("baldav_live_url") || "").trim();
|
||||
let token = (localStorage.getItem("baldav_live_token") || "").trim();
|
||||
if (!url || !token) { appAlert("Сначала задай адрес и токен трансляции в профиле"); return; }
|
||||
let shareUrl = url.endsWith("/push") ? url.slice(0, -5) + "/share" : url.replace(/\/[^/]*$/, "/share");
|
||||
let track = (GPS.recording.points || []).map(p => [+p.lat, +p.lon]);
|
||||
let route = [];
|
||||
try {
|
||||
let rid = localStorage.getItem("baldav_active_route");
|
||||
if (rid) { let r = await DB.getRoute(rid); if (r && r.points) route = r.points.map(p => [+p.lat, +p.lon]); }
|
||||
} catch (e) {}
|
||||
let roads = [];
|
||||
try {
|
||||
let segs = buildRoadSegments(await DB.getRoadPoints());
|
||||
roads = segs.map(s => ({ p: s.pts.map(q => [+q.lat, +q.lon]), t: s.atype || "b" }));
|
||||
} catch (e) {}
|
||||
showToast("Отдаю смотрящему...");
|
||||
try {
|
||||
let raw = await window.__TAURI__.core.invoke("http_request", {
|
||||
url: shareUrl, method: "POST", body: JSON.stringify({ token, route, roads, track })
|
||||
});
|
||||
let res = JSON.parse(raw || "{}");
|
||||
if (res.ok) showToast(`Отдано: маршрут ${route.length}, дороги ${roads.length}, трек ${track.length}`);
|
||||
else appAlert("Не отдалось: " + (res.error || "ошибка"));
|
||||
} catch (e) { appAlert("Не отдалось: " + (e.message || e)); }
|
||||
}
|
||||
|
||||
async function renderRecordingInline(container) {
|
||||
if (!GPS.recording.active) { renderScreen("gps"); return; }
|
||||
|
||||
|
|
@ -5026,6 +5191,14 @@ async function renderRecordingInline(container) {
|
|||
smsBtn.onclick = () => sendLocationSms();
|
||||
btnWrap.appendChild(smsBtn);
|
||||
|
||||
// Hand the viewer the route + ridden roads + track (via the server's RAM relay, not its disk).
|
||||
let shareBtn = document.createElement("button");
|
||||
shareBtn.className = "rec-map-btn rec-share-btn";
|
||||
shareBtn.textContent = "Отд";
|
||||
shareBtn.title = "Отдать смотрящему: маршрут, проезженные дороги и пройденный трек";
|
||||
shareBtn.onclick = () => shareRideContext();
|
||||
btnWrap.appendChild(shareBtn);
|
||||
|
||||
// Roads toggle button. Roads are viewport-culled onto a canvas pane (only the segments in
|
||||
// view are drawn, redrawn on pan/zoom) so a huge road network doesn't jank the WebView.
|
||||
let roadsBtn = document.createElement("button");
|
||||
|
|
@ -5125,6 +5298,7 @@ async function renderRecordingInline(container) {
|
|||
let ng = await getNogosForRouter();
|
||||
let bearing = (typeof smoothedHeading === "number") ? smoothedHeading : null;
|
||||
let res = await rejoinReroute(cur, bearing, route.points, ng);
|
||||
if (res && res.noBlockage) { showToast("Впереди затыка нет (нет nogo на маршруте)"); rerouteBtn.classList.remove("active"); return; }
|
||||
if (!res) { showToast("Объезд не найден"); rerouteBtn.classList.remove("active"); return; }
|
||||
if (rerouteLayer) { map.removeLayer(rerouteLayer); rerouteLayer = null; }
|
||||
let ll = res.points.map(p => [p.lat, p.lon]);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
|
||||
|
|
|
|||
|
|
@ -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 ) )
|
||||
|
|
@ -58,6 +58,8 @@ assign processUnusedTags = false # %processUnusedTags% | Set true to
|
|||
|
||||
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 = false # %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
|
||||
|
|
@ -128,6 +130,14 @@ assign initialclassifier =
|
|||
# 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
|
||||
|
|
@ -297,6 +307,7 @@ assign costfactor
|
|||
add no_river_penalty
|
||||
add noise_penalty
|
||||
add traffic_penalty
|
||||
add adventurepenalty
|
||||
|
||||
#
|
||||
# apply oneway-and access-penalties
|
||||
|
|
@ -432,7 +443,7 @@ assign footaccess =
|
|||
else not foot=private|no
|
||||
|
||||
assign initialcost =
|
||||
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 )
|
||||
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 ) )
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import android.provider.Settings
|
|||
import android.os.Bundle
|
||||
import android.os.FileObserver
|
||||
import android.provider.ContactsContract
|
||||
import android.telephony.SmsManager
|
||||
import android.util.Log
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
|
|
@ -391,6 +390,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<DoubleArray>()
|
||||
for (i in 0 until arr.length()) {
|
||||
|
|
@ -406,9 +406,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 +422,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<DoubleArray>, nogos: List<DoubleArray> = emptyList()): String {
|
||||
private fun routeBrouter(segmentsDir: File, profilePath: String, pts: List<DoubleArray>, nogos: List<DoubleArray> = emptyList(), format: String = "gpx"): String {
|
||||
val rc = btools.router.RoutingContext()
|
||||
rc.memoryclass = 128
|
||||
rc.localFunction = profilePath
|
||||
|
|
@ -457,37 +457,13 @@ 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
|
||||
// (the in-app confirm was the only tap). Otherwise request the permission for next time and fall
|
||||
// back to the system SMS composer, prefilled — which needs no permission. Either path works with
|
||||
// no internet, over 2G/EDGE.
|
||||
// Send the location SMS by opening the system SMS app prefilled (ACTION_SENDTO). This needs
|
||||
// NO permission by design — the user taps send in their SMS app. Works offline over 2G/EDGE.
|
||||
private fun sendSms(number: String, text: String) {
|
||||
if (number.isBlank()) return
|
||||
val granted = checkSelfPermission(android.Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED
|
||||
if (granted) {
|
||||
try {
|
||||
val sm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
getSystemService(SmsManager::class.java)
|
||||
else
|
||||
@Suppress("DEPRECATION") SmsManager.getDefault()
|
||||
val parts = sm.divideMessage(text)
|
||||
if (parts.size > 1) sm.sendMultipartTextMessage(number, null, parts, null, null)
|
||||
else sm.sendTextMessage(number, null, text, null, null)
|
||||
Log.d("BalDav", "SMS sent directly to $number")
|
||||
runOnUiThread {
|
||||
android.widget.Toast.makeText(this, "СМС отправлено", android.widget.Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
Log.w("BalDav", "direct SMS failed, falling back to composer: ${e.message}")
|
||||
}
|
||||
} else {
|
||||
// Ask for the permission so the next send can go directly; this one still uses the composer.
|
||||
try { requestPermissions(arrayOf(android.Manifest.permission.SEND_SMS), 4343) } catch (_: Exception) {}
|
||||
}
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:$number")).apply {
|
||||
putExtra("sms_body", text)
|
||||
|
|
|
|||
|
|
@ -441,6 +441,55 @@ fn get_ride_graph(app: tauri::AppHandle, min_speed_ms: f64) -> Result<String, St
|
|||
Ok(format!("{{\"nodes\":[{}],\"edges\":[{}]}}", nodes_json, edges_json))
|
||||
}
|
||||
|
||||
// Elevation correction grid from ridden tracks: per ~20m cell, the MEDIAN vertical
|
||||
// offset (measured altitude_gps_m minus terrain altitude_srtm_m) over all passes.
|
||||
// SRTM stays the base; this is only the up/down nudge where you've actually ridden.
|
||||
// Returns [[cellLat, cellLon, medianDelta, passes], ...] for cells ridden on at least
|
||||
// `min_passes` distinct sessions. The JS side adds a smoothed delta onto SRTM elevations.
|
||||
#[tauri::command]
|
||||
fn get_elev_correction(app: tauri::AppHandle, min_passes: u32) -> Result<String, String> {
|
||||
use std::collections::{HashMap, HashSet};
|
||||
let data_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
|
||||
let db_path = {
|
||||
let p1 = data_dir.join("../../baldav.db");
|
||||
let p2 = data_dir.join("baldav.db");
|
||||
let p3 = std::path::PathBuf::from(data_dir.to_str().unwrap_or("").replace("/files/dev.baldav.app", "")).join("baldav.db");
|
||||
if p1.exists() { p1 } else if p2.exists() { p2 } else { p3 }
|
||||
};
|
||||
let db = rusqlite::Connection::open(&db_path).map_err(|e| format!("open: {e}"))?;
|
||||
let d_lat = 0.00018_f64; // ~20 m (same grid as the ride graph)
|
||||
let d_lon = 0.00027_f64;
|
||||
let mut stmt = db.prepare(
|
||||
"SELECT lat, lon, altitude_gps_m, altitude_srtm_m, session_id FROM gps_points
|
||||
WHERE altitude_gps_m IS NOT NULL AND altitude_srtm_m IS NOT NULL AND COALESCE(walk,0)=0"
|
||||
).map_err(|e| format!("prepare: {e}"))?;
|
||||
let rows = stmt.query_map([], |r| {
|
||||
Ok((r.get::<_, f64>(0)?, r.get::<_, f64>(1)?, r.get::<_, f64>(2)?, r.get::<_, f64>(3)?, r.get::<_, i64>(4)?))
|
||||
}).map_err(|e| format!("query: {e}"))?;
|
||||
|
||||
let mut cells: HashMap<(i64, i64), (Vec<f64>, HashSet<i64>)> = HashMap::new();
|
||||
for row in rows {
|
||||
let (lat, lon, gps, srtm, sid) = row.map_err(|e| format!("row: {e}"))?;
|
||||
let delta = gps - srtm;
|
||||
if !delta.is_finite() || delta.abs() > 300.0 { continue; } // guard against garbage fixes
|
||||
let c = ((lat / d_lat).round() as i64, (lon / d_lon).round() as i64);
|
||||
let e = cells.entry(c).or_insert_with(|| (Vec::new(), HashSet::new()));
|
||||
e.0.push(delta);
|
||||
e.1.insert(sid);
|
||||
}
|
||||
let mut out: Vec<String> = Vec::new();
|
||||
for ((clat, clon), (mut deltas, sessions)) in cells {
|
||||
if (sessions.len() as u32) < min_passes { continue; }
|
||||
deltas.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||
let m = deltas.len();
|
||||
let median = if m % 2 == 1 { deltas[m / 2] } else { (deltas[m / 2 - 1] + deltas[m / 2]) / 2.0 };
|
||||
let lat = (clat as f64) * d_lat;
|
||||
let lon = (clon as f64) * d_lon;
|
||||
out.push(format!("[{:.6},{:.6},{:.2},{}]", lat, lon, median, sessions.len()));
|
||||
}
|
||||
Ok(format!("[{}]", out.join(",")))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AppInfo {
|
||||
version: String,
|
||||
|
|
@ -579,7 +628,7 @@ fn write_app_root_file(app: &tauri::AppHandle, name: &str, contents: &str) {
|
|||
// FileObserver watches, then poll for brouter_result_<id>.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<String>) -> Result<String, String> {
|
||||
fn brouter_route(app: tauri::AppHandle, lonlats: String, profile: String, nogos: Option<String>, format: Option<String>) -> Result<String, String> {
|
||||
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()
|
||||
|
|
@ -587,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).
|
||||
|
|
@ -1261,7 +1313,7 @@ pub fn run() {
|
|||
.add_migrations("sqlite:baldav.db", migrations)
|
||||
.build(),
|
||||
)
|
||||
.invoke_handler(tauri::generate_handler![get_safe_insets, import_seed_products, import_tiles, download_file, get_points_filename, delete_points_file, gps_set_session_id, list_points_files, read_points_file, pick_file_open, get_picked_file, debug_native_paths, rebuild_roads, get_road_points, get_ride_graph, get_app_info, gps_service_command, set_hydration_config, set_live_config, send_sms, pick_contact, get_contact_result, gps_service_read_points, brouter_route, brouter_download_tile, brouter_download_status, brouter_list_tiles, save_file, export_image, read_file_base64, export_db, import_db, restart_app, get_update_info, http_request, chzn_http, ble::ble_scan, ble::ble_scan_results, ble::ble_connect, ble::ble_disconnect, ble::ble_get_hr, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_read_hr_log, ble::ble_read_strap_uptime, ble::ble_reset_strap_uptime, ble::ble_request_enable, read_state_file])
|
||||
.invoke_handler(tauri::generate_handler![get_safe_insets, import_seed_products, import_tiles, download_file, get_points_filename, delete_points_file, gps_set_session_id, list_points_files, read_points_file, pick_file_open, get_picked_file, debug_native_paths, rebuild_roads, get_road_points, get_ride_graph, get_elev_correction, get_app_info, gps_service_command, set_hydration_config, set_live_config, send_sms, pick_contact, get_contact_result, gps_service_read_points, brouter_route, brouter_download_tile, brouter_download_status, brouter_list_tiles, save_file, export_image, read_file_base64, export_db, import_db, restart_app, get_update_info, http_request, chzn_http, ble::ble_scan, ble::ble_scan_results, ble::ble_connect, ble::ble_disconnect, ble::ble_get_hr, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_read_hr_log, ble::ble_read_strap_uptime, ble::ble_reset_strap_uptime, ble::ble_request_enable, read_state_file])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
|
|
|||
449
tools/brouter-web-staging/profiles2/trekking-adventure.brf
Normal file
449
tools/brouter-web-staging/profiles2/trekking-adventure.brf
Normal file
|
|
@ -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 ) )
|
||||
449
tools/brouter-web-staging/profiles2/trekking.brf
Normal file
449
tools/brouter-web-staging/profiles2/trekking.brf
Normal file
|
|
@ -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 = false # %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 ) )
|
||||
Loading…
Add table
Add a link
Reference in a new issue