Add the possibility to export road usage (without temporal filtering)

This commit is contained in:
gluap 2023-05-18 13:51:05 +02:00
parent a6811d4ba2
commit 7ff88aba15
No known key found for this signature in database
5 changed files with 85 additions and 11 deletions

View file

@ -3,11 +3,12 @@ from enum import Enum
from contextlib import contextmanager from contextlib import contextmanager
import zipfile import zipfile
import io import io
import re
from sqlite3 import connect from sqlite3 import connect
import shapefile import shapefile
from obs.api.db import OvertakingEvent from obs.api.db import OvertakingEvent
from sqlalchemy import select, func from sqlalchemy import select, func, text
from sanic.response import raw from sanic.response import raw
from sanic.exceptions import InvalidUsage from sanic.exceptions import InvalidUsage
@ -39,11 +40,11 @@ PROJECTION_4326 = (
@contextmanager @contextmanager
def shapefile_zip(): def shapefile_zip(shape_type=shapefile.POINT, basename="events"):
zip_buffer = io.BytesIO() zip_buffer = io.BytesIO()
shp, shx, dbf = (io.BytesIO() for _ in range(3)) shp, shx, dbf = (io.BytesIO() for _ in range(3))
writer = shapefile.Writer( 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 yield writer, zip_buffer
@ -52,10 +53,10 @@ def shapefile_zip():
writer.close() writer.close()
zip_file = zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) zip_file = zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False)
zip_file.writestr("events.shp", shp.getbuffer()) zip_file.writestr(f"{basename}.shp", shp.getbuffer())
zip_file.writestr("events.shx", shx.getbuffer()) zip_file.writestr(f"{basename}.shx", shx.getbuffer())
zip_file.writestr("events.dbf", dbf.getbuffer()) zip_file.writestr(f"{basename}.dbf", dbf.getbuffer())
zip_file.writestr("events.prj", PROJECTION_4326) zip_file.writestr(f"{basename}.prj", PROJECTION_4326)
zip_file.close() zip_file.close()
@ -74,7 +75,7 @@ async def export_events(req):
) )
if fmt == ExportFormat.SHAPEFILE: 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_overtaker", "N", decimal=4)
writer.field("distance_stationary", "N", decimal=4) writer.field("distance_stationary", "N", decimal=4)
writer.field("way_id", "N", decimal=0) writer.field("way_id", "N", decimal=0)
@ -119,3 +120,74 @@ async def export_events(req):
return json_response(geojson) return json_response(geojson)
raise InvalidUsage("unknown export format") raise InvalidUsage("unknown export format")
@api.get(r"/export/road_usage")
async def export_road_usage(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)
events = await req.ctx.db.stream(
text(
f"select ST_AsGeoJSON(ST_Transform(geometry,4326)), 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="road_usage") 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 road in events:
geom = json.loads(road.st_asgeojson)
writer.line([geom["coordinates"]])
writer.record(
distance_overtaker_mean=road.distance_overtaker_mean,
distance_overtaker_median=road.distance_overtaker_median,
distance_overtaker_max=road.distance_overtaker_max,
distance_overtaker_min=road.distance_overtaker_min,
usage_count=road.usage_count,
overtaking_event_count=road.overtaking_event_count,
direction=road.direction,
way_id=road.way_id,
zone=road.zone,
# "time"=event.time,
)
return raw(zip_buffer.getbuffer())
if fmt == ExportFormat.GEOJSON:
features = []
async for road in events:
features.append(
{
"type": "Feature",
"geometry": json.loads(road.st_asgeojson),
"properties": {
"distance_overtaker_mean": road.distance_overtaker_mean,
"distance_overtaker_max": road.distance_overtaker_max,
"distance_overtaker_median": road.distance_overtaker_median,
"overtaking_event_count": road.overtaking_event_count,
"usage_count": road.usage_count,
"distance_overtaker_array": road.distance_overtaker_array,
"direction": road.direction,
"way_id": road.way_id,
"zone": road.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", "road_usage"];
const FORMATS = ["geojson", "shapefile"]; const FORMATS = ["geojson", "shapefile"];
export default function ExportPage() { 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 [bbox, setBbox] = useState("8.294678,49.651182,9.059601,50.108249");
const [fmt, setFmt] = useState("geojson"); const [fmt, setFmt] = useState("geojson");
const config = useConfig(); const config = useConfig();
const exportUrl = `${config?.apiUrl}/export/events?bbox=${bbox}&fmt=${fmt}`;
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Page title="Export"> <Page title="Export">
@ -163,7 +162,7 @@ export default function ExportPage() {
<Button <Button
primary primary
as="a" as="a"
href={exportUrl} href={`${config?.apiUrl}/export/${mode}?bbox=${bbox}&fmt=${fmt}`}
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >

View file

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

View file

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

View file

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