Skip to content
This repository was archived by the owner on Jan 8, 2026. It is now read-only.

Commit 182060c

Browse files
committed
Add live query runner script and docs
1 parent f9b1600 commit 182060c

3 files changed

Lines changed: 246 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ uv run ty check
2525
uv run pytest tests/ --import-mode importlib
2626
```
2727

28+
For running live query examples against the public API, see `docs/live-queries.md`.
29+
2830
## Usage
2931

3032
### `API()` constructor

‎docs/live-queries.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Live Query Runner
2+
3+
This script is a lightweight trust tool for running known Overpass QL examples against the live API.
4+
5+
## Usage
6+
7+
List examples:
8+
9+
```bash
10+
python tools/live_queries.py --list
11+
```
12+
13+
Run by index or key:
14+
15+
```bash
16+
python tools/live_queries.py --run 1
17+
python tools/live_queries.py --run cafes_sf_bbox
18+
```
19+
20+
Custom query:
21+
22+
```bash
23+
python tools/live_queries.py --query 'node["amenity"="cafe"](37.77,-122.45,37.79,-122.43)' --responseformat geojson
24+
```
25+
26+
Notes:
27+
- Uses the public Overpass API by default.
28+
- `--model` enables Pydantic models.
29+
- `--save` writes raw output to disk.

‎tools/live_queries.py‎

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import json
5+
import sys
6+
from dataclasses import dataclass
7+
from typing import Optional
8+
9+
import overpass
10+
11+
12+
@dataclass(frozen=True)
13+
class ExampleQuery:
14+
key: str
15+
title: str
16+
query: str
17+
responseformat: str = "geojson"
18+
verbosity: str = "body"
19+
build: bool = True
20+
21+
22+
EXAMPLES: list[ExampleQuery] = [
23+
ExampleQuery(
24+
key="cafes_sf_bbox",
25+
title="Cafes in a small SF bbox",
26+
query='node["amenity"="cafe"](37.770,-122.450,37.790,-122.430)',
27+
),
28+
ExampleQuery(
29+
key="admin_boundaries_uluru",
30+
title="Administrative boundaries (Uluru bbox)",
31+
query='rel["boundary"="administrative"](-25.38653,130.99883,-25.31478,131.08938)',
32+
),
33+
ExampleQuery(
34+
key="route_bus_sf",
35+
title="Bus route relations in SF bbox (with recursion)",
36+
query=(
37+
'relation["route"="bus"](37.770,-122.520,37.805,-122.380);'
38+
"(._;>;);"
39+
),
40+
verbosity="body geom",
41+
),
42+
ExampleQuery(
43+
key="is_in_paris_admin2",
44+
title="Find admin areas containing a point (Paris, admin_level=2)",
45+
query=(
46+
"is_in(48.856089,2.29789);"
47+
'area._[admin_level="2"];'
48+
),
49+
),
50+
ExampleQuery(
51+
key="bus_stops_bonn",
52+
title="Bus stops in Bonn (area by name)",
53+
query=(
54+
'area[name="Bonn"];'
55+
"node(area)[highway=bus_stop];"
56+
),
57+
),
58+
ExampleQuery(
59+
key="multipolygon_relations",
60+
title="Multipolygon relations in a small bbox",
61+
query='rel[type=multipolygon](37.770,-122.450,37.790,-122.430)',
62+
verbosity="body geom",
63+
),
64+
]
65+
66+
67+
def list_examples() -> None:
68+
for idx, ex in enumerate(EXAMPLES, start=1):
69+
print(f"{idx:2d}. {ex.title} ({ex.key})")
70+
71+
72+
def run_query(
73+
query: str,
74+
*,
75+
responseformat: str,
76+
verbosity: str,
77+
build: bool,
78+
model: bool,
79+
endpoint: Optional[str],
80+
timeout: Optional[float],
81+
debug: bool,
82+
) -> object:
83+
api = overpass.API(endpoint=endpoint, timeout=timeout, debug=debug) if endpoint else overpass.API(timeout=timeout, debug=debug)
84+
return api.get(
85+
query,
86+
responseformat=responseformat,
87+
verbosity=verbosity,
88+
build=build,
89+
model=model,
90+
)
91+
92+
93+
def summarize(result: object, responseformat: str, model: bool) -> str:
94+
if model:
95+
if hasattr(result, "features"):
96+
return f"GeoJSON FeatureCollection: {len(result.features)} features"
97+
if hasattr(result, "elements"):
98+
return f"Overpass JSON: {len(result.elements)} elements"
99+
if hasattr(result, "rows"):
100+
return f"CSV: {len(result.rows)} rows"
101+
if hasattr(result, "text"):
102+
return f"XML: {len(result.text)} chars"
103+
if responseformat == "geojson" and isinstance(result, dict):
104+
return f"GeoJSON FeatureCollection: {len(result.get('features', []))} features"
105+
if responseformat == "json" and isinstance(result, dict):
106+
return f"Overpass JSON: {len(result.get('elements', []))} elements"
107+
if responseformat.startswith("csv") and isinstance(result, list):
108+
return f"CSV: {max(len(result) - 1, 0)} rows"
109+
if responseformat == "xml" and isinstance(result, str):
110+
return f"XML: {len(result)} chars"
111+
return f"Result type: {type(result).__name__}"
112+
113+
114+
def main() -> int:
115+
parser = argparse.ArgumentParser(description="Run live Overpass QL queries.")
116+
parser.add_argument("--list", action="store_true", help="List built-in example queries")
117+
parser.add_argument("--run", type=str, help="Run by example key or numeric index")
118+
parser.add_argument("--query", type=str, help="Run a custom query string")
119+
parser.add_argument("--responseformat", type=str, default="geojson")
120+
parser.add_argument("--verbosity", type=str, default="body")
121+
parser.add_argument("--build", action="store_true", help="Build query wrapper (default)")
122+
parser.add_argument("--no-build", action="store_true", help="Use raw query without wrapper")
123+
parser.add_argument("--model", action="store_true", help="Return Pydantic models")
124+
parser.add_argument("--endpoint", type=str, default=None)
125+
parser.add_argument("--timeout", type=float, default=25.0)
126+
parser.add_argument("--debug", action="store_true")
127+
parser.add_argument("--pretty", action="store_true", help="Pretty-print JSON output")
128+
parser.add_argument("--save", type=str, help="Write raw output to a file")
129+
130+
args = parser.parse_args()
131+
132+
if args.list:
133+
list_examples()
134+
return 0
135+
136+
if args.query:
137+
query = args.query
138+
responseformat = args.responseformat
139+
verbosity = args.verbosity
140+
build = not args.no_build
141+
elif args.run:
142+
example: Optional[ExampleQuery] = None
143+
if args.run.isdigit():
144+
idx = int(args.run) - 1
145+
if 0 <= idx < len(EXAMPLES):
146+
example = EXAMPLES[idx]
147+
else:
148+
for ex in EXAMPLES:
149+
if ex.key == args.run:
150+
example = ex
151+
break
152+
if example is None:
153+
print("Unknown example. Use --list to see options.", file=sys.stderr)
154+
return 2
155+
query = example.query
156+
responseformat = example.responseformat
157+
verbosity = example.verbosity
158+
build = example.build
159+
else:
160+
list_examples()
161+
choice = input("Choose an example number (or 'c' for custom): ").strip()
162+
if choice.lower() == "c":
163+
query = input("Enter Overpass QL: ").strip()
164+
responseformat = input("responseformat [geojson|json|xml|csv(...)]: ").strip() or "geojson"
165+
verbosity = input("verbosity [ids|skel|body|tags|meta ...]: ").strip() or "body"
166+
build = input("build wrapper? [Y/n]: ").strip().lower() != "n"
167+
else:
168+
if not choice.isdigit():
169+
print("Invalid selection.", file=sys.stderr)
170+
return 2
171+
idx = int(choice) - 1
172+
if idx < 0 or idx >= len(EXAMPLES):
173+
print("Invalid selection.", file=sys.stderr)
174+
return 2
175+
example = EXAMPLES[idx]
176+
query = example.query
177+
responseformat = example.responseformat
178+
verbosity = example.verbosity
179+
build = example.build
180+
181+
result = run_query(
182+
query,
183+
responseformat=responseformat,
184+
verbosity=verbosity,
185+
build=build,
186+
model=args.model,
187+
endpoint=args.endpoint,
188+
timeout=args.timeout,
189+
debug=args.debug,
190+
)
191+
192+
print(f"Query: {query}")
193+
print(summarize(result, responseformat, args.model))
194+
195+
if args.save:
196+
if hasattr(result, "to_geojson"):
197+
raw = result.to_geojson()
198+
elif hasattr(result, "text"):
199+
raw = result.text
200+
else:
201+
raw = json.dumps(result, indent=2 if args.pretty else None)
202+
with open(args.save, "w", encoding="utf-8") as fp:
203+
fp.write(raw)
204+
print(f"Wrote {args.save}")
205+
return 0
206+
207+
if args.pretty and isinstance(result, dict):
208+
print(json.dumps(result, indent=2))
209+
elif isinstance(result, str):
210+
print(result)
211+
return 0
212+
213+
214+
if __name__ == "__main__":
215+
raise SystemExit(main())

0 commit comments

Comments
 (0)