Merge pull request #341 from openbikesensor/next-semaphore-with-road-usage

Add export of road usage after review (without temporal filtering)
This commit is contained in:
gluap 2023-05-29 13:22:16 +02:00 committed by GitHub
commit b1cfd30da9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 13 deletions

View file

@ -21,7 +21,7 @@ RUN npm run build
# Build the API and add the built frontend to it
#############################################
FROM python:3.9.7-bullseye
FROM python:3.11.3-bullseye
RUN apt-get update &&\
apt-get install -y \

View file

@ -1,4 +1,4 @@
FROM python:3.9.7-bullseye
FROM python:3.11.3-bullseye
WORKDIR /opt/obs/api

View file

@ -3,11 +3,12 @@ from enum import Enum
from contextlib import contextmanager
import zipfile
import io
import re
from sqlite3 import connect
import shapefile
from obs.api.db import OvertakingEvent
from sqlalchemy import select, func
from sqlalchemy import select, func, text
from sanic.response import raw
from sanic.exceptions import InvalidUsage
@ -39,11 +40,11 @@ PROJECTION_4326 = (
@contextmanager
def shapefile_zip():
def shapefile_zip(shape_type=shapefile.POINT, basename="events"):
zip_buffer = io.BytesIO()
shp, shx, dbf = (io.BytesIO() for _ in range(3))
writer = shapefile.Writer(
shp=shp, shx=shx, dbf=dbf, shapeType=shapefile.POINT, encoding="utf8"
shp=shp, shx=shx, dbf=dbf, shapeType=shape_type, encoding="utf8"
)
yield writer, zip_buffer
@ -52,10 +53,10 @@ def shapefile_zip():
writer.close()
zip_file = zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False)
zip_file.writestr("events.shp", shp.getbuffer())
zip_file.writestr("events.shx", shx.getbuffer())
zip_file.writestr("events.dbf", dbf.getbuffer())
zip_file.writestr("events.prj", PROJECTION_4326)
zip_file.writestr(f"{basename}.shp", shp.getbuffer())
zip_file.writestr(f"{basename}.shx", shx.getbuffer())
zip_file.writestr(f"{basename}.dbf", dbf.getbuffer())
zip_file.writestr(f"{basename}.prj", PROJECTION_4326)
zip_file.close()
@ -74,7 +75,7 @@ async def export_events(req):
)
if fmt == ExportFormat.SHAPEFILE:
with shapefile_zip() as (writer, zip_buffer):
with shapefile_zip(basename="events") as (writer, zip_buffer):
writer.field("distance_overtaker", "N", decimal=4)
writer.field("distance_stationary", "N", decimal=4)
writer.field("way_id", "N", decimal=0)
@ -119,3 +120,73 @@ async def export_events(req):
return json_response(geojson)
raise InvalidUsage("unknown export format")
@api.get(r"/export/segments")
async def export_segments(req):
async with use_request_semaphore(req, "export_semaphore", timeout=30):
bbox = req.ctx.get_single_arg(
"bbox", default="-180,-90,180,90"
)
assert re.match(r"(-?\d+\.?\d+,?){4}", bbox)
fmt = req.ctx.get_single_arg("fmt", convert=ExportFormat)
segments = await req.ctx.db.stream(
text(
f"select ST_AsGeoJSON(ST_Transform(geometry,4326)) AS geometry, way_id, distance_overtaker_mean, distance_overtaker_min,distance_overtaker_max,distance_overtaker_median,overtaking_event_count,usage_count,direction,zone,offset_direction,distance_overtaker_array from layer_obs_roads(ST_Transform(ST_MakeEnvelope({bbox},4326),3857),11,NULL,'1900-01-01'::timestamp,'2100-01-01'::timestamp) WHERE usage_count>0"
)
)
if fmt == ExportFormat.SHAPEFILE:
with shapefile_zip(shape_type=3, basename="segments") as (writer, zip_buffer):
writer.field("distance_overtaker_mean", "N", decimal=4)
writer.field("distance_overtaker_max", "N", decimal=4)
writer.field("distance_overtaker_min", "N", decimal=4)
writer.field("distance_overtaker_median", "N", decimal=4)
writer.field("overtaking_event_count", "N", decimal=4)
writer.field("usage_count", "N", decimal=4)
writer.field("way_id", "N", decimal=0)
writer.field("direction", "N", decimal=0)
writer.field("zone", "C")
async for segment in segments:
geom = json.loads(segment.st_asgeojson)
writer.line([geom["coordinates"]])
writer.record(
distance_overtaker_mean=segment.distance_overtaker_mean,
distance_overtaker_median=segment.distance_overtaker_median,
distance_overtaker_max=segment.distance_overtaker_max,
distance_overtaker_min=segment.distance_overtaker_min,
usage_count=segment.usage_count,
overtaking_event_count=segment.overtaking_event_count,
direction=segment.direction,
way_id=segment.way_id,
zone=segment.zone,
)
return raw(zip_buffer.getbuffer())
if fmt == ExportFormat.GEOJSON:
features = []
async for segment in segments:
features.append(
{
"type": "Feature",
"geometry": json.loads(segment.geometry),
"properties": {
"distance_overtaker_mean": segment.distance_overtaker_mean,
"distance_overtaker_max": segment.distance_overtaker_max,
"distance_overtaker_median": segment.distance_overtaker_median,
"overtaking_event_count": segment.overtaking_event_count,
"usage_count": segment.usage_count,
"distance_overtaker_array": segment.distance_overtaker_array,
"direction": segment.direction,
"way_id": segment.way_id,
"zone": segment.zone,
},
}
)
geojson = {"type": "FeatureCollection", "features": features}
return json_response(geojson)
raise InvalidUsage("unknown export format")

View file

@ -104,7 +104,7 @@ const BoundingBoxSelector = React.forwardRef(
}
);
const MODES = ["events"];
const MODES = ["events", "segments"];
const FORMATS = ["geojson", "shapefile"];
export default function ExportPage() {
@ -112,7 +112,6 @@ export default function ExportPage() {
const [bbox, setBbox] = useState("8.294678,49.651182,9.059601,50.108249");
const [fmt, setFmt] = useState("geojson");
const config = useConfig();
const exportUrl = `${config?.apiUrl}/export/events?bbox=${bbox}&fmt=${fmt}`;
const { t } = useTranslation();
return (
<Page title="Export">
@ -163,7 +162,7 @@ export default function ExportPage() {
<Button
primary
as="a"
href={exportUrl}
href={`${config?.apiUrl}/export/${mode}?bbox=${bbox}&fmt=${fmt}`}
target="_blank"
rel="noreferrer noopener"
>

View file

@ -95,6 +95,7 @@ ExportPage:
label: Modus
placeholder: Modus wählen
events: Überholvorgänge
segments: Straßenstatistiken
format:
label: Format
placeholder: Format wählen

View file

@ -101,6 +101,7 @@ ExportPage:
label: Mode
placeholder: Select mode
events: Events
segments: Road statistics
format:
label: Format
placeholder: Select format

View file

@ -102,6 +102,7 @@ ExportPage:
label: Mode
placeholder: Sélectionner un mode
events: Evénements
segments: Statistiques des rues
format:
label: Format
placeholder: Sélectionner un format