hike-finder
Finds marked hiking routes from OpenStreetMap filtered by locally-computed elevation gain, distance, loop shape, and car/chairlift access, providing consistent and tunable metrics instead of third-party scraped data.
README
hike-finder-mcp
Find marked hiking routes from OpenStreetMap and filter them by real, locally-computed elevation gain and distance — not numbers scraped from trail-description websites — plus shape and access: whether a route is a loop, and whether you can reach it by car or chairlift.
It runs three ways on one engine: a command-line tool, a local web UI (a map you pan to your area), or an MCP server for LLM clients. The CLI and web UI need no LLM and no MCP client — they're plain standalone programs.
It targets OSM route relations (route=hiking/foot), the same signed,
maintained trail data — including the Czech KČT network — that mapy.cz
renders. Distance and elevation gain are computed in this codebase, so the
numbers are consistent and tunable instead of inherited from a third party.
Why this exists
Trail sites (AllTrails, Komoot, mapy.cz) all report different gain for the same trail because elevation gain depends entirely on how you sample and de-noise the terrain. This tool makes that step explicit and consistent: it resamples each track to even spacing, smooths the elevation series, and counts climbs with a hysteresis threshold so DEM noise isn't mistaken for ascent.
Filters
find_hikes(south, west, north, east, …) takes these optional filters:
| Filter | Meaning | Confidence |
|---|---|---|
min_gain_m / max_gain_m |
elevation gain bounds (m), computed locally | high |
min_distance_km / max_distance_km |
route length bounds | high |
circular |
true = loops only, false = point-to-point only |
high |
car_access |
true/false: is amenity=parking mapped near a trail end? |
best-effort |
chairlift_access |
true/false: is a ride-up aerialway (chairlift/gondola/cable car) mapped near a trail end? |
best-effort |
The three boolean filters are tri-state: omit = don't care, true = require,
false = exclude. Honesty note: car_access/chairlift_access reflect OSM
mapping, not the world — a false means nothing of that kind is mapped near the
route's ends, not that it's impossible to get there. Loop detection is reliable.
Internally the search is two-pass: cheap geometry/shape/access filters run first and a long through-route that merely crosses the area is dropped, so the elevation backend is only queried for routes that already match.
Two elevation backends (both supported)
| Mode | Source | Setup | Accuracy | Limits |
|---|---|---|---|---|
api |
Open-Elevation / OpenTopoData | none | coarser | rate-limited (per-sec throttle + daily counter, both managed) |
local |
SRTM/ASTER GeoTIFF tiles | download tiles once | high | none |
auto |
local if available, else api | optional tiles | best available | graceful fallback |
Set via HIKE_ELEVATION_MODE. See src/hike_finder/config.py.
Quickstart
pip install -e . # CLI + web UI; no LLM / MCP stack required
# browser: pan a map to your area, set filters, search
hike-finder-web # then open http://127.0.0.1:8765
# terminal: one command, prints results
hike-finder --bbox 50.72 15.58 50.74 15.62 --circular --user-agent you@example.com
Using it
Three frontends, one engine. The CLI and web UI need no LLM and no MCP client.
Install
pip install -e . # base: the `hike-finder` CLI and `hike-finder-web` UI
pip install -e ".[mcp]" # + the MCP server (`hike-finder-mcp`)
pip install -e ".[local-dem]" # + the local GeoTIFF DEM elevation backend (needs rasterio)
pip install -e ".[dev]" # + pytest
Extras combine: pip install -e ".[mcp,local-dem]".
Set a contact for Overpass. OSM's public server rejects the default User-Agent
with 406. Provide a real email/URL via --user-agent (CLI), the Contact field
(web UI), or HIKE_OVERPASS_UA=you@example.com in the environment — per
OSM etiquette.
Option A — Web UI (easiest; no coordinates to type)
hike-finder-web # serves http://127.0.0.1:8765 (--host/--port to change)
Open it, pan/zoom the map to your area, fill in the contact field, choose filters (shape, car/chairlift access, gain and distance ranges), then click "Search this map area". Matches are listed and pinned at their start point — click one to jump to it. This is the answer to "how do I get a bounding box": you draw it by moving the map. Pure standard library, no web-framework dependency.
Option B — Command line
hike-finder --bbox 50.72 15.58 50.74 15.62 \
--circular --chairlift-access \
--user-agent you@example.com
--bbox is south west north east (min-lat min-lon max-lat max-lon). The
three boolean filters are tri-state: omit = don't care, --circular = require,
--no-circular = exclude (same for --car-access and --chairlift-access).
Numeric filters: --min-gain/--max-gain (m), --min-distance/--max-distance
(km). Add --json for machine-readable output. hike-finder --help lists all.
Each match prints as one line:
<name> — <km> km, +<gain> m / -<loss> m [loop, car, lift:chair_lift] (start <lat>,<lon>, OSM relation <id>)
The [...] flags: loop/one-way, then car and/or lift:<type> when access
is mapped near an endpoint.
Option C — MCP server (drive it from an LLM client)
Needs the mcp extra. Register the hike-finder-mcp command:
claude mcp add hike-finder --env HIKE_OVERPASS_UA=you@example.com -- hike-finder-mcp
.mcp.json / Claude Desktop config (equivalent):
{
"mcpServers": {
"hike-finder": {
"command": "hike-finder-mcp",
"env": { "HIKE_OVERPASS_UA": "you@example.com" }
}
}
}
Then ask in plain language ("find loop hikes near Špindlerův Mlýn reachable by
chairlift") and the client calls find_hikes(south, west, north, east, …) with
the same filters as the CLI.
The MCP registration form isn't live-verified in this repo (the build env has no
mcpSDK). The SDK's decorator API has shifted across versions — if the server won't start, check the imports insrc/hike_finder/server.pyagainst your installedmcpversion (seeHANDOFF.md).
Getting a bounding box (CLI / MCP)
The web UI gives you the box for free. For the CLI or MCP you supply four corners
in the order south, west, north, east (min latitude, min longitude, max
latitude, max longitude):
- openstreetmap.org → "Export" tab draws a draggable box and shows its four edges — copy them straight in.
- Or read the corners off mapy.cz for the area you're planning.
Validated live (2026-06-23): the bbox
50.72,15.58,50.74,15.62(Špindlerův Mlýn) returned 12 routes (each flagged forcar/lift/shape), with a computed gain/loss for every one — e.g. [Z] Richtrovy Boudy → Špindlerův mlýn at +678 m / −251 m. The detected loop Špindlerův mlýn – okruh came back +34 m / −34 m — gain ≈ loss, exactly as a closed loop must, which cross-checks the whole sampling/gain pipeline. Loop detection was also validated live (2026-06-23) against the real "Medvěd*" relations — which caught and corrected an over-reporting bug; closure now reads the member ways as a vertex graph (circuit rank), independent of way-stitching. Distance was also hardened here (2026-06-23): it now sums every member way's length rather than the greedily-stitched line, so branched relations that the stitch couldn't chain no longer under-count (validated live by a per-route stitched-vs-summed diff). Remaining caveat: the local DEM backend (mode=local) is still untested, and the trail's start/endpoints (used for car/lift access) still come from the greedy stitch on branched relations — both tracked inHANDOFF.md.
Configuration (environment variables)
All optional except where noted; defaults come from src/hike_finder/config.py.
| Variable | Meaning | Default |
|---|---|---|
HIKE_OVERPASS_UA |
User-Agent for Overpass — required by the public server; use a real contact | generic UA naming no contact |
HIKE_OVERPASS_URL |
Override the Overpass endpoint (use a regional/self-hosted instance for heavy use) | overpass-api.de |
HIKE_ELEVATION_MODE |
api | local | auto |
auto |
HIKE_DEM_DIR |
GeoTIFF DEM tile directory (for local/auto) |
— |
HIKE_API_ENDPOINT |
Override the elevation API endpoint | provider default |
HIKE_API_MIN_INTERVAL |
Min seconds between elevation-API requests (keeps you under the public ~1 req/sec limit) | 1.1 |
HIKE_API_MAX_RETRIES |
Retries on transient API errors (429 / 5xx / network), with exponential backoff honouring Retry-After |
3 |
HIKE_API_BACKOFF |
Backoff base seconds, doubled each retry | 2.0 |
HIKE_API_MAX_BACKOFF |
Cap on any single wait, seconds; a Retry-After above this (e.g. a daily-quota 429) makes the route degrade to n/a instead of stalling |
30 |
HIKE_API_DAILY_LIMIT |
Max elevation-API requests per UTC day, counted in a persistent file across runs; at the cap, routes degrade to n/a instead of getting the IP banned. 0 disables tracking |
1000 |
HIKE_API_STATE_DIR |
Directory holding the daily-counter file | per-user cache (%LOCALAPPDATA%/hike-finder or ~/.cache/hike-finder) |
HIKE_GAIN_THRESHOLD |
Hysteresis climb threshold, metres (must exceed peak-to-peak DEM noise) | 10 |
HIKE_SAMPLE_INTERVAL |
Resample spacing along the track, metres | 25 |
HIKE_SMOOTH_WINDOW |
Elevation smoothing window, samples | 3 |
HIKE_LOOP_TOLERANCE |
start≈end distance that closes a loop, metres | 150 |
HIKE_CAR_RADIUS |
Parking-near-endpoint radius, metres | 300 |
HIKE_LIFT_RADIUS |
Lift-station-near-endpoint radius, metres | 400 |
HIKE_MAX_ROUTE_FACTOR |
Drop routes longer than this × the bbox diagonal (kills through-routes) | 4.0 |
Troubleshooting
406 Not Acceptable/ every Overpass request fails → setHIKE_OVERPASS_UAto a real contact. The public server rejects the default Python User-Agent.- No hikes returned → widen the bbox or loosen the filters. Note that loops are
genuinely sparse in KČT data (most relations are linear marked segments), so
circular=truelegitimately returns few results. - Slow / occasional
504→ public Overpass overload; the client retries with backoff. PointHIKE_OVERPASS_URLat a regional instance for heavy use.
Status
Core geometry, gain, access/shape math, the Overpass response parser, the
elevation-API client (including its rate-limit throttle, transient-error
retry/backoff, and a persistent daily-request counter that degrades to n/a
before blowing the API's daily cap), and the CLI argument/formatter layer:
implemented and unit-tested (72 tests, all offline). The Overpass HTTP call and the API
elevation backend are validated live (CLI + web), with computed gain
cross-checked against the loop invariant (gain ≈ loss). The local-DEM backend
and the MCP entry point are implemented; validate on a networked machine.
See HANDOFF.md for exactly what's done and what's next.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。