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 = "a049e5eb24dd"
down_revision = "a9627f63fbed"
down_revision = "99a3d2eb08f9"
branch_labels = None
depends_on = None

View file

@ -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"

View file

@ -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);"

View file

@ -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 (
<>
<Header as="h2">Top Regions</Header>
<Header as="h2">{t(`Stats.topRegions`)}</Header>
<div>
<Loader active={stats == null} />
@ -48,8 +51,8 @@ export default function Stats() {
<Table celled>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Region name</Table.HeaderCell>
<Table.HeaderCell>Event count</Table.HeaderCell>
<Table.HeaderCell>{t(`Stats.regionName`)}</Table.HeaderCell>
<Table.HeaderCell>{t(`Stats.eventCount`)}</Table.HeaderCell>
</Table.Row>
</Table.Header>

View file

@ -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],

View file

@ -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 (
<>
<Header as="h2">Most recent tracks</Header>
<Loader active={tracks === null} />
{tracks?.length === 0 ? (
<Message>
<Translate i18nKey="HomePage.noPublicTracks">
No public tracks yet. <Link to="/upload">Upload the first!</Link>
</Translate>
</Message>
) : tracks ? (
<Header as="h2">{t('HomePage.mostRecentTrack')}</Header>
<Loader active={track === null} />
{track === undefined ? (
<NoPublicTracksMessage />
) : track ? (
<Item.Group>
{tracks.map((track) => (
<TrackListItem key={track.id} track={track} />
))}
<TrackListItem track={track} />
</Item.Group>
) : null}
</>
@ -52,6 +46,7 @@ export default function HomePage() {
<Grid.Row>
<Grid.Column width={8}>
<Stats />
<RegionStats />
</Grid.Column>
<Grid.Column width={8}>
<MostRecentTrack />

View file

@ -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({
/>
</List.Item>
</>
) : (
<List.Item>
<ColorMapLegend
map={_.chunk(
colorByCount(
"obsRoads.maxCount",
mapConfig.obsRoads.maxCount,
viridisSimpleHtml
).slice(3),
2
)}
twoTicks
/>
</List.Item>
</>
) : attribute.endsWith("zone") ? (
<>
<List.Item>

View file

@ -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 = (
<>
<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}>
<Icon name="close" />
</Button>
@ -17,7 +19,7 @@ export default function RegionInfo({ region, mapInfoPortal, onClose }) {
<List>
<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.Item>
</List>

View file

@ -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 }) {
/>
</Header>
{hasFilters && (
<Message info icon>
<Icon name="info circle" small />
<Message.Content>
{t("MapPage.roadInfo.hintFiltersNotApplied")}
</Message.Content>
</Message>
)}
{info?.road.zone && (
<Label size="small" color={ZONE_COLORS[info?.road.zone]}>
{t(`general.zone.${info.road.zone}`)}
</Label>
)}
{info.road.oneway && (
{info?.road.oneway && (
<Label size="small" color="blue">
<Icon name="long arrow alternate right" fitted />{" "}
{t("MapPage.roadInfo.oneway")}
</Label>
)}
{info.road.oneway ? null : (
<Menu size="tiny" pointing>
{info?.road.oneway ? null : (
<Menu size="tiny" fluid secondary>
<Menu.Item header>{t("MapPage.roadInfo.direction")}</Menu.Item>
<Menu.Item
name="forwards"
active={direction === "forwards"}
onClick={onClickDirection}
>
{getCardinalDirection(t, info.forwards?.bearing)}
{getCardinalDirection(t, info?.forwards?.bearing)}
</Menu.Item>
<Menu.Item
name="backwards"
active={direction === "backwards"}
onClick={onClickDirection}
>
{getCardinalDirection(t, info.backwards?.bearing)}
{getCardinalDirection(t, info?.backwards?.bearing)}
</Menu.Item>
</Menu>
)}
{info[direction] && <RoadStatsTable data={info[direction]} />}
{info?.[direction] && <RoadStatsTable data={info[direction]} />}
{info[direction]?.distanceOvertaker?.histogram && (
{info?.[direction]?.distanceOvertaker?.histogram && (
<>
<Header as="h5">
{t("MapPage.roadInfo.overtakerDistanceDistribution")}
@ -246,7 +274,7 @@ export default function RoadInfo({ clickLocation }) {
return (
<>
{info.road && (
{info?.road && (
<Source id="highlight" type="geojson" data={info.road.geometry}>
<Layer
id="route"
@ -279,11 +307,10 @@ export default function RoadInfo({ clickLocation }) {
</Source>
)}
{content && mapInfoPortal && (
createPortal(
{content && (
<div className={styles.mapInfoBox}>
{content}
</div>, mapInfoPortal))}
<Segment loading={loading}>{content}</Segment>
</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 { 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 && (
<div className={styles.mapSidebar}>
@ -338,6 +310,7 @@ function MapPage({ login }) {
)}
<div className={styles.map}>
<Map viewportFromUrl onClick={onClick} onViewportChange={onViewportChange} hasToolbar>
<div className={styles.mapToolbar}>
<Button
style={{
position: "absolute",
@ -355,12 +328,12 @@ function MapPage({ login }) {
<Layer key={layer.id} {...layer} />
))}
</Source>
{details?.type === "road" && details?.road?.road && (
<RoadInfo
{...{ clickLocation, hasFilters, onClose: onCloseRoadInfo }}
/>
)}
{details?.type === "region" && details?.region && (
<RegionInfo
@ -369,8 +342,6 @@ function MapPage({ login }) {
onClose={onCloseDetails}
/>
)}
<RoadInfo {...{ clickLocation }} />
</Map>
</div>
</div>

View file

@ -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 {

View file

@ -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

View file

@ -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

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,
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);

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))
WHERE
zoom_level >= 10 AND
zoom_level >= 11 AND
road.geometry && bbox
GROUP BY
road.name,