Add the possibility to export road usage (without temporal filtering)
This commit is contained in:
parent
a6811d4ba2
commit
7ff88aba15
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue