Make it work (fix rebase issues; add translations)

This commit is contained in:
gluap 2022-10-28 11:56:37 +02:00
parent c7970d9382
commit 30fb29e437
No known key found for this signature in database
15 changed files with 129 additions and 160 deletions

View file

@ -13,7 +13,7 @@ from migrations.utils import dbtype
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = "a049e5eb24dd" revision = "a049e5eb24dd"
down_revision = "a9627f63fbed" down_revision = "99a3d2eb08f9"
branch_labels = None branch_labels = None
depends_on = None depends_on = None

View file

@ -408,29 +408,6 @@ class User(Base):
self.username = new_name self.username = new_name
async def rename(self, config, new_name):
old_name = self.username
renames = [
(join(basedir, old_name), join(basedir, new_name))
for basedir in [config.PROCESSING_OUTPUT_DIR, config.TRACKS_DIR]
]
for src, dst in renames:
if exists(dst):
raise FileExistsError(
f"cannot move {src!r} to {dst!r}, destination exists"
)
for src, dst in renames:
if not exists(src):
log.debug("Rename user %s: Not moving %s, not found", self.id, src)
else:
log.info("Rename user %s: Moving %s to %s", self.id, src, dst)
os.rename(src, dst)
self.username = new_name
class Comment(Base): class Comment(Base):
__tablename__ = "comment" __tablename__ = "comment"

View file

@ -1,6 +1,10 @@
from gzip import decompress from gzip import decompress
from sqlite3 import connect from sqlite3 import connect
from sanic.exceptions import Forbidden from datetime import datetime, time, timedelta
from typing import Optional, Tuple
import dateutil.parser
from sanic.exceptions import Forbidden, InvalidUsage
from sanic.response import raw from sanic.response import raw
from sqlalchemy import select, text from sqlalchemy import select, text
@ -89,10 +93,6 @@ async def tiles(req, zoom: int, x: int, y: str):
else: else:
user_id, start, end = get_filter_options(req) user_id, start, end = get_filter_options(req)
parse_date = lambda s: dateutil.parser.parse(s)
start = req.ctx.get_single_arg("start", default=None, convert=parse_date)
end = req.ctx.get_single_arg("end", default=None, convert=parse_date)
tile = await req.ctx.db.scalar( tile = await req.ctx.db.scalar(
text( text(
f"select data from getmvt(:zoom, :x, :y, :user_id, :min_time, :max_time) as b(data, key);" f"select data from getmvt(:zoom, :x, :y, :user_id, :min_time, :max_time) as b(data, key);"

View file

@ -14,6 +14,7 @@ import { useObservable } from "rxjs-hooks";
import { of, from, concat, combineLatest } from "rxjs"; import { of, from, concat, combineLatest } from "rxjs";
import { map, switchMap, distinctUntilChanged } from "rxjs/operators"; import { map, switchMap, distinctUntilChanged } from "rxjs/operators";
import { Duration, DateTime } from "luxon"; import { Duration, DateTime } from "luxon";
import {useTranslation} from 'react-i18next'
import api from "api"; import api from "api";
@ -26,6 +27,8 @@ function formatDuration(seconds) {
} }
export default function Stats() { export default function Stats() {
const {t} = useTranslation()
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const PER_PAGE = 10; const PER_PAGE = 10;
const stats = useObservable( const stats = useObservable(
@ -40,7 +43,7 @@ export default function Stats() {
return ( return (
<> <>
<Header as="h2">Top Regions</Header> <Header as="h2">{t(`Stats.topRegions`)}</Header>
<div> <div>
<Loader active={stats == null} /> <Loader active={stats == null} />
@ -48,8 +51,8 @@ export default function Stats() {
<Table celled> <Table celled>
<Table.Header> <Table.Header>
<Table.Row> <Table.Row>
<Table.HeaderCell>Region name</Table.HeaderCell> <Table.HeaderCell>{t(`Stats.regionName`)}</Table.HeaderCell>
<Table.HeaderCell>Event count</Table.HeaderCell> <Table.HeaderCell>{t(`Stats.eventCount`)}</Table.HeaderCell>
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>

View file

@ -130,7 +130,7 @@ export const getRegionLayers = (adminLevel = 6, baseColor = "#00897B", maxValue
"source": "obs", "source": "obs",
"source-layer": "obs_regions", "source-layer": "obs_regions",
"minzoom": 0, "minzoom": 0,
"maxzoom": 10, "maxzoom": 11,
"filter": [ "filter": [
"all", "all",
["==", "admin_level", adminLevel], ["==", "admin_level", adminLevel],
@ -162,7 +162,7 @@ export const getRegionLayers = (adminLevel = 6, baseColor = "#00897B", maxValue
"source": "obs", "source": "obs",
"source-layer": "obs_regions", "source-layer": "obs_regions",
"minzoom": 0, "minzoom": 0,
"maxzoom": 10, "maxzoom": 11,
"filter": [ "filter": [
"all", "all",
["==", "admin_level", adminLevel], ["==", "admin_level", adminLevel],

View file

@ -1,15 +1,15 @@
import React from 'react' import React from 'react'
import {Link} from 'react-router-dom' import {Grid, Loader, Header, Item} from 'semantic-ui-react'
import {Message, Grid, Loader, Header, Item} from 'semantic-ui-react'
import {useObservable} from 'rxjs-hooks' import {useObservable} from 'rxjs-hooks'
import {of, from} from 'rxjs' import {of, from} from 'rxjs'
import {map, switchMap} from 'rxjs/operators' import {map, switchMap} from 'rxjs/operators'
import {useTranslation} from 'react-i18next' import {useTranslation} from 'react-i18next'
import api from 'api' import api from 'api'
import {Stats, Page} from 'components' import {RegionStats, Stats, Page} from 'components'
import type {Track} from 'types'
import {TrackListItem} from './TracksPage' import {TrackListItem, NoPublicTracksMessage} from './TracksPage'
function MostRecentTrack() { function MostRecentTrack() {
const {t} = useTranslation() const {t} = useTranslation()
@ -26,19 +26,13 @@ function MostRecentTrack() {
return ( return (
<> <>
<Header as="h2">Most recent tracks</Header> <Header as="h2">{t('HomePage.mostRecentTrack')}</Header>
<Loader active={tracks === null} /> <Loader active={track === null} />
{tracks?.length === 0 ? ( {track === undefined ? (
<Message> <NoPublicTracksMessage />
<Translate i18nKey="HomePage.noPublicTracks"> ) : track ? (
No public tracks yet. <Link to="/upload">Upload the first!</Link>
</Translate>
</Message>
) : tracks ? (
<Item.Group> <Item.Group>
{tracks.map((track) => ( <TrackListItem track={track} />
<TrackListItem key={track.id} track={track} />
))}
</Item.Group> </Item.Group>
) : null} ) : null}
</> </>
@ -52,6 +46,7 @@ export default function HomePage() {
<Grid.Row> <Grid.Row>
<Grid.Column width={8}> <Grid.Column width={8}>
<Stats /> <Stats />
<RegionStats />
</Grid.Column> </Grid.Column>
<Grid.Column width={8}> <Grid.Column width={8}>
<MostRecentTrack /> <MostRecentTrack />

View file

@ -24,14 +24,6 @@ import { ColorMapLegend, DiscreteColorMapLegend } from "components";
const BASEMAP_STYLE_OPTIONS = ["positron", "bright"]; const BASEMAP_STYLE_OPTIONS = ["positron", "bright"];
const ROAD_ATTRIBUTE_OPTIONS = [ const ROAD_ATTRIBUTE_OPTIONS = [
{value: 'distance_overtaker_mean', key: 'distance_overtaker_mean', text: 'Overtaker distance mean'},
{value: 'distance_overtaker_min', key: 'distance_overtaker_min', text: 'Overtaker distance minimum'},
{value: 'distance_overtaker_max', key: 'distance_overtaker_max', text: 'Overtaker distance maximum'},
{value: 'distance_overtaker_median', key: 'distance_overtaker_median', text: 'Overtaker distance median'},
{value: 'overtaking_event_count', key: 'overtaking_event_count', text: 'Event count'},
{value: 'usage_count', key: 'usage_count', text: 'Usage count'},
{value: 'zone', key: 'zone', text: 'Overtaking distance zone'}
]
"distance_overtaker_mean", "distance_overtaker_mean",
"distance_overtaker_min", "distance_overtaker_min",
"distance_overtaker_max", "distance_overtaker_max",
@ -41,11 +33,7 @@ const ROAD_ATTRIBUTE_OPTIONS = [
"zone", "zone",
]; ];
const DATE_FILTER_MODES = [ const DATE_FILTER_MODES = ["none", "range", "threshold"];
{ value: "none", key: "none", text: "All time" },
{ value: "range", key: "range", text: "Start and end range" },
{ value: "threshold", key: "threshold", text: "Before/after comparison" },
];
type User = Object; type User = Object;
@ -183,21 +171,6 @@ function LayerSidebar({
/> />
</List.Item> </List.Item>
</> </>
) : (
<List.Item>
<ColorMapLegend
map={_.chunk(
colorByCount(
"obsRoads.maxCount",
mapConfig.obsRoads.maxCount,
viridisSimpleHtml
).slice(3),
2
)}
twoTicks
/>
</List.Item>
</>
) : attribute.endsWith("zone") ? ( ) : attribute.endsWith("zone") ? (
<> <>
<List.Item> <List.Item>

View file

@ -2,14 +2,16 @@ import React, { useState, useCallback } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import _ from "lodash"; import _ from "lodash";
import { List, Header, Icon, Button } from "semantic-ui-react"; import { List, Header, Icon, Button } from "semantic-ui-react";
import { useTranslation } from "react-i18next";
import styles from "./styles.module.less"; import styles from "./styles.module.less";
export default function RegionInfo({ region, mapInfoPortal, onClose }) { export default function RegionInfo({ region, mapInfoPortal, onClose }) {
const { t } = useTranslation();
const content = ( const content = (
<> <>
<div className={styles.closeHeader}> <div className={styles.closeHeader}>
<Header as="h3">{region.properties.name || "Unnamed region"}</Header> <Header as="h3">{region.properties.name || t(`MapPage.regionInfo.unnamedRegion`)}</Header>
<Button primary icon onClick={onClose}> <Button primary icon onClick={onClose}>
<Icon name="close" /> <Icon name="close" />
</Button> </Button>
@ -17,7 +19,7 @@ export default function RegionInfo({ region, mapInfoPortal, onClose }) {
<List> <List>
<List.Item> <List.Item>
<List.Header>Number of events</List.Header> <List.Header>{t(`MapPage.regionInfo.eventNumber`)}</List.Header>
<List.Content>{region.properties.overtaking_event_count ?? 0}</List.Content> <List.Content>{region.properties.overtaking_event_count ?? 0}</List.Content>
</List.Item> </List.Item>
</List> </List>

View file

@ -1,15 +1,26 @@
import React, {useState, useCallback} from 'react' import React, { useState, useCallback } from "react";
import _ from 'lodash' import _ from "lodash";
import {Segment, Menu, Header, Label, Icon, Table} from 'semantic-ui-react' import {
import {Layer, Source} from 'react-map-gl' Segment,
import {of, from, concat} from 'rxjs' Menu,
import {useObservable} from 'rxjs-hooks' Header,
import {switchMap, distinctUntilChanged} from 'rxjs/operators' Label,
import {Chart} from 'components' Icon,
import {pairwise} from 'utils' Table,
Message,
Button,
} from "semantic-ui-react";
import { Layer, Source } from "react-map-gl";
import { of, from, concat } from "rxjs";
import { useObservable } from "rxjs-hooks";
import { switchMap, distinctUntilChanged } from "rxjs/operators";
import { Chart } from "components";
import { pairwise } from "utils";
import { useTranslation } from "react-i18next";
import api from 'api' import type { Location } from "types";
import {colorByDistance, borderByZone} from 'mapstyles' import api from "api";
import { colorByDistance, borderByZone } from "mapstyles";
import styles from "./styles.module.less"; import styles from "./styles.module.less";
@ -127,7 +138,15 @@ function HistogramChart({ bins, counts, zone }) {
); );
} }
export default function RoadInfo({ clickLocation }) { export default function RoadInfo({
clickLocation,
hasFilters,
onClose,
}: {
clickLocation: Location | null;
hasFilters: boolean;
onClose: () => void;
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [direction, setDirection] = useState("forwards"); const [direction, setDirection] = useState("forwards");
@ -196,42 +215,51 @@ export default function RoadInfo({ clickLocation }) {
/> />
</Header> </Header>
{hasFilters && (
<Message info icon>
<Icon name="info circle" small />
<Message.Content>
{t("MapPage.roadInfo.hintFiltersNotApplied")}
</Message.Content>
</Message>
)}
{info?.road.zone && ( {info?.road.zone && (
<Label size="small" color={ZONE_COLORS[info?.road.zone]}> <Label size="small" color={ZONE_COLORS[info?.road.zone]}>
{t(`general.zone.${info.road.zone}`)} {t(`general.zone.${info.road.zone}`)}
</Label> </Label>
)} )}
{info.road.oneway && ( {info?.road.oneway && (
<Label size="small" color="blue"> <Label size="small" color="blue">
<Icon name="long arrow alternate right" fitted />{" "} <Icon name="long arrow alternate right" fitted />{" "}
{t("MapPage.roadInfo.oneway")} {t("MapPage.roadInfo.oneway")}
</Label> </Label>
)} )}
{info.road.oneway ? null : ( {info?.road.oneway ? null : (
<Menu size="tiny" pointing> <Menu size="tiny" fluid secondary>
<Menu.Item header>{t("MapPage.roadInfo.direction")}</Menu.Item> <Menu.Item header>{t("MapPage.roadInfo.direction")}</Menu.Item>
<Menu.Item <Menu.Item
name="forwards" name="forwards"
active={direction === "forwards"} active={direction === "forwards"}
onClick={onClickDirection} onClick={onClickDirection}
> >
{getCardinalDirection(t, info.forwards?.bearing)} {getCardinalDirection(t, info?.forwards?.bearing)}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
name="backwards" name="backwards"
active={direction === "backwards"} active={direction === "backwards"}
onClick={onClickDirection} onClick={onClickDirection}
> >
{getCardinalDirection(t, info.backwards?.bearing)} {getCardinalDirection(t, info?.backwards?.bearing)}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
)} )}
{info[direction] && <RoadStatsTable data={info[direction]} />} {info?.[direction] && <RoadStatsTable data={info[direction]} />}
{info[direction]?.distanceOvertaker?.histogram && ( {info?.[direction]?.distanceOvertaker?.histogram && (
<> <>
<Header as="h5"> <Header as="h5">
{t("MapPage.roadInfo.overtakerDistanceDistribution")} {t("MapPage.roadInfo.overtakerDistanceDistribution")}
@ -246,7 +274,7 @@ export default function RoadInfo({ clickLocation }) {
return ( return (
<> <>
{info.road && ( {info?.road && (
<Source id="highlight" type="geojson" data={info.road.geometry}> <Source id="highlight" type="geojson" data={info.road.geometry}>
<Layer <Layer
id="route" id="route"
@ -279,11 +307,10 @@ export default function RoadInfo({ clickLocation }) {
</Source> </Source>
)} )}
{content && mapInfoPortal && ( {content && (
createPortal(
<div className={styles.mapInfoBox}> <div className={styles.mapInfoBox}>
{content} <Segment loading={loading}>{content}</Segment>
</div>, mapInfoPortal))} </div>
)} )}
</> </>
); );

View file

@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo } from "react"; import React, { useState, useCallback, useMemo, useRef } from "react";
import _ from "lodash"; import _ from "lodash";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Button } from "semantic-ui-react"; import { Button } from "semantic-ui-react";
@ -6,6 +6,7 @@ import { Layer, Source } from "react-map-gl";
import produce from "immer"; import produce from "immer";
import classNames from "classnames"; import classNames from "classnames";
import api from "api";
import type { Location } from "types"; import type { Location } from "types";
import { Page, Map } from "components"; import { Page, Map } from "components";
import { useConfig } from "config"; import { useConfig } from "config";
@ -15,6 +16,7 @@ import {
borderByZone, borderByZone,
reds, reds,
isValidAttribute, isValidAttribute,
getRegionLayers
} from "mapstyles"; } from "mapstyles";
import { useMapConfig } from "reducers/mapConfig"; import { useMapConfig } from "reducers/mapConfig";
@ -69,8 +71,8 @@ const getRoadsLayer = (colorAttribute, maxCount) =>
: colorAttribute.endsWith("zone") : colorAttribute.endsWith("zone")
? borderByZone() ? borderByZone()
: "#DDD"; : "#DDD";
draft.paint["line-opacity"][3] = 12; draft.paint["line-opacity"][3] = 10;
draft.paint["line-opacity"][5] = 13; draft.paint["line-opacity"][5] = 11;
}); });
const getEventsLayer = () => ({ const getEventsLayer = () => ({
@ -193,10 +195,9 @@ function MapPage({ login }) {
node = node.parentNode; node = node.parentNode;
} }
setClickLocation({longitude: e.lngLat[0], latitude: e.lngLat[1]})
const { zoom } = viewportRef.current; const { zoom } = viewportRef.current;
if (zoom < 10) { if (zoom < 11) {
const clickedRegion = e.features?.find( const clickedRegion = e.features?.find(
(f) => f.source === "obs" && f.sourceLayer === "obs_regions" (f) => f.source === "obs" && f.sourceLayer === "obs_regions"
); );
@ -204,11 +205,8 @@ function MapPage({ login }) {
clickedRegion ? { type: "region", region: clickedRegion } : null clickedRegion ? { type: "region", region: clickedRegion } : null
); );
} else { } else {
const road = await api.get("/mapdetails/road", { setClickLocation({longitude: e.lngLat[0], latitude: e.lngLat[1]})
query: { }
longitude: e.lngLat[0],
latitude: e.lngLat[1],
radius: 100,
}, },
[setClickLocation] [setClickLocation]
); );
@ -292,33 +290,6 @@ function MapPage({ login }) {
} }
); );
const tiles = obsMapSource?.tiles?.map((tileUrl: string) => {
const query = new URLSearchParams();
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);
}
if (mapConfig.filters.endDate) {
query.append("end", mapConfig.filters.endDate);
}
} else if (mapConfig.filters.dateMode === "threshold") {
if (mapConfig.filters.startDate) {
query.append(
mapConfig.filters.thresholdAfter ? "start" : "end",
mapConfig.filters.startDate
);
}
}
}
const queryString = String(query);
return tileUrl + (queryString ? "?" : "") + queryString;
});
const hasFilters: boolean = const hasFilters: boolean =
login && login &&
(mapConfig.filters.currentUser || mapConfig.filters.dateMode !== "none"); (mapConfig.filters.currentUser || mapConfig.filters.dateMode !== "none");
@ -330,6 +301,7 @@ function MapPage({ login }) {
styles.mapContainer, styles.mapContainer,
banner ? styles.hasBanner : null banner ? styles.hasBanner : null
)} )}
ref={mapInfoPortal}
> >
{layerSidebar && ( {layerSidebar && (
<div className={styles.mapSidebar}> <div className={styles.mapSidebar}>
@ -338,6 +310,7 @@ function MapPage({ login }) {
)} )}
<div className={styles.map}> <div className={styles.map}>
<Map viewportFromUrl onClick={onClick} onViewportChange={onViewportChange} hasToolbar> <Map viewportFromUrl onClick={onClick} onViewportChange={onViewportChange} hasToolbar>
<div className={styles.mapToolbar}>
<Button <Button
style={{ style={{
position: "absolute", position: "absolute",
@ -355,12 +328,12 @@ function MapPage({ login }) {
<Layer key={layer.id} {...layer} /> <Layer key={layer.id} {...layer} />
))} ))}
</Source> </Source>
{details?.type === "road" && details?.road?.road && (
<RoadInfo <RoadInfo
{...{ clickLocation, hasFilters, onClose: onCloseRoadInfo }} {...{ clickLocation, hasFilters, onClose: onCloseRoadInfo }}
/> />
)}
{details?.type === "region" && details?.region && ( {details?.type === "region" && details?.region && (
<RegionInfo <RegionInfo
@ -369,8 +342,6 @@ function MapPage({ login }) {
onClose={onCloseDetails} onClose={onCloseDetails}
/> />
)} )}
<RoadInfo {...{ clickLocation }} />
</Map> </Map>
</div> </div>
</div> </div>

View file

@ -1,11 +1,20 @@
import React, {useCallback} from 'react' import React, { useCallback } from "react";
import {connect} from 'react-redux' import { connect } from "react-redux";
import {Button, Message, Item, Header, Loader, Pagination, Icon} from 'semantic-ui-react' import {
import {useObservable} from 'rxjs-hooks' Button,
import {Link} from 'react-router-dom' Message,
import {of, from, concat} from 'rxjs' Item,
import {map, switchMap, distinctUntilChanged} from 'rxjs/operators' Header,
import _ from 'lodash' Loader,
Pagination,
Icon,
} from "semantic-ui-react";
import { useObservable } from "rxjs-hooks";
import { Link } from "react-router-dom";
import { of, from, concat } from "rxjs";
import { map, switchMap, distinctUntilChanged } from "rxjs/operators";
import _ from "lodash";
import { useTranslation, Trans as Translate } from "react-i18next";
import type { Track } from "types"; import type { Track } from "types";
import { import {
@ -82,7 +91,7 @@ export function NoPublicTracksMessage() {
); );
} }
function maxLength(t: string|null, max: number): string|null { function maxLength(t: string | null, max: number): string | null {
if (t && t.length > max) { if (t && t.length > max) {
return t.substring(0, max) + " ..."; return t.substring(0, max) + " ...";
} else { } else {

View file

@ -62,6 +62,9 @@ Stats:
thisMonth: Dieser Monat thisMonth: Dieser Monat
thisYear: Dieses Jahr thisYear: Dieses Jahr
allTime: Immer allTime: Immer
topRegions: Regionen mit den meisten Messwerten
eventCount: Überholevents
regionName: Region
TracksPage: TracksPage:
titlePublic: Öffentliche Fahrten titlePublic: Öffentliche Fahrten
@ -201,6 +204,9 @@ MapPage:
southWest: südwestwärts southWest: südwestwärts
west: westwärts west: westwärts
northWest: nordwestwärts northWest: nordwestwärts
regionInfo:
unnamedRegion: Unbenannte Region
eventNumber: Anzahl Überholevents
SettingsPage: SettingsPage:
title: Einstellungen title: Einstellungen

View file

@ -67,6 +67,9 @@ Stats:
thisMonth: This month thisMonth: This month
thisYear: This year thisYear: This year
allTime: All time allTime: All time
topRegions: Region Leaderboard
eventCount: Overtaking events
regionName: Region
TracksPage: TracksPage:
titlePublic: Public tracks titlePublic: Public tracks
@ -206,6 +209,10 @@ MapPage:
southWest: south-west bound southWest: south-west bound
west: west bound west: west bound
northWest: north-west bound northWest: north-west bound
regionInfo:
unnamedRegion: "unnamed region"
eventNumber: Number of Overtaking events
SettingsPage: SettingsPage:
title: Settings title: Settings

View file

@ -12,11 +12,10 @@ RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, dist
CASE WHEN road.zone IS NULL THEN 'urban' else road.zone END as zone, CASE WHEN road.zone IS NULL THEN 'urban' else road.zone END as zone,
overtaking_event.way_id::bigint as way_id overtaking_event.way_id::bigint as way_id
FROM overtaking_event FROM overtaking_event
FULL OUTER JOIN road ON (road.way_id = overtaking_event.way_id) FULL OUTER JOIN road ON road.way_id = overtaking_event.way_id
JOIN track on track.id = overtaking_event.track_id JOIN track on track.id = overtaking_event.track_id
WHERE WHERE ST_Transform(overtaking_event.geometry, 3857) && bbox
zoom_level >= 10 AND AND zoom_level >= 12
ST_Transform(overtaking_event.geometry, 3857) && bbox;
AND (user_id is NULL OR user_id = track.author_id) AND (user_id is NULL OR user_id = track.author_id)
AND time BETWEEN COALESCE(min_time, '1900-01-01'::timestamp) AND COALESCE(max_time, '2100-01-01'::timestamp); AND time BETWEEN COALESCE(min_time, '1900-01-01'::timestamp) AND COALESCE(max_time, '2100-01-01'::timestamp);

View file

@ -67,7 +67,7 @@ RETURNS TABLE(
) e on (e.way_id = road.way_id and (road.directionality != 0 or e.direction_reversed = r.rev)) ) e on (e.way_id = road.way_id and (road.directionality != 0 or e.direction_reversed = r.rev))
WHERE WHERE
zoom_level >= 10 AND zoom_level >= 11 AND
road.geometry && bbox road.geometry && bbox
GROUP BY GROUP BY
road.name, road.name,