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
26 changes: 26 additions & 0 deletions .docker/entrypoint_pbf-patch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ OUTPUT="$OUTPUT_PATH/$OUTPUT_FILENAME"
BOUNDING_BOX=${BOUNDING_BOX:-9.357298,54.751799,9.506812,54.837072}
BOUNDED_FILENAME=${BOUNDED_FILENAME:-${FILENAME/schleswig-holstein/flensburg}}
SKIP_CONSTRUCTION=${SKIP_CONSTRUCTION:-false}
SKIP_PATHS=${SKIP_PATHS:-true}
POINTS_FILE=${POINTS_FILE:-}

CHANGESETS=()

Expand All @@ -25,6 +27,8 @@ print_config() {
echo "BOUNDING_BOX: $BOUNDING_BOX"
echo "BOUNDED_FILENAME: $BOUNDED_FILENAME"
echo "SKIP_CONSTRUCTION: $SKIP_CONSTRUCTION"
echo "SKIP_PATHS: $SKIP_PATHS"
echo "POINTS_FILE: $POINTS_FILE"
}

setup() {
Expand Down Expand Up @@ -65,6 +69,26 @@ create_construction_changeset() {
CHANGESETS+=("construction.osc")
}

validate_config() {
[[ $SKIP_PATHS == "false" ]] || return 0
# Checked before the download so a missing file fails in a second, not
# after a few hundred megabytes. The working directory is DATA_DIR, so
# POINTS_FILE has to be absolute.
[[ -n $POINTS_FILE ]] || fatal "POINTS_FILE must be set when SKIP_PATHS=false"
[[ -r $POINTS_FILE ]] || fatal "POINTS_FILE is not readable: $POINTS_FILE"
}

create_allowed_paths_changeset() {
if [[ $SKIP_PATHS != "false" ]]; then
echo "Allowed-paths changeset skipped"
return
fi
echo "Creating allowed-paths changeset..."
/pbf-patch allowed-paths --input "$BOUNDED_FILENAME" --output city_access.osc \
|| fatal "failed to create allowed-paths changeset"
CHANGESETS+=("city_access.osc")
}

apply_changesets() {
echo "Applying ${#CHANGESETS[@]} changeset(s)..."
osmium apply-changes --overwrite -o "$OUTPUT" "$FILENAME" "${CHANGESETS[@]}" \
Expand All @@ -79,10 +103,12 @@ main() {
fi

setup
validate_config
download_pbf
extract_bounding_box
empty_changeset "empty.osc"
create_construction_changeset
create_allowed_paths_changeset
apply_changesets

echo "Output file: $OUTPUT"
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ Streamlet is configured entirely via environment variables:
| `STREAMLET_ENGINE_TIMEOUT_MS` | `10000` | Timeout for requests to the routing engine |
| `STREAMLET_SOLVER_TIME_LIMIT_MS` | `2000` | Maximum solver time budget per solve request |

## Tile pipeline

Alongside the service, the workspace ships `pbf-patch`, which patches an OSM
extract before tiles are built (blocked roads, footpaths opened for service
vehicles). Its input is plain GeoJSON, so any workflow can use it. See
[`crates/pbf-patch/README.md`](crates/pbf-patch/README.md).

## Development

The project is a Cargo workspace with two crates: `streamlet-core` (domain
model and solver) and `server` (HTTP API).
The project is a Cargo workspace with three crates: `streamlet-core` (domain
model and solver), `server` (HTTP API) and `pbf-patch` (tile-pipeline CLI).

```sh
# Build and test the whole workspace
Expand Down
4 changes: 2 additions & 2 deletions crates/pbf-patch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "Generates OSC changesets and patched OSM PBF files for the tile pipeline"
readme = "../../README.md"
readme = "README.md"
publish.workspace = true

[lib]
Expand All @@ -20,7 +20,7 @@ path = "src/main.rs"

[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
clap = { version = "4", features = ["derive", "env"] }
osmpbf = "0.3.8"
reqwest = { version = "0.13.4", default-features = false, features = ["blocking", "rustls"] }
rstar = "0.13.0"
Expand Down
88 changes: 88 additions & 0 deletions crates/pbf-patch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# pbf-patch

Generates OSC changesets for an OSM PBF extract and applies them, so a routing
engine can be built from map data that reflects local rules. Two changesets
exist today, both consumer-agnostic in their input.

## `construction`

Closes roads that the Flensburg TBZ Verkehrsticker reports as blocked
(`access=no`), and drops `oneway` on directly connected ways so detours stay
possible.

```sh
pbf-patch construction --input flensburg.osm.pbf --output construction.osc
```

## `allowed-paths`

Opens footpaths for motor vehicles around points that no car-accessible road
reaches. Use it when a vehicle has to get to a place that is only served by a
path, for example a tree, a bin or a hydrant.

```sh
pbf-patch allowed-paths \
--input flensburg.osm.pbf \
--output city_access.osc \
--points targets.geojson

# or from a pipeline
your-export-tool | pbf-patch allowed-paths --input flensburg.osm.pbf --output city_access.osc --points -
```

Input is a GeoJSON FeatureCollection of Point features. `properties.id` is
optional and used for logging only; a third coordinate is ignored.

```json
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "id": 42 },
"geometry": { "type": "Point", "coordinates": [9.4705, 54.81005] }
}
]
}
```

| Option | Env | Default | Description |
| --- | --- | --- | --- |
| `--points` | `POINTS_FILE` | required | GeoJSON point file, `-` for stdin |
| `--road-max-distance` | `ROAD_MAX_DISTANCE` | `10` | A target with a car-accessible way within this many metres is considered reachable |
| `--footpath-search-radius` | `FOOTPATH_SEARCH_RADIUS` | `20` | Footpaths within this radius of an unreachable target are opened |

A target counts as unreachable only if no car-accessible way lies within
`--road-max-distance`. Its nearby footpaths then get `motor_vehicle=yes`, plus
`maxspeed=10` and `maxweight=9` where the way does not already carry those keys
— an existing limit is kept rather than raised. Sidewalks, steps,
`access=private`, `access=no`, `motor_vehicle=no` and `vehicle=no` are never
opened; the last one because mappers use it for paths that are physically
impassable for a vehicle.

Targets that end up with neither a road nor a footpath in range are reported
with their ids, so a point source can be checked against the map data.

The command performs no network access: whoever knows where the targets are
exports them, and the patcher stays independent of that system.

## Pipeline image

`.docker/entrypoint_pbf-patch.sh` downloads a PBF, cuts a bounding box, builds
the enabled changesets and applies them with `osmium apply-changes`.

| Variable | Default | Description |
| --- | --- | --- |
| `PBF_URL` | Geofabrik Schleswig-Holstein | Source extract |
| `BOUNDING_BOX` | Flensburg | `minlon,minlat,maxlon,maxlat` for `osmium extract` |
| `BOUNDED_FILENAME` | derived from `PBF_URL` | Name of the cut extract; must differ from the downloaded file |
| `DATA_DIR` | `.` | Working directory inside the container, mount a volume here |
| `OUTPUT_PATH` | `./output` | Where the patched PBF is written |
| `OUTPUT_FILENAME` | derived from `PBF_URL` | Name of the patched PBF |
| `SKIP_CONSTRUCTION` | `false` | Set to anything else to skip the construction changeset |
| `SKIP_PATHS` | `true` | Set to `false` to build the allowed-paths changeset |
| `POINTS_FILE` | unset | Path to the GeoJSON point file, required when `SKIP_PATHS=false` |

The two radii are not declared by the entrypoint; `ROAD_MAX_DISTANCE` and
`FOOTPATH_SEARCH_RADIUS` reach the subcommand straight from the container
environment, so their defaults live in one place only.
Loading
Loading