Skip to content

Commit 0fc6bd9

Browse files
Add initial challenges and simple library (#1)
* Add challenge: stop_area_name derived from platform names * Add challenge: check large stop_area bounding boxes * Add READMEs --------- Co-authored-by: Robin Thomas <Robbendebiene@users.noreply.github.com>
1 parent fb7c30e commit 0fc6bd9

File tree

10 files changed

+400
-0
lines changed

10 files changed

+400
-0
lines changed

.github/workflows/release.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Create Release with JSON Artifacts
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v2
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v2
19+
with:
20+
python-version: '3.10'
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
26+
- name: Run large_stop_area_bbox script
27+
run: python challenges/large_stop_area_bbox/main.py
28+
29+
- name: Run stop_area_names_from_platform_names script
30+
run: python challenges/stop_area_names_from_platform_names/main.py
31+
32+
- name: Create JSON artifacts directory
33+
run: mkdir -p artifacts
34+
35+
- name: Move JSON files to artifacts
36+
run: |
37+
mv large_stop_area_bbox.json artifacts/
38+
mv stop_area_names_from_platform_names.json artifacts/
39+
40+
- name: Get current date
41+
id: get_date
42+
run: echo "DATE=$(date +'%Y.%m.%d')" >> $GITHUB_ENV
43+
44+
- name: Create Release
45+
id: create_release
46+
uses: softprops/action-gh-release@v2
47+
with:
48+
name: "${{ env.DATE }}+${{ github.run_number }}"
49+
tag_name: "${{ env.DATE }}+${{ github.run_number }}"
50+
body: 'Release with JSON artifacts'
51+
token: ${{ secrets.GITHUB_TOKEN }}
52+
files: artifacts/*.json

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
data.osm
3+
*.pyc
4+
*.csv
5+
challenges/large_stop_area_bbox/large_stop_area_bbox.json
6+
challenges/stop_area_names_from_platform_names/stop_area_names_from_platform_names.json
7+
shared/creds.json
8+
*.json
9+
.venv/
10+
venv/

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,18 @@
11
# PT-QA-MapRoulette
22
Public transport quality assurance callenges for MapRoulette
3+
4+
## Current challenges
5+
6+
- `large-stop_area-bbox` - Checks the bbox size of all stop_area relations in a certain area and creates tasks to check on those that surpass a certain threshold value
7+
- `stop_area-names-from-platform-names` - Creates a TagFix-Challenge json-File that proposes to add a name=-Tag to stop_area-Relations where all platforms have the same name
8+
9+
## How to use the output of the scripts / this repository
10+
11+
A GitHub Action executes the scripts and creates a release with the output files. You can download the files from the [releases page](https://github.com/OPENER-next/PT-QA-MapRoulette/releases).
12+
13+
[![Release with JSON Artifacts](https://github.com/OPENER-next/PT-QA-MapRoulette/actions/workflows/release.yml/badge.svg)](https://github.com/OPENER-next/PT-QA-MapRoulette/actions/workflows/release.yml)
14+
15+
For usage with MapRoulette, it is recommended that you use the static url that will always point to the latest release.
16+
These URLs are currently:
17+
- https://github.com/OPENER-next/PT-QA-MapRoulette/releases/latest/download/large_stop_area_bbox.json
18+
- https://github.com/OPENER-next/PT-QA-MapRoulette/releases/latest/download/stop_area_names_from_platform_names.json
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Überprüfung großer stop_area BoundingBoxen - Deutschland
2+
3+
This challenge checks the bbox size of all stop_area relations in germany and creates tasks to check on those that surpass a certain threshold value.
4+
5+
## Maproulette Challenge Description
6+
7+
```
8+
In dieser Challenge werden dir einige Haltestellenrelationen gezeigt, die ungewöhnlich groß sind. Sieh nach, ob sie nur die korrekten Elemente enthalten und verändere die Relation wenn nötig.
9+
10+
```
11+
12+
13+
14+
## Maproulette Task Instruction
15+
16+
```
17+
Diese Haltestellenrelation ist ungewöhnlich groß. Sieh nach, ob nur die korrekten Elemente enthalten sind und verändere die Relation wenn nötig.
18+
```
19+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import sys, math
2+
from pathlib import Path
3+
sys.path.append(str(Path(__file__).parent.parent.parent / "shared"))
4+
import challenge_builder as mrcb
5+
from turfpy.measurement import distance
6+
from geojson import Point
7+
8+
## Functions specific to this challenge
9+
10+
# Use this function to determine if a task needs to be created for a given element
11+
# use this function for filtering things that overpass cannot filter, maybe by using a function from a different file that you specifically implemented for this task
12+
# if your overpass query already returns all elements that need to be fixed, make this function return True
13+
def needsTask(e):
14+
# An element needs a task if either the vertical or the horizontal bbox edge is longer than a predefined value in meters
15+
max_distance = 1000
16+
# Calculate the length of the longitude difference of the bbox
17+
if len(e['geometry']['coordinates']) == 1:
18+
e['geometry']['coordinates'] = e['geometry']['coordinates'][0]
19+
20+
e['bounds'] = {}
21+
e['bounds']['minlat'] = e['geometry']['coordinates'][0][1]
22+
e['bounds']['minlon'] = e['geometry']['coordinates'][0][0]
23+
e['bounds']['maxlat'] = e['geometry']['coordinates'][2][1]
24+
e['bounds']['maxlon'] = e['geometry']['coordinates'][2][0]
25+
26+
# Calculate the length of the longitude difference of the bbox
27+
point1 = Point((e['bounds']['minlon'], e['bounds']['minlat']))
28+
point2 = Point((e['bounds']['maxlon'], e['bounds']['minlat']))
29+
lon_diff = distance(point1, point2) * 1000 # Convert kilometers to meters
30+
31+
# Calculate the length of the latitude difference of the bbox
32+
point3 = Point((e['bounds']['minlon'], e['bounds']['maxlat']))
33+
lat_diff = distance(point1, point3) * 1000 # Convert kilometers to meters
34+
35+
# If either the longitude or the latitude difference is longer than 1000 meters, return True
36+
if lon_diff > max_distance or lat_diff > max_distance:
37+
return True
38+
39+
40+
41+
opQuery = """
42+
[out:json][timeout:250];
43+
area(id:3600051477)->.searchArea;
44+
relation["public_transport"="stop_area"](area.searchArea);
45+
foreach {
46+
>> -> .ancestors;
47+
make myCustomElement
48+
::id=min(id()),
49+
::geom=hull(ancestors.gcat(geom()));
50+
out bb;
51+
}
52+
"""
53+
54+
op = mrcb.Overpass()
55+
resultElements = op.queryElementsAsGeoJSON(opQuery, forceGeomType="Polygon")
56+
57+
challenge = mrcb.Challenge()
58+
59+
for element in resultElements:
60+
if needsTask(element):
61+
mainFeature = mrcb.GeoFeature.withId(
62+
osmType="relation",
63+
osmId=element["properties"]["@id"],
64+
geometry=element["geometry"],
65+
properties={})
66+
t = mrcb.Task(
67+
mainFeature=mainFeature)
68+
challenge.addTask(t)
69+
70+
challenge.saveToFile("large_stop_area_bbox.json")
71+
72+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Add names to stop_areas by platform names
2+
3+
This script gets all stop_areas without names from the overpass API. In the query, a virtual OSM object is constructed with a "combined name" of all platforms of this stop_area. If their names are different, the `name=`-tag of this new object will read `< multiple values found >`, and it will not be considered. If their names are equal, this name is written to the `name=`-tag and suggested to be added as the name for the whole stop_area.
4+
5+
## Maproulette Challenge Description
6+
7+
```
8+
In dieser Challenge bearbeitest du Haltestellenrelationen, die keinen Namen haben. Alle Steige in dieser Relation (oder zumindest alle, die einen Namen haben), haben den gleichen Namen. Deswegen ist mit hoher Wahrscheinlichkeit davon auszugehen, dass die Haltestelle als ganzes auch so heißt. Wenn der Name sinnvoll erscheint, drücke auf "Yes", um ihn der Haltestellenrelation hinzuzufügen.
9+
10+
```
11+
12+
13+
14+
## Maproulette Task Instruction
15+
16+
```
17+
Das ist der Mittelpunkt einer Haltestellenrelation. Sie hat selber noch keinen Namen. Alle Steige in dieser Relation (oder zumindest alle, die einen Namen haben), haben den gleichen Namen. Deswegen ist mit hoher Wahrscheinlichkeit davon auszugehen, dass die Haltestelle als ganzes auch so heißt. Wenn der Name sinnvoll erscheint, drücke auf "Yes", um ihn der Haltestellenrelation hinzuzufügen.
18+
19+
```
20+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import sys
2+
sys.path.append('shared')
3+
import challenge_builder as mrcb
4+
5+
opQuery = """
6+
[out:json][timeout:250];
7+
area(id:3600051477)->.searchArea;
8+
rel["public_transport"="stop_area"][!"name"](area.searchArea)->.stop_areas;
9+
// for each stop area
10+
foreach.stop_areas -> .stop_area {
11+
// recurse relation fully down (select all ancestors)
12+
// required to construct the full geometry for relations that contain other relations
13+
// and to get members of type relation (single > only returns ways and nodes)
14+
.stop_area >>;
15+
nwr._["public_transport"="platform"];
16+
17+
if (u(t["name"]) != "< multiple values found >" && u(t["name"]) != "") {
18+
make StopArea
19+
name=u(t["name"]),
20+
// get stop are relation id
21+
::id=stop_area.u(id()),
22+
// group geometries into one
23+
::geom=hull(gcat(geom()));
24+
25+
out center tags;
26+
}
27+
}
28+
"""
29+
30+
op = mrcb.Overpass()
31+
resultElements = op.queryElementsAsGeoJSON(opQuery)
32+
33+
challenge = mrcb.Challenge()
34+
35+
for element in resultElements:
36+
mainFeature = mrcb.GeoFeature.withId(
37+
osmType="relation",
38+
osmId=element["properties"]["@id"],
39+
geometry=element["geometry"],
40+
properties={})
41+
suggestedName = element["properties"]["name"]
42+
cooperativeWork = mrcb.TagFix(
43+
osmType="relation",
44+
osmId=element["properties"]["@id"],
45+
tags={"name": suggestedName})
46+
t = mrcb.Task(
47+
mainFeature=mainFeature,
48+
additionalFeatures=[],
49+
cooperativeWork=cooperativeWork)
50+
challenge.addTask(t)
51+
52+
challenge.saveToFile("stop_area_names_from_platform_names.json")

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
requests
2+
turfpy
3+
geojson

shared/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)