diff --git a/api/migrations/versions/a049e5eb24dd_create_table_region.py b/api/migrations/versions/a049e5eb24dd_create_table_region.py
index 7e50667..1970967 100644
--- a/api/migrations/versions/a049e5eb24dd_create_table_region.py
+++ b/api/migrations/versions/a049e5eb24dd_create_table_region.py
@@ -13,7 +13,7 @@ from migrations.utils import dbtype
# revision identifiers, used by Alembic.
revision = "a049e5eb24dd"
-down_revision = "a9627f63fbed"
+down_revision = "99a3d2eb08f9"
branch_labels = None
depends_on = None
diff --git a/api/obs/api/db.py b/api/obs/api/db.py
index 585bf1c..f7716a6 100644
--- a/api/obs/api/db.py
+++ b/api/obs/api/db.py
@@ -408,29 +408,6 @@ class User(Base):
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):
__tablename__ = "comment"
diff --git a/api/obs/api/routes/tiles.py b/api/obs/api/routes/tiles.py
index 3579687..9b6b652 100644
--- a/api/obs/api/routes/tiles.py
+++ b/api/obs/api/routes/tiles.py
@@ -1,6 +1,10 @@
from gzip import decompress
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 sqlalchemy import select, text
@@ -89,10 +93,6 @@ async def tiles(req, zoom: int, x: int, y: str):
else:
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(
text(
f"select data from getmvt(:zoom, :x, :y, :user_id, :min_time, :max_time) as b(data, key);"
diff --git a/frontend/src/components/RegionStats/index.tsx b/frontend/src/components/RegionStats/index.tsx
index 5dac6b8..1282531 100644
--- a/frontend/src/components/RegionStats/index.tsx
+++ b/frontend/src/components/RegionStats/index.tsx
@@ -14,6 +14,7 @@ import { useObservable } from "rxjs-hooks";
import { of, from, concat, combineLatest } from "rxjs";
import { map, switchMap, distinctUntilChanged } from "rxjs/operators";
import { Duration, DateTime } from "luxon";
+import {useTranslation} from 'react-i18next'
import api from "api";
@@ -26,6 +27,8 @@ function formatDuration(seconds) {
}
export default function Stats() {
+ const {t} = useTranslation()
+
const [page, setPage] = useState(1);
const PER_PAGE = 10;
const stats = useObservable(
@@ -40,7 +43,7 @@ export default function Stats() {
return (
<>
-
+
@@ -48,8 +51,8 @@ export default function Stats() {
- Region name
- Event count
+ {t(`Stats.regionName`)}
+ {t(`Stats.eventCount`)}
diff --git a/frontend/src/mapstyles/index.js b/frontend/src/mapstyles/index.js
index 4e75ad4..abbf89e 100644
--- a/frontend/src/mapstyles/index.js
+++ b/frontend/src/mapstyles/index.js
@@ -130,7 +130,7 @@ export const getRegionLayers = (adminLevel = 6, baseColor = "#00897B", maxValue
"source": "obs",
"source-layer": "obs_regions",
"minzoom": 0,
- "maxzoom": 10,
+ "maxzoom": 11,
"filter": [
"all",
["==", "admin_level", adminLevel],
@@ -162,7 +162,7 @@ export const getRegionLayers = (adminLevel = 6, baseColor = "#00897B", maxValue
"source": "obs",
"source-layer": "obs_regions",
"minzoom": 0,
- "maxzoom": 10,
+ "maxzoom": 11,
"filter": [
"all",
["==", "admin_level", adminLevel],
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index cc1babb..2085330 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -1,15 +1,15 @@
import React from 'react'
-import {Link} from 'react-router-dom'
-import {Message, Grid, Loader, Header, Item} from 'semantic-ui-react'
+import {Grid, Loader, Header, Item} from 'semantic-ui-react'
import {useObservable} from 'rxjs-hooks'
import {of, from} from 'rxjs'
import {map, switchMap} from 'rxjs/operators'
import {useTranslation} from 'react-i18next'
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() {
const {t} = useTranslation()
@@ -26,19 +26,13 @@ function MostRecentTrack() {
return (
<>
-
-
- {tracks?.length === 0 ? (
-
-
- No public tracks yet. Upload the first!
-
-
- ) : tracks ? (
+ {t('HomePage.mostRecentTrack')}
+
+ {track === undefined ? (
+
+ ) : track ? (
- {tracks.map((track) => (
-
- ))}
+
) : null}
>
@@ -52,6 +46,7 @@ export default function HomePage() {
+
diff --git a/frontend/src/pages/MapPage/LayerSidebar.tsx b/frontend/src/pages/MapPage/LayerSidebar.tsx
index 42b7f1e..e92416a 100644
--- a/frontend/src/pages/MapPage/LayerSidebar.tsx
+++ b/frontend/src/pages/MapPage/LayerSidebar.tsx
@@ -24,14 +24,6 @@ import { ColorMapLegend, DiscreteColorMapLegend } from "components";
const BASEMAP_STYLE_OPTIONS = ["positron", "bright"];
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_min",
"distance_overtaker_max",
@@ -41,11 +33,7 @@ const ROAD_ATTRIBUTE_OPTIONS = [
"zone",
];
-const DATE_FILTER_MODES = [
- { value: "none", key: "none", text: "All time" },
- { value: "range", key: "range", text: "Start and end range" },
- { value: "threshold", key: "threshold", text: "Before/after comparison" },
-];
+const DATE_FILTER_MODES = ["none", "range", "threshold"];
type User = Object;
@@ -183,21 +171,6 @@ function LayerSidebar({
/>
>
- ) : (
-
-
-
- >
) : attribute.endsWith("zone") ? (
<>
diff --git a/frontend/src/pages/MapPage/RegionInfo.tsx b/frontend/src/pages/MapPage/RegionInfo.tsx
index cf29948..89ba5fa 100644
--- a/frontend/src/pages/MapPage/RegionInfo.tsx
+++ b/frontend/src/pages/MapPage/RegionInfo.tsx
@@ -2,14 +2,16 @@ import React, { useState, useCallback } from "react";
import { createPortal } from "react-dom";
import _ from "lodash";
import { List, Header, Icon, Button } from "semantic-ui-react";
+import { useTranslation } from "react-i18next";
import styles from "./styles.module.less";
export default function RegionInfo({ region, mapInfoPortal, onClose }) {
+ const { t } = useTranslation();
const content = (
<>
-
{region.properties.name || "Unnamed region"}
+
{region.properties.name || t(`MapPage.regionInfo.unnamedRegion`)}
@@ -17,7 +19,7 @@ export default function RegionInfo({ region, mapInfoPortal, onClose }) {
- Number of events
+ {t(`MapPage.regionInfo.eventNumber`)}
{region.properties.overtaking_event_count ?? 0}
diff --git a/frontend/src/pages/MapPage/RoadInfo.tsx b/frontend/src/pages/MapPage/RoadInfo.tsx
index 186ebe7..a4c988a 100644
--- a/frontend/src/pages/MapPage/RoadInfo.tsx
+++ b/frontend/src/pages/MapPage/RoadInfo.tsx
@@ -1,15 +1,26 @@
-import React, {useState, useCallback} from 'react'
-import _ from 'lodash'
-import {Segment, Menu, Header, Label, Icon, Table} 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 React, { useState, useCallback } from "react";
+import _ from "lodash";
+import {
+ Segment,
+ Menu,
+ Header,
+ Label,
+ Icon,
+ 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 {colorByDistance, borderByZone} from 'mapstyles'
+import type { Location } from "types";
+import api from "api";
+import { colorByDistance, borderByZone } from "mapstyles";
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 [direction, setDirection] = useState("forwards");
@@ -196,42 +215,51 @@ export default function RoadInfo({ clickLocation }) {
/>
+ {hasFilters && (
+
+
+
+ {t("MapPage.roadInfo.hintFiltersNotApplied")}
+
+
+ )}
+
{info?.road.zone && (
)}
- {info.road.oneway && (
+ {info?.road.oneway && (
)}
- {info.road.oneway ? null : (
-
)}
>
);
diff --git a/frontend/src/pages/MapPage/index.tsx b/frontend/src/pages/MapPage/index.tsx
index cdad0ea..a57fb44 100644
--- a/frontend/src/pages/MapPage/index.tsx
+++ b/frontend/src/pages/MapPage/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useCallback, useMemo } from "react";
+import React, { useState, useCallback, useMemo, useRef } from "react";
import _ from "lodash";
import { connect } from "react-redux";
import { Button } from "semantic-ui-react";
@@ -6,6 +6,7 @@ import { Layer, Source } from "react-map-gl";
import produce from "immer";
import classNames from "classnames";
+import api from "api";
import type { Location } from "types";
import { Page, Map } from "components";
import { useConfig } from "config";
@@ -15,6 +16,7 @@ import {
borderByZone,
reds,
isValidAttribute,
+ getRegionLayers
} from "mapstyles";
import { useMapConfig } from "reducers/mapConfig";
@@ -69,8 +71,8 @@ const getRoadsLayer = (colorAttribute, maxCount) =>
: colorAttribute.endsWith("zone")
? borderByZone()
: "#DDD";
- draft.paint["line-opacity"][3] = 12;
- draft.paint["line-opacity"][5] = 13;
+ draft.paint["line-opacity"][3] = 10;
+ draft.paint["line-opacity"][5] = 11;
});
const getEventsLayer = () => ({
@@ -193,10 +195,9 @@ function MapPage({ login }) {
node = node.parentNode;
}
- setClickLocation({longitude: e.lngLat[0], latitude: e.lngLat[1]})
const { zoom } = viewportRef.current;
- if (zoom < 10) {
+ if (zoom < 11) {
const clickedRegion = e.features?.find(
(f) => f.source === "obs" && f.sourceLayer === "obs_regions"
);
@@ -204,12 +205,9 @@ function MapPage({ login }) {
clickedRegion ? { type: "region", region: clickedRegion } : null
);
} else {
- const road = await api.get("/mapdetails/road", {
- query: {
- longitude: e.lngLat[0],
- latitude: e.lngLat[1],
- radius: 100,
- },
+ setClickLocation({longitude: e.lngLat[0], latitude: e.lngLat[1]})
+ }
+ },
[setClickLocation]
);
const onCloseRoadInfo = useCallback(() => {
@@ -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 =
login &&
(mapConfig.filters.currentUser || mapConfig.filters.dateMode !== "none");
@@ -330,6 +301,7 @@ function MapPage({ login }) {
styles.mapContainer,
banner ? styles.hasBanner : null
)}
+ ref={mapInfoPortal}
>
{layerSidebar && (
@@ -338,6 +310,7 @@ function MapPage({ login }) {
)}
diff --git a/frontend/src/pages/TracksPage.tsx b/frontend/src/pages/TracksPage.tsx
index 3942329..68e3a80 100644
--- a/frontend/src/pages/TracksPage.tsx
+++ b/frontend/src/pages/TracksPage.tsx
@@ -1,11 +1,20 @@
-import React, {useCallback} from 'react'
-import {connect} from 'react-redux'
-import {Button, Message, Item, Header, 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 React, { useCallback } from "react";
+import { connect } from "react-redux";
+import {
+ Button,
+ Message,
+ Item,
+ Header,
+ 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 {
@@ -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) {
return t.substring(0, max) + " ...";
} else {
diff --git a/frontend/src/translations/de.yaml b/frontend/src/translations/de.yaml
index db25376..c9df3a5 100644
--- a/frontend/src/translations/de.yaml
+++ b/frontend/src/translations/de.yaml
@@ -62,6 +62,9 @@ Stats:
thisMonth: Dieser Monat
thisYear: Dieses Jahr
allTime: Immer
+ topRegions: Regionen mit den meisten Messwerten
+ eventCount: Überholevents
+ regionName: Region
TracksPage:
titlePublic: Öffentliche Fahrten
@@ -201,6 +204,9 @@ MapPage:
southWest: südwestwärts
west: westwärts
northWest: nordwestwärts
+ regionInfo:
+ unnamedRegion: Unbenannte Region
+ eventNumber: Anzahl Überholevents
SettingsPage:
title: Einstellungen
diff --git a/frontend/src/translations/en.yaml b/frontend/src/translations/en.yaml
index 1246052..831d999 100644
--- a/frontend/src/translations/en.yaml
+++ b/frontend/src/translations/en.yaml
@@ -67,6 +67,9 @@ Stats:
thisMonth: This month
thisYear: This year
allTime: All time
+ topRegions: Region Leaderboard
+ eventCount: Overtaking events
+ regionName: Region
TracksPage:
titlePublic: Public tracks
@@ -206,6 +209,10 @@ MapPage:
southWest: south-west bound
west: west bound
northWest: north-west bound
+ regionInfo:
+ unnamedRegion: "unnamed region"
+ eventNumber: Number of Overtaking events
+
SettingsPage:
title: Settings
diff --git a/tile-generator/layers/obs_events/layer.sql b/tile-generator/layers/obs_events/layer.sql
index ec6c9af..4ba0f12 100644
--- a/tile-generator/layers/obs_events/layer.sql
+++ b/tile-generator/layers/obs_events/layer.sql
@@ -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,
overtaking_event.way_id::bigint as way_id
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
- WHERE
- zoom_level >= 10 AND
- ST_Transform(overtaking_event.geometry, 3857) && bbox;
+ WHERE ST_Transform(overtaking_event.geometry, 3857) && bbox
+ AND zoom_level >= 12
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);
diff --git a/tile-generator/layers/obs_roads/layer.sql b/tile-generator/layers/obs_roads/layer.sql
index 391bfee..cd12a95 100644
--- a/tile-generator/layers/obs_roads/layer.sql
+++ b/tile-generator/layers/obs_roads/layer.sql
@@ -67,7 +67,7 @@ RETURNS TABLE(
) e on (e.way_id = road.way_id and (road.directionality != 0 or e.direction_reversed = r.rev))
WHERE
- zoom_level >= 10 AND
+ zoom_level >= 11 AND
road.geometry && bbox
GROUP BY
road.name,