Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ BUILD_ELEVATION=True
# MAP_MATCH_MAX_POINTS=10000
# MAP_MATCH_CONCURRENCY=4
# VALHALLA_MATCH_TIMEOUT=60000
# Comma-separated coverage labels for a remote Valhalla whose source files are
# not mounted into Atlas. This is operator-declared metadata, not a health check.
# VALHALLA_COVERAGE_REGIONS=Germany

# Transit API default; Settings persists the selected engine.
TRANSIT_BACKEND=motis
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to Dawarich Atlas are documented here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- External Valhalla deployments can declare their routing and map-matching coverage with `VALHALLA_COVERAGE_REGIONS`, so API clients do not lose regional metadata when Valhalla's source files live outside Atlas.

## [0.6.1] - 2026-09-22

### Added
Expand Down
55 changes: 53 additions & 2 deletions app-phoenix/lib/atlas/control/service_coverage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ defmodule Atlas.Control.ServiceCoverage do

read_opts =
opts
|> Keyword.drop([:health, :transit_backend])
|> Keyword.drop([:health, :transit_backend, :valhalla_regions])
|> Keyword.put_new(:catalog, [])
|> Keyword.put(:probe, &skip_header_probe/1)

statuses = Map.get(health, :capabilities, %{})

routing = capability("valhalla", Map.get(statuses, "routing", "down"), read_opts)
routing =
"valhalla"
|> capability(Map.get(statuses, "routing", "down"), read_opts)
|> use_declared_regions(valhalla_regions(opts), "VALHALLA_COVERAGE_REGIONS")

%{
capabilities: %{
Expand Down Expand Up @@ -89,6 +92,54 @@ defmodule Atlas.Control.ServiceCoverage do
}
end

defp valhalla_regions(opts) do
opts
|> Keyword.get_lazy(:valhalla_regions, fn -> System.get_env("VALHALLA_COVERAGE_REGIONS") end)
|> normalize_regions()
end

defp normalize_regions(regions) when is_binary(regions) do
regions
|> String.split(",")
|> normalize_regions()
end

defp normalize_regions(regions) when is_list(regions) do
regions
|> Enum.map(&present_string/1)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
end

defp normalize_regions(_regions), do: []

defp use_declared_regions(%{regions: [_ | _]} = capability, _regions, _source),
do: capability

defp use_declared_regions(capability, [], _source), do: capability

defp use_declared_regions(capability, regions, source) do
datasets =
Enum.map(regions, fn region ->
%{
label: region,
kind: "Declared service coverage",
evidence: "Declared by the Atlas operator; not inspected from the remote service",
source: source
}
end)

%{
capability
| coverage_status: "known",
datasets: capability.datasets ++ datasets,
note:
capability.note <>
" Region coverage is declared by the operator via #{source}.",
regions: regions
}
end

defp inspect_service("libpostal", _dir, _catalog, _probe) do
%{
entries: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ defmodule AtlasWeb.Api.V1.CoverageController do
an empty list with `coverage_status=unknown` does not prove that the service
has no coverage. Map matching inherits routing's Valhalla coverage. Transit
timetable coverage is reported separately in `transit_feeds` because it can
differ from the walking-network regions.
differ from the walking-network regions. An external Valhalla whose files
are not mounted into Atlas can declare its regions with
`VALHALLA_COVERAGE_REGIONS`.
""",
responses: %{
200 => response("Capability coverage", "application/json", Schemas.CoverageResponse)
Expand Down
28 changes: 28 additions & 0 deletions app-phoenix/test/atlas/control/service_coverage_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ defmodule Atlas.Control.ServiceCoverageTest do
ServiceCoverage.summary(
data_dir: dir,
catalog: [@berlin],
valhalla_regions: "Germany",
transit_backend: "otp",
health: %{
capabilities: %{
Expand Down Expand Up @@ -202,6 +203,33 @@ defmodule Atlas.Control.ServiceCoverageTest do
]
end

test "uses declared Valhalla coverage when remote dataset provenance is unavailable", %{
dir: dir
} do
result =
ServiceCoverage.summary(
data_dir: dir,
valhalla_regions: " Germany, Europe, Germany, ",
transit_backend: "motis",
health: %{capabilities: %{"routing" => "up"}}
)

assert result.capabilities.routing.regions == ["Germany", "Europe"]
assert result.capabilities.routing.coverage_status == "known"
assert result.capabilities.routing.available
assert result.capabilities.map_matching.regions == ["Germany", "Europe"]
assert result.capabilities.map_matching.inherits == "routing"

assert Enum.map(result.capabilities.routing.datasets, & &1.label) == ["Germany", "Europe"]

assert Enum.all?(
result.capabilities.routing.datasets,
&(&1.source == "VALHALLA_COVERAGE_REGIONS")
)

assert result.capabilities.routing.note =~ "declared by the operator"
end

test "public summary does not launch header probes for missing manifests", %{dir: dir} do
for path <- ~w(valhalla/region.osm.pbf otp/region.osm.pbf osm/current.osm.pbf),
do: put(dir, path, "not a real pbf")
Expand Down
1 change: 1 addition & 0 deletions compose.compare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ services:
PLACEHOLDER_URL: http://placeholder:3000
LIBPOSTAL_URL: http://libpostal:4400
VALHALLA_URL: http://valhalla:8002
VALHALLA_COVERAGE_REGIONS: ${VALHALLA_COVERAGE_REGIONS:-}
OVERPASS_URL: http://overpass:80
OTP_URL: http://otp:8080
TILES_URL: ${TILES_URL:-}
Expand Down
1 change: 1 addition & 0 deletions compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ services:
PLACEHOLDER_URL: http://placeholder:3000
LIBPOSTAL_URL: http://libpostal:4400
VALHALLA_URL: http://valhalla:8002
VALHALLA_COVERAGE_REGIONS: ${VALHALLA_COVERAGE_REGIONS:-}
# Map matching knobs. Declared here so a value set in .env actually
# reaches the app — this service passes an explicit allowlist, not
# env_file, so an undeclared variable is silently dropped.
Expand Down
1 change: 1 addition & 0 deletions compose.dokploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ services:
PHOTON_URL: ${PHOTON_URL:-http://photon:2322}
# Internal names — resolve once you uncomment the services at the bottom.
VALHALLA_URL: http://valhalla:8002
VALHALLA_COVERAGE_REGIONS: ${VALHALLA_COVERAGE_REGIONS:-}
# Map matching knobs. Declared here so a value set in .env actually
# reaches the app — this service passes an explicit allowlist, not
# env_file, so an undeclared variable is silently dropped.
Expand Down
1 change: 1 addition & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ services:
PLACEHOLDER_URL: http://placeholder:3000
LIBPOSTAL_URL: http://libpostal:4400
VALHALLA_URL: http://valhalla:8002
VALHALLA_COVERAGE_REGIONS: ${VALHALLA_COVERAGE_REGIONS:-}
# Map matching knobs. Declared here so a value set in .env actually
# reaches the app — this service passes an explicit allowlist, not
# env_file, so an undeclared variable is silently dropped.
Expand Down
7 changes: 7 additions & 0 deletions test/deployment_config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ test('Valhalla has a safe worker cap and nofile limit by default', () => {
assert.match(compose, /ulimits:\n\s+nofile:\n\s+soft:\s+1048576\n\s+hard:\s+1048576/);
});

test('Valhalla coverage metadata reaches the app in standard and Dokploy Compose', () => {
for (const file of ['compose.yml', 'compose.dokploy.yml']) {
const compose = readFileSync(new URL(`../${file}`, import.meta.url), 'utf8');
assert.match(compose, /VALHALLA_COVERAGE_REGIONS:\s+\$\{VALHALLA_COVERAGE_REGIONS:-\}/);
}
});

test('Overpass diff updates are opt-in by default', () => {
const compose = readFileSync(new URL('../compose.yml', import.meta.url), 'utf8');

Expand Down
Loading