get address efficiency by getting only centroids
This commit is contained in:
+57
-32
@@ -7,8 +7,6 @@ Usage:
|
|||||||
python download-overpass.py --type addresses "Lake County" "Florida" output/addresses.geojson
|
python download-overpass.py --type addresses "Lake County" "Florida" output/addresses.geojson
|
||||||
python download-overpass.py --type multimodal "Sumter County" "Florida" output/paths.geojson
|
python download-overpass.py --type multimodal "Sumter County" "Florida" output/paths.geojson
|
||||||
|
|
||||||
TODO:
|
|
||||||
- Don't just download roads. Probably ignore relations also.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -49,7 +47,17 @@ def build_overpass_query(county_name, state_name, data_type="highways"):
|
|||||||
"""Build Overpass API query for specified data type in a county."""
|
"""Build Overpass API query for specified data type in a county."""
|
||||||
area_id = get_county_area_id(county_name, state_name)
|
area_id = get_county_area_id(county_name, state_name)
|
||||||
|
|
||||||
base_query = f"""[out:json][timeout:60];
|
# Addresses: include nwr so building ways and relations are captured too,
|
||||||
|
# but use "out center;" instead of "out geom;" so ways/relations return a
|
||||||
|
# single centroid rather than their full coordinate list — much smaller payload.
|
||||||
|
if data_type == "addresses":
|
||||||
|
query = f"""[out:json][timeout:180];
|
||||||
|
area(id:{area_id})->.searchArea;
|
||||||
|
nwr["addr:housenumber"](area.searchArea);
|
||||||
|
out center;"""
|
||||||
|
return query
|
||||||
|
|
||||||
|
base_query = f"""[out:json][timeout:120];
|
||||||
area(id:{area_id})->.searchArea;"""
|
area(id:{area_id})->.searchArea;"""
|
||||||
|
|
||||||
if data_type == "highways":
|
if data_type == "highways":
|
||||||
@@ -65,8 +73,6 @@ area(id:{area_id})->.searchArea;"""
|
|||||||
selector += 'way["highway"="service"](area.searchArea);'
|
selector += 'way["highway"="service"](area.searchArea);'
|
||||||
selector += 'way["highway"="track"](area.searchArea);'
|
selector += 'way["highway"="track"](area.searchArea);'
|
||||||
selector += ');'
|
selector += ');'
|
||||||
elif data_type == "addresses":
|
|
||||||
selector = 'nwr["addr:housenumber"](area.searchArea);'
|
|
||||||
elif data_type == "multimodal":
|
elif data_type == "multimodal":
|
||||||
selector = '(way["highway"="path"](area.searchArea);way["highway"="cycleway"](area.searchArea););'
|
selector = '(way["highway"="path"](area.searchArea);way["highway"="cycleway"](area.searchArea););'
|
||||||
else:
|
else:
|
||||||
@@ -77,24 +83,30 @@ area(id:{area_id})->.searchArea;"""
|
|||||||
|
|
||||||
|
|
||||||
def query_overpass(query):
|
def query_overpass(query):
|
||||||
"""Send query to Overpass API and return JSON response."""
|
"""Send query to Overpass API and return JSON response, with retries."""
|
||||||
url = "https://overpass-api.de/api/interpreter"
|
url = "https://overpass-api.de/api/interpreter"
|
||||||
data = urllib.parse.urlencode({"data": query}).encode("utf-8")
|
data = urllib.parse.urlencode({"data": query}).encode("utf-8")
|
||||||
|
|
||||||
|
max_attempts = 3
|
||||||
try:
|
for attempt in range(1, max_attempts + 1):
|
||||||
with urllib.request.urlopen(url, data=data) as response:
|
|
||||||
return json.loads(response.read().decode("utf-8"))
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
print(f"HTTP Error {e.code}: {e.reason}", file=sys.stderr)
|
|
||||||
try:
|
try:
|
||||||
error_body = e.read().decode("utf-8")
|
with urllib.request.urlopen(url, data=data, timeout=300) as response:
|
||||||
|
return json.loads(response.read().decode("utf-8"))
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
try:
|
||||||
|
error_body = e.read().decode("utf-8")
|
||||||
|
except Exception:
|
||||||
|
error_body = "(unreadable)"
|
||||||
|
print(f"HTTP Error {e.code} on attempt {attempt}: {e.reason}", file=sys.stderr)
|
||||||
print(f"Error response body: {error_body}", file=sys.stderr)
|
print(f"Error response body: {error_body}", file=sys.stderr)
|
||||||
except:
|
if attempt < max_attempts and e.code in (429, 504):
|
||||||
print("Could not read error response body", file=sys.stderr)
|
wait = 30 * attempt
|
||||||
sys.exit(1)
|
print(f"Retrying in {wait}s...", file=sys.stderr)
|
||||||
except Exception as e:
|
time.sleep(wait)
|
||||||
print(f"Error querying Overpass API: {e}", file=sys.stderr)
|
else:
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error querying Overpass API: {e}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -103,20 +115,7 @@ def convert_to_geojson(overpass_data):
|
|||||||
features = []
|
features = []
|
||||||
|
|
||||||
for element in overpass_data.get("elements", []):
|
for element in overpass_data.get("elements", []):
|
||||||
if element["type"] == "way" and "geometry" in element:
|
if element["type"] == "node":
|
||||||
coordinates = [[coord["lon"], coord["lat"]] for coord in element["geometry"]]
|
|
||||||
|
|
||||||
feature = {
|
|
||||||
"type": "Feature",
|
|
||||||
"properties": element.get("tags", {}),
|
|
||||||
"geometry": {
|
|
||||||
"type": "LineString",
|
|
||||||
"coordinates": coordinates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
features.append(feature)
|
|
||||||
|
|
||||||
elif element["type"] == "node":
|
|
||||||
feature = {
|
feature = {
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
"properties": element.get("tags", {}),
|
"properties": element.get("tags", {}),
|
||||||
@@ -127,6 +126,32 @@ def convert_to_geojson(overpass_data):
|
|||||||
}
|
}
|
||||||
features.append(feature)
|
features.append(feature)
|
||||||
|
|
||||||
|
elif element["type"] in ("way", "relation"):
|
||||||
|
if "geometry" in element:
|
||||||
|
# out geom; — full coordinate list (used for highways/paths)
|
||||||
|
coordinates = [[coord["lon"], coord["lat"]] for coord in element["geometry"]]
|
||||||
|
feature = {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": element.get("tags", {}),
|
||||||
|
"geometry": {
|
||||||
|
"type": "LineString",
|
||||||
|
"coordinates": coordinates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
features.append(feature)
|
||||||
|
elif "center" in element:
|
||||||
|
# out center; — single centroid point (used for addresses on buildings)
|
||||||
|
c = element["center"]
|
||||||
|
feature = {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": element.get("tags", {}),
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [c["lon"], c["lat"]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
features.append(feature)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"type": "FeatureCollection",
|
"type": "FeatureCollection",
|
||||||
"features": features
|
"features": features
|
||||||
|
|||||||
Reference in New Issue
Block a user