Make it work (fix rebase issues; add translations)
This commit is contained in:
parent
c7970d9382
commit
30fb29e437
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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);"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue