Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toggle snapping #374

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions api/obs/api/routes/exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,6 +71,8 @@ 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"))

assert re.match(r"(-?\d+\.?\d+,?){4}", bbox)
bbox = list(map(float, bbox.split(",")))

Expand All @@ -93,10 +97,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:
Expand Down
22 changes: 15 additions & 7 deletions api/obs/api/routes/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
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

from obs.api.app import app
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
Expand All @@ -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

Expand All @@ -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]]:
"""
Expand Down Expand Up @@ -91,22 +97,24 @@ def get_filter_options(
@app.route(r"/tiles/<zoom:int>/<x:int>/<y:(\d+)\.pbf>")
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),
y=int(y),
user_id=user_id,
min_time=start,
max_time=end,
snap=snap,
)
)

Expand Down
3 changes: 2 additions & 1 deletion api/tools/prepare_sql_tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion api/tools/reimport_tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def reimport_tracks():
app.config.POSTGRES_MAX_OVERFLOW,
):
async with make_session() as session:
await session.execute(text("UPDATE track SET processing_status = 'queued';"))
await session.execute(text("UPDATE track SET processing_status = 'queued', processing_queued_at = '2030-01-01 10:10:10.000000';"))
await session.commit()


Expand Down
16 changes: 14 additions & 2 deletions frontend/src/pages/ExportPage/index.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -127,6 +129,16 @@ export default function ExportPage() {
onChange={(_e, {value}) => setMode(value)}
/>
</Form.Field>
<Form.Field>
<Checkbox
label={t('ExportPage.snapping')}
name="snap"
value="true"
onChange={(_e, {value}) => setSnap(!snap)}

/>
</Form.Field>


<Form.Field>
<label>{t('ExportPage.format.label')}</label>
Expand All @@ -149,7 +161,7 @@ export default function ExportPage() {
<Button
primary
as="a"
href={`${config?.apiUrl}/export/${mode}?bbox=${bbox}&fmt=${fmt}`}
href={`${config?.apiUrl}/export/${mode}?bbox=${bbox}&fmt=${fmt}&snap=${snap}`}
target="_blank"
rel="noreferrer noopener"
>
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/pages/MapPage/LayerSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function LayerSidebar({
obsEvents: {show: showEvents},
obsRegions: {show: showRegions},
obsTracks: {show: showTracks},
filters: {currentUser: filtersCurrentUser, dateMode, startDate, endDate, thresholdAfter},
filters: {currentUser: filtersCurrentUser, dateMode, startDate, endDate, thresholdAfter, snapEvents},
} = mapConfig

const openStreetMapCopyright = (
Expand Down Expand Up @@ -257,6 +257,12 @@ function LayerSidebar({
</List.Item>
{showEvents && (
<>
<List.Item>
<Checkbox
checked={snapEvents}
onChange={() => setMapConfigFlag('filters.snapEvents', !snapEvents)}
label={t('MapPage.sidebar.obsEvents.snap')}/>
</List.Item>
<List.Item>
<List.Header>{_.upperFirst(t('general.zone.urban'))}</List.Header>
<DiscreteColorMapLegend map={COLORMAP_URBAN} />
Expand All @@ -265,8 +271,12 @@ function LayerSidebar({
<List.Header>{_.upperFirst(t('general.zone.rural'))}</List.Header>
<DiscreteColorMapLegend map={COLORMAP_RURAL} />
</List.Item>

</>
)}



<Divider />

{filtersCurrentUser && login && (
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/pages/MapPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,15 @@ function MapPage({login}) {

const tiles = obsMapSource?.tiles?.map((tileUrl: string) => {
const query = new URLSearchParams()
if (mapConfig.filters.snapEvents) {
query.append('snap', true)
}
if (login) {
if (mapConfig.filters.currentUser) {
query.append('user', login.id)
}


if (mapConfig.filters.dateMode === 'range') {
if (mapConfig.filters.startDate) {
query.append('start', mapConfig.filters.startDate)
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/reducers/mapConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type MapConfig = {
startDate?: null | string
endDate?: null | string
thresholdAfter?: null | boolean
snap?: false | boolean
}
}

Expand Down Expand Up @@ -71,6 +72,7 @@ export const initialState: MapConfig = {
startDate: null,
endDate: null,
thresholdAfter: true,
snap: false
},
}

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/translations/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ ExportPage:
rechenaufwändig ist, nutze ihn daher nur so häufig wie nötig und nur in
begrenzten geografischen Bereichen, je nach Bedarf.
export: Exportieren
snapping: Events auf die zugeordneten Straßensegmente "snappen"
mode:
label: Modus
placeholder: Modus wählen
Expand Down Expand Up @@ -173,6 +174,7 @@ MapPage:

obsEvents:
title: Überholvorgänge
snap: Events auf Straßen "snappen"

obsTracks:
title: Meine eigenen Fahrten
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ ExportPage:
possible and do not exceed unreasonable poll frequencies.

export: Export
snapping: Snap events to OpenStreetMap
mode:
label: Mode
placeholder: Select mode
Expand Down Expand Up @@ -175,6 +176,8 @@ MapPage:

obsEvents:
title: Event points
snap: Snap events to road segments


obsTracks:
title: My own tracks
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/translations/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ ExportPage:
sélectionnez la zone de délimitation la plus petit possible et
n'effectuez pas d'export à répétition.
export: Exporter
snap: Snap evénements sur les rues
mode:
label: Mode
placeholder: Sélectionner un mode
Expand Down Expand Up @@ -176,6 +177,7 @@ MapPage:

obsEvents:
title: Points d'événement
snap: Snap points d'événement sur rues

obsTracks:
title: Mes propre traces
Expand Down
5 changes: 3 additions & 2 deletions tile-generator/layers/obs_events/layer.sql
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
DROP FUNCTION IF EXISTS layer_obs_events(bbox geometry, zoom_level int, user_id integer, min_time timestamp, max_time timestamp);
DROP FUNCTION IF EXISTS layer_obs_events(bbox geometry, zoom_level int, user_id integer, min_time timestamp, max_time timestamp, snap boolean);

CREATE OR REPLACE FUNCTION layer_obs_events(bbox geometry, zoom_level int, user_id integer, min_time timestamp, max_time timestamp)
CREATE OR REPLACE FUNCTION layer_obs_events(bbox geometry, zoom_level int, user_id integer, min_time timestamp, max_time timestamp, snap boolean)
RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, distance_stationary float, direction int, course float, speed float, time_stamp timestamp, zone zone_type, way_id bigint) AS $$

SELECT
overtaking_event.id::bigint as event_id,
overtaking_event.geometry as geometry,
CASE WHEN snap THEN ST_ClosestPoint(road.geometry,overtaking_event.geometry) else overtaking_event.geometry END as geometry,
distance_overtaker,
distance_stationary,
(case when direction_reversed then -1 else 1 end)::int as direction,
Expand Down
2 changes: 1 addition & 1 deletion tile-generator/layers/obs_events/obs_events.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ layer:
SELECT
event_id, geometry, distance_overtaker, distance_stationary, direction, course, speed, zone, way_id
FROM
layer_obs_events(!bbox!, z(!scale_denominator!), user_id, min_time, max_time)
layer_obs_events(!bbox!, z(!scale_denominator!), user_id, min_time, max_time, snap)
) AS t

schema:
Expand Down