Make public transport routes discoverable with shareable search
Add one-click examples and route search by number or metadata, with transport type, location and map-area filters. Preserve search filters and bounds in shareable URLs and restore searches on reload and browser navigation. Validate with 91 passing tests, including desktop and mobile browser coverage, and strict mypy checks. Closes #2
This commit is contained in:
parent
29afb4f115
commit
a79b0a7098
7 changed files with 919 additions and 12 deletions
40
web/app.py
40
web/app.py
|
|
@ -7,26 +7,24 @@ from flask.typing import ResponseReturnValue
|
|||
|
||||
from osm_geojson.pt.core import (
|
||||
OsmError,
|
||||
PUBLIC_TRANSPORT_ROUTE_TYPES,
|
||||
Bounds,
|
||||
build_route_coords,
|
||||
fetch_relation_full,
|
||||
fetch_relation_name,
|
||||
fetch_route_master_routes,
|
||||
fetch_sibling_routes,
|
||||
find_locations,
|
||||
make_geojson,
|
||||
nearest_coord_index,
|
||||
node_name,
|
||||
parse_elements,
|
||||
search_routes,
|
||||
)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = "dev"
|
||||
|
||||
PUBLIC_TRANSPORT_ROUTE_TYPES = {
|
||||
"bus", "trolleybus", "tram", "subway", "train",
|
||||
"light_rail", "monorail", "ferry", "funicular",
|
||||
}
|
||||
|
||||
|
||||
def parse_relation_id(text: str) -> int | None:
|
||||
"""Extract a relation ID from a bare integer, partial path, or full OSM URL."""
|
||||
text = text.strip()
|
||||
|
|
@ -144,6 +142,36 @@ def api_route(relation_id: int) -> ResponseReturnValue:
|
|||
})
|
||||
|
||||
|
||||
@app.get("/api/search")
|
||||
def api_search() -> ResponseReturnValue:
|
||||
"""Find public transport routes by name, terminus or route number."""
|
||||
try:
|
||||
bounds: Bounds | None = None
|
||||
keys = ("south", "west", "north", "east")
|
||||
if any(key in request.args for key in keys):
|
||||
try:
|
||||
bounds = (
|
||||
float(request.args["south"]), float(request.args["west"]),
|
||||
float(request.args["north"]), float(request.args["east"]),
|
||||
)
|
||||
except (KeyError, ValueError) as exc:
|
||||
raise OsmError("All four search area coordinates are required and must be numbers.", 400) from exc
|
||||
return jsonify(search_routes(request.args.get("q", ""), request.args.get("mode", ""), bounds))
|
||||
except OsmError as exc:
|
||||
code = "invalid_query" if exc.status_code == 400 else "osm_error"
|
||||
return jsonify({"error": code, "message": str(exc)}), exc.status_code
|
||||
|
||||
|
||||
@app.get("/api/locations")
|
||||
def api_locations() -> ResponseReturnValue:
|
||||
"""Find candidate locations and their search bounds for a place name."""
|
||||
try:
|
||||
return jsonify({"locations": find_locations(request.args.get("q", ""))})
|
||||
except OsmError as exc:
|
||||
code = "invalid_query" if exc.status_code == 400 else "osm_error"
|
||||
return jsonify({"error": code, "message": str(exc)}), exc.status_code
|
||||
|
||||
|
||||
@app.route("/api/segment/<int:relation_id>")
|
||||
def api_segment(relation_id: int) -> ResponseReturnValue:
|
||||
"""Return GeoJSON for the segment between two named stops."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue