Initial commit
This commit is contained in:
commit
4366a53d23
4 changed files with 101811 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export*.json
|
101582
results_2023-04-26_2200Z.json
Normal file
101582
results_2023-04-26_2200Z.json
Normal file
File diff suppressed because it is too large
Load diff
31
results_2023-04-26_2200Z.json.txt
Normal file
31
results_2023-04-26_2200Z.json.txt
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"changes": {
|
||||
"removed oneway:bicycle=yes": {
|
||||
"added cycleway:both=yes": [0 elements],
|
||||
"added cycleway:both=no": [12 elements],
|
||||
"added cycleway:both=other": [0 elements],
|
||||
"didn't modify cycleway:both": [4 elements]
|
||||
},
|
||||
"removed oneway:bicycle=no": {
|
||||
"added cycleway:both=yes": [0 elements],
|
||||
"added cycleway:both=no": [875 elements],
|
||||
"added cycleway:both=other": [0 elements],
|
||||
"didn't modify cycleway:both": [71 elements]
|
||||
},
|
||||
"removed oneway:bicycle=-1": {
|
||||
"added cycleway:both=yes": [0 elements],
|
||||
"added cycleway:both=no": [0 elements],
|
||||
"added cycleway:both=other": [0 elements],
|
||||
"didn't modify cycleway:both": [0 elements]
|
||||
},
|
||||
"removed oneway:bicycle=other": {
|
||||
"added cycleway:both=yes": [0 elements],
|
||||
"added cycleway:both=no": [0 elements],
|
||||
"added cycleway:both=other": [0 elements],
|
||||
"didn't modify cycleway:both": [0 elements]
|
||||
}
|
||||
},
|
||||
"oneway_added_after_cycleway_both": [4323 elements],
|
||||
"nothing_to_report": [96253 elements],
|
||||
"preconditions_not_met": [6 elements]
|
||||
}
|
197
script.py
Executable file
197
script.py
Executable file
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import concurrent.futures
|
||||
import requests
|
||||
from collections import defaultdict
|
||||
from ipo import ipo, opi, p
|
||||
|
||||
|
||||
OSM_USERNAME = ""
|
||||
|
||||
if not OSM_USERNAME:
|
||||
print("Fill in your OSM username in the script first.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
|
||||
changes = defaultdict(dict)
|
||||
for oneway_bicycle in ("yes", "no", "-1", "other"):
|
||||
for cycleway_both in ("yes", "no", "other"):
|
||||
changes \
|
||||
[f"removed oneway:bicycle={oneway_bicycle}"] \
|
||||
[f"added cycleway:both={cycleway_both}"] \
|
||||
= []
|
||||
changes \
|
||||
[f"removed oneway:bicycle={oneway_bicycle}"] \
|
||||
["didn't modify cycleway:both"] \
|
||||
= []
|
||||
|
||||
oneway_added_after_cycleway_both = []
|
||||
nothing_to_report = []
|
||||
preconditions_not_met = []
|
||||
|
||||
|
||||
def normalized_oneway(value: str) -> str:
|
||||
if value in ("yes", "1", "true"):
|
||||
return "yes"
|
||||
elif value in ("no", "0", "false"):
|
||||
return "no"
|
||||
elif value in ("-1", "reverse"):
|
||||
return "-1"
|
||||
else:
|
||||
return "other"
|
||||
|
||||
def normalized_cycleway(value: str) -> str:
|
||||
if value in ("yes", "no"):
|
||||
return value
|
||||
else:
|
||||
return "other"
|
||||
|
||||
|
||||
def get(url):
|
||||
r = session.get(
|
||||
url, timeout=30,
|
||||
headers={
|
||||
"User-Agent": "M!dgard's script to detect data corruption by a Vespucci bug, run by "
|
||||
"`{OSM_USERNAME}`",
|
||||
"X-Note": "See https://community.openstreetmap.org/t/98353. This script is used on "
|
||||
"output of Overpass to check the tag history to determine if a road was corrupted "
|
||||
f"by a Vespucci bug or UX issue. Run by OSM user `{OSM_USERNAME}`, contact them in "
|
||||
"case of problems."
|
||||
}
|
||||
)
|
||||
if r.status_code == 503:
|
||||
time.sleep(60)
|
||||
return get(url)
|
||||
r.raise_for_status()
|
||||
return r
|
||||
|
||||
|
||||
def handle_way(i, total, way_id):
|
||||
print(f"\r{i}/{len(ways)}", flush=True, end="", file=sys.stderr)
|
||||
|
||||
history_url = f"https://api.openstreetmap.org/api/0.6/way/{way_id}/history.json"
|
||||
r = get(history_url)
|
||||
|
||||
history = (ipo(r.json()["elements"]) | reversed | list | opi)
|
||||
# history = [{"version": 6, …}, {"version": 5, …}, …]
|
||||
|
||||
sort_in = None
|
||||
|
||||
if (
|
||||
history[0].get("tags", {}).get("cycleway:both") != "no" or
|
||||
history[0].get("tags", {}).get("oneway") not in ("yes", "-1") or
|
||||
"oneway:bicycle" in history[0].get("tags", {})
|
||||
):
|
||||
print(
|
||||
f"w{way_id}: preconditions for tags not met: wrong query used, "
|
||||
"or OSM element modified after executing query",
|
||||
file=sys.stderr, flush=True
|
||||
)
|
||||
sort_in = preconditions_not_met
|
||||
sort_in.append(way_id)
|
||||
return
|
||||
|
||||
for previous_version, version in zip(history[1:], history):
|
||||
assert sort_in is None
|
||||
|
||||
cs = version["changeset"]
|
||||
v = version["version"]
|
||||
version_prefix = f"w{way_id}: v{v} (#{cs})"
|
||||
|
||||
|
||||
if not previous_version.get("tags", []):
|
||||
sort_in = nothing_to_report
|
||||
break
|
||||
|
||||
|
||||
assert "oneway:bicycle" not in version["tags"]
|
||||
oneway_bicycle_removed = "oneway:bicycle" in previous_version["tags"]
|
||||
cycleway_both_modified = (
|
||||
"cycleway:both" in version["tags"] and
|
||||
previous_version["tags"].get("cycleway:both") != version["tags"]["cycleway:both"]
|
||||
)
|
||||
|
||||
|
||||
# oneway changed somewhere after the purported StreetComplete change
|
||||
if previous_version["tags"].get("oneway") != version["tags"].get("oneway"):
|
||||
print(
|
||||
f"\n{version_prefix} +oneway={version['tags']['oneway']}",
|
||||
flush=True, file=sys.stderr
|
||||
)
|
||||
sort_in = oneway_added_after_cycleway_both
|
||||
break
|
||||
|
||||
|
||||
oneway_bicycle = previous_version["tags"].get("oneway:bicycle")
|
||||
cycleway_both = version["tags"].get("cycleway:both")
|
||||
|
||||
if cycleway_both_modified:
|
||||
|
||||
if oneway_bicycle_removed:
|
||||
|
||||
print(
|
||||
f"\n{version_prefix} "
|
||||
f"+cycleway:both={cycleway_both} -oneway:bicycle={oneway_bicycle}",
|
||||
flush=True, file=sys.stderr
|
||||
)
|
||||
sort_in = changes \
|
||||
[f"removed oneway:bicycle={normalized_oneway(oneway_bicycle)}"] \
|
||||
[f"added cycleway:both={normalized_cycleway(cycleway_both)}"]
|
||||
break
|
||||
|
||||
else:
|
||||
sort_in = nothing_to_report
|
||||
break
|
||||
|
||||
if oneway_bicycle_removed:
|
||||
# oneway:bicycle=* was removed after cycleway:both was added
|
||||
print(
|
||||
f"\n{version_prefix} "
|
||||
f"-oneway:bicycle={oneway_bicycle}",
|
||||
flush=True, file=sys.stderr
|
||||
)
|
||||
sort_in = changes \
|
||||
[f"removed oneway:bicycle={normalized_oneway(oneway_bicycle)}"] \
|
||||
["didn't modify cycleway:both"]
|
||||
break
|
||||
|
||||
if sort_in is None:
|
||||
sort_in = nothing_to_report
|
||||
|
||||
sort_in.append(way_id)
|
||||
|
||||
|
||||
print(
|
||||
"Feed JSON data from this Overpass wizard query on stdin:\n"
|
||||
"(cycleway:both=no and (oneway=yes or oneway=1 or oneway=true or oneway=-1 or oneway=reverse) "
|
||||
"and oneway:bicycle!=*) and type:way global\n",
|
||||
file=sys.stderr
|
||||
)
|
||||
|
||||
ways = json.load(sys.stdin)["elements"]
|
||||
for way in ways:
|
||||
assert way["type"] == "way"
|
||||
|
||||
try:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
||||
_ = list(executor.map(
|
||||
lambda i_way: handle_way(i_way[0], len(ways), i_way[1]),
|
||||
enumerate(map(lambda x: x["id"], ways))
|
||||
))
|
||||
|
||||
finally:
|
||||
print(json.dumps(
|
||||
{
|
||||
"changes": changes,
|
||||
"oneway_added_after_cycleway_both": oneway_added_after_cycleway_both,
|
||||
"nothing_to_report": nothing_to_report,
|
||||
"preconditions_not_met": preconditions_not_met
|
||||
},
|
||||
indent="\t"
|
||||
))
|
Loading…
Reference in a new issue