From b11048530867a4492db08424f650a0d6679335be Mon Sep 17 00:00:00 2001 From: gluap Date: Mon, 21 Oct 2024 22:39:43 +0200 Subject: [PATCH 1/3] Add "snapping" toggle to Map and Export function --- api/obs/api/routes/exports.py | 12 ++++++++-- api/obs/api/routes/tiles.py | 22 +++++++++++++------ api/tools/prepare_sql_tiles.py | 3 ++- frontend/src/pages/ExportPage/index.tsx | 16 ++++++++++++-- frontend/src/pages/MapPage/LayerSidebar.tsx | 12 +++++++++- frontend/src/pages/MapPage/index.tsx | 4 ++++ frontend/src/reducers/mapConfig.ts | 2 ++ frontend/src/translations/de.yaml | 2 ++ frontend/src/translations/en.yaml | 3 +++ frontend/src/translations/fr.yaml | 2 ++ tile-generator/layers/obs_events/layer.sql | 5 +++-- .../layers/obs_events/obs_events.yaml | 2 +- 12 files changed, 69 insertions(+), 16 deletions(-) diff --git a/api/obs/api/routes/exports.py b/api/obs/api/routes/exports.py index 44fe34ad..89eb3380 100644 --- a/api/obs/api/routes/exports.py +++ b/api/obs/api/routes/exports.py @@ -8,6 +8,8 @@ from sqlite3 import connect import shapefile +from sanic.utils import str_to_bool + from obs.api.db import OvertakingEvent from sqlalchemy import select, func, text from sanic.response import raw @@ -69,6 +71,11 @@ def shapefile_zip(shape_type=shapefile.POINT, basename="events"): async def export_events(req): async with use_request_semaphore(req, "export_semaphore", timeout=30): bbox = req.ctx.get_single_arg("bbox", default="-180,-90,180,90") + snap = str_to_bool(req.ctx.get_single_arg("snap", default="false")) + log.info(req.ctx) + log.info(snap) + + assert re.match(r"(-?\d+\.?\d+,?){4}", bbox) bbox = list(map(float, bbox.split(","))) @@ -93,10 +100,11 @@ async def export_events(req): 19, NULL, '1900-01-01'::timestamp, - '2100-01-01'::timestamp + '2100-01-01'::timestamp, + :snap ) """ - ).bindparams(bbox0=bbox[0], bbox1=bbox[1], bbox2=bbox[2], bbox3=bbox[3]) + ).bindparams(bbox0=bbox[0], bbox1=bbox[1], bbox2=bbox[2], bbox3=bbox[3], snap=snap) ) if fmt == ExportFormat.SHAPEFILE: diff --git a/api/obs/api/routes/tiles.py b/api/obs/api/routes/tiles.py index 9ae12b47..dc5fef60 100644 --- a/api/obs/api/routes/tiles.py +++ b/api/obs/api/routes/tiles.py @@ -4,8 +4,10 @@ from typing import Optional, Tuple import dateutil.parser +from marshmallow.fields import Boolean from sanic.exceptions import Forbidden, InvalidUsage from sanic.response import raw +from sanic.utils import str_to_bool from sqlalchemy import text @@ -13,7 +15,7 @@ from obs.api.utils import use_request_semaphore -def get_tile(filename, zoom, x, y): +def get_tile(filename, zoom, x, y, snap): """ Inspired by: https://github.com/TileStache/TileStache/blob/master/TileStache/MBTiles.py @@ -27,8 +29,8 @@ def get_tile(filename, zoom, x, y): raise ValueError("mbtiles file is in wrong format: %s" % fmt) content = db.execute( - "SELECT tile_data FROM tiles WHERE zoom_level=? AND tile_column=? AND tile_row=?", - (zoom, x, (2**zoom - 1) - y), + "SELECT tile_data FROM tiles WHERE zoom_level=? AND tile_column=? AND tile_row=? AND snap=?", + (zoom, x, (2 ** zoom - 1) - y, snap), ).fetchone() return content and content[0] or None @@ -55,7 +57,11 @@ def round_date(date, to="weeks", up=False): TILE_CACHE_MAX_AGE = 3600 * 24 -def get_filter_options( +def get_public_filter_options(req) -> Tuple[bool,]: + return (str_to_bool(req.ctx.get_single_arg("snap", default="false")),) + + +def get_user_filter_options( req, ) -> Tuple[Optional[str], Optional[datetime], Optional[datetime]]: """ @@ -91,15 +97,16 @@ def get_filter_options( @app.route(r"/tiles///") async def tiles(req, zoom: int, x: int, y: str): async with use_request_semaphore(req, "tile_semaphore"): + snap, = get_public_filter_options(req) if app.config.get("TILES_FILE"): - tile = get_tile(req.app.config.TILES_FILE, int(zoom), int(x), int(y)) + tile = get_tile(req.app.config.TILES_FILE, int(zoom), int(x), int(y), snap=snap) else: - user_id, start, end = get_filter_options(req) + user_id, start, end = get_user_filter_options(req) tile = await req.ctx.db.scalar( text( - "select data from getmvt(:zoom, :x, :y, :user_id, :min_time, :max_time) as b(data, key);" + "select data from getmvt(:zoom, :x, :y, :user_id, :min_time, :max_time, :snap) as b(data, key);" ).bindparams( zoom=int(zoom), x=int(x), @@ -107,6 +114,7 @@ async def tiles(req, zoom: int, x: int, y: str): user_id=user_id, min_time=start, max_time=end, + snap=snap, ) ) diff --git a/api/tools/prepare_sql_tiles.py b/api/tools/prepare_sql_tiles.py index 2833fb59..715cc5c1 100755 --- a/api/tools/prepare_sql_tiles.py +++ b/api/tools/prepare_sql_tiles.py @@ -29,11 +29,12 @@ ("user_id", "integer", "NULL"), ("min_time", "timestamp", "NULL"), ("max_time", "timestamp", "NULL"), + ("snap", "boolean", "false"), ] class CustomMvtGenerator(MvtGenerator): - def generate_sqltomvt_func(self, fname, extra_args: List[Tuple[str, str]]) -> str: + def generate_sqltomvt_func(self, fname, extra_args: List[Tuple[str, str, str]]) -> str: """ Creates a SQL function that returns a single bytea value or null. This method is overridden to allow for custom arguments in the created function diff --git a/frontend/src/pages/ExportPage/index.tsx b/frontend/src/pages/ExportPage/index.tsx index 3d82721e..28bdc62e 100644 --- a/frontend/src/pages/ExportPage/index.tsx +++ b/frontend/src/pages/ExportPage/index.tsx @@ -1,7 +1,7 @@ import React, {useState, useCallback, useMemo} from 'react' import {Source, Layer} from 'react-map-gl' import _ from 'lodash' -import {Button, Form, Dropdown, Header, Message, Icon} from 'semantic-ui-react' +import {Button, Form, Dropdown, Header, Message, Icon, Checkbox} from 'semantic-ui-react' import {useTranslation, Trans as Translate} from 'react-i18next' import Markdown from 'react-markdown' @@ -96,6 +96,8 @@ const FORMATS = ['geojson', 'shapefile'] export default function ExportPage() { const [mode, setMode] = useState('events') + const [snap, setSnap] = useState('snap') + const [bbox, setBbox] = useState('8.294678,49.651182,9.059601,50.108249') const [fmt, setFmt] = useState('geojson') const config = useConfig() @@ -127,6 +129,16 @@ export default function ExportPage() { onChange={(_e, {value}) => setMode(value)} /> + + setSnap(!snap)} + + /> + + @@ -149,7 +161,7 @@ export default function ExportPage() {