Metadata-Version: 2.4
Name: roubikon
Version: 1.1.0
Summary: Flask web app for OSM road-network shortest-path navigation and street-view visualization
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: flask>=3.0
Requires-Dist: flask-cors>=4.0
Requires-Dist: pillow>=10.0
Requires-Dist: pyppeteer>=2.0
Requires-Dist: opencv-python>=4.8
Requires-Dist: numpy>=1.24
Requires-Dist: python-dotenv>=1.0
Requires-Dist: requests>=2.31
Requires-Dist: osmnx>=2.1
Requires-Dist: networkx>=3.6
Requires-Dist: matplotlib>=3.7
Requires-Dist: rasterio>=1.3
Requires-Dist: shapely>=2.0
Dynamic: license-file

# roubikon

A Flask-based package for shortest-path route visualization on road networks with street-level imagery.

## Features

- Serves a Flask web app with an interactive map interface
- Generates a side-by-side route video combining a 2D map view and street-level photos
- Supports pre-processed street view images or live Google Street View API fetching
- Barrier-free routing with configurable surface and elevation penalties

## Installation

```bash
pip install roubikon
```

## Usage

### 1. Extract a road network graph

`extract_graph(place_name, output_file_path=None, output_dir=None, generate_plots=True)` downloads the pedestrian road network for a place from OpenStreetMap and writes it to a graph file (default `graph_export.txt`).

`print_graph_stats(graph_file_path)` prints summary statistics for a graph file — node/edge counts, surface-type breakdown, and the wheelchair-accessible edge ratio.

```python
from roubikon import extract_graph, print_graph_stats

extract_graph("Konstanz, Germany")
print_graph_stats("graph_export.txt")
```

### 2. Start the web app

`create_app(data_file, preprocessed_images_folder, port, ...)` loads the graph and configures the Flask `app` — call `.run()` on the returned object to start serving.

```python
from roubikon import create_app

app = create_app(
    data_file="graph_export.txt",
    preprocessed_images_folder="augmented_konstanz_rn_small_preprocessed_rough",
    port=5000,
)
app.run(debug=True, port=5000)
```

Then open [http://localhost:5000](http://localhost:5000) in your browser.

### 3. Use routing standalone (without the Flask app)

The graph-loading and routing functions used internally by the app are also available directly, so you can compute paths without starting the Flask server.

`build_adjacency_list(graph_file_path)` parses a graph file into an adjacency list: `{node_id: [(neighbor_id, distance_m), ...]}`.

`build_edge_properties_map(graph_file_path)` parses a graph file into per-edge metadata (both directions): `{(a, b): {"length", "surface_type", "wheelchair", "elevation_change"}}`.

`build_vertices(graph_file_path)` parses a graph file into a vertex list: `[{"id", "lat", "lon"}, ...]`.

```python
from roubikon import (
    build_adjacency_list, build_edge_properties_map, build_vertices,
    find_min_and_max_values, make_grid_coordinate_nodes_map, find_nearest_node,
    shortest_path_dijkstra, shortest_path_barrier_free,
)

GRAPH_FILE = "graph_export.txt"
GRID_SIZE = 100  # rows/cols of the lookup grid used by find_nearest_node

adj_list = build_adjacency_list(GRAPH_FILE)                   # {node_id: [(neighbor_id, distance_m), ...]}
edge_properties_map = build_edge_properties_map(GRAPH_FILE)   # {(a, b): {"length", "surface_type", "wheelchair", "elevation_change"}}
vertices = build_vertices(GRAPH_FILE)                         # [{"id", "lat", "lon"}, ...]
```

#### Resolve coordinates to the nearest graph node

`find_min_and_max_values(vertices)` returns the bounding box of a vertex list: `{"min_lat", "max_lat", "min_lon", "max_lon"}`.

`make_grid_coordinate_nodes_map(vertices, grid_size)` buckets vertices into a `grid_size` x `grid_size` spatial grid for fast nearest-node lookup.

`find_nearest_node(target_lat, target_lon, min_and_max_data, vertex_lat_long_map, grid_coordinate_nodes_map, grid_size)` returns the graph node nearest to a given lat/lon: `{"nearest_id", "nearest_lat", "nearest_lon", "nearest_distance"}`.

```python
min_and_max_data = find_min_and_max_values(vertices)
grid_coordinate_nodes_map = make_grid_coordinate_nodes_map(vertices, grid_size=GRID_SIZE)
vertex_lat_long_map = {v["id"]: (v["lat"], v["lon"]) for v in vertices}

nearest = find_nearest_node(
    target_lat=47.6779, target_lon=9.1732,
    min_and_max_data=min_and_max_data,
    vertex_lat_long_map=vertex_lat_long_map,
    grid_coordinate_nodes_map=grid_coordinate_nodes_map,
    grid_size=GRID_SIZE,
)
src = nearest["nearest_id"]
```

`grid_size` must be the same value passed to `make_grid_coordinate_nodes_map` and `find_nearest_node`.

#### Shortest path

`shortest_path_dijkstra(graph, src, dest)` returns the shortest path between two node IDs as a list of node IDs, using plain distance-weighted Dijkstra.

```python
path = shortest_path_dijkstra(adj_list, src, dest)
```

#### Barrier-free shortest path

`shortest_path_barrier_free(graph, src, dest, params, dist_low, dist_high, elev_low, elev_high, edge_properties_map)` returns a shortest path that also weighs surface type, wheelchair accessibility, and elevation change according to `params`.

```python
# distance + elevation + smoothness should sum to about 1
params = {
    "mobility_mode": "wheelchair",   # "wheelchair" | "crutches" | "walker" | None
    "distance":      0.33,           # weight: prefer shorter routes
    "elevation":     0.33,           # weight: prefer flatter routes
    "smoothness":    0.33,           # weight: prefer better surfaces / avoid steps
}

# Normalization bounds — the running app computes these once from percentiles
# over the whole graph; standalone, min/max over the loaded graph works fine.
distances = [d for edges in adj_list.values() for _, d in edges]
elevations = [abs(p["elevation_change"]) for p in edge_properties_map.values()]

barrier_free_path = shortest_path_barrier_free(
    adj_list, src, dest,
    params=params,
    dist_low=min(distances), dist_high=max(distances),
    elev_low=min(elevations), elev_high=max(elevations),
    edge_properties_map=edge_properties_map,
)
```

If any one of `distance`, `elevation`, `smoothness` is ≥ 0.9, that criterion is used exclusively (weight 1.0) and the other two are ignored.

## Road network file format

The `graph_export.txt` file follows this structure:

```
<number of vertices>
<number of edges>
<id> <lat> <lon> <elevation>
...
<node_a> <node_b> <length> <surface_type> <wheelchair>
...
```
