diff --git a/frontend/src/pages/MapPage/LayerSidebar.tsx b/frontend/src/pages/MapPage/LayerSidebar.tsx
index 46167b2..0e4861f 100644
--- a/frontend/src/pages/MapPage/LayerSidebar.tsx
+++ b/frontend/src/pages/MapPage/LayerSidebar.tsx
@@ -1,53 +1,66 @@
-import React from 'react'
-import _ from 'lodash'
-import {connect} from 'react-redux'
-import {List, Select, Input, Divider, Label, Checkbox, Header} from 'semantic-ui-react'
+import React from "react";
+import _ from "lodash";
+import { connect } from "react-redux";
+import {
+ List,
+ Select,
+ Input,
+ Divider,
+ Label,
+ Checkbox,
+ Header,
+} from "semantic-ui-react";
+import { useTranslation } from "react-i18next";
import {
MapConfig,
setMapConfigFlag as setMapConfigFlagAction,
initialState as defaultMapConfig,
-} from 'reducers/mapConfig'
-import {colorByDistance, colorByCount, viridisSimpleHtml} from 'mapstyles'
-import {ColorMapLegend, DiscreteColorMapLegend} from 'components'
+} from "reducers/mapConfig";
+import { colorByDistance, colorByCount, viridisSimpleHtml } from "mapstyles";
+import { ColorMapLegend, DiscreteColorMapLegend } from "components";
-const BASEMAP_STYLE_OPTIONS = [
- {value: 'positron', key: 'positron', text: 'Positron'},
- {value: 'bright', key: 'bright', text: 'OSM Bright'},
-]
+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",
+ "distance_overtaker_median",
+ "overtaking_event_count",
+ "usage_count",
+ "zone",
+];
function LayerSidebar({
mapConfig,
setMapConfigFlag,
}: {
- mapConfig: MapConfig
- setMapConfigFlag: (flag: string, value: unknown) => void
+ mapConfig: MapConfig;
+ setMapConfigFlag: (flag: string, value: unknown) => void;
}) {
+ const { t } = useTranslation();
const {
- baseMap: {style},
- obsRoads: {show: showRoads, showUntagged, attribute, maxCount},
- obsEvents: {show: showEvents},
- } = mapConfig
+ baseMap: { style },
+ obsRoads: { show: showRoads, showUntagged, attribute, maxCount },
+ obsEvents: { show: showEvents },
+ } = mapConfig;
return (
- Basemap Style
+ {t("MapPage.sidebar.baseMap.style.label")}
@@ -56,12 +69,12 @@ function LayerSidebar({
toggle
size="small"
id="obsRoads.show"
- style={{float: 'right'}}
+ style={{ float: "right" }}
checked={showRoads}
- onChange={() => setMapConfigFlag('obsRoads.show', !showRoads)}
+ onChange={() => setMapConfigFlag("obsRoads.show", !showRoads)}
/>
{showRoads && (
@@ -69,49 +82,80 @@ function LayerSidebar({
setMapConfigFlag('obsRoads.showUntagged', !showUntagged)}
- label="Include roads without data"
+ onChange={() =>
+ setMapConfigFlag("obsRoads.showUntagged", !showUntagged)
+ }
+ label={t("MapPage.sidebar.obsRoads.showUntagged.label")}
/>
- Color based on
+
+ {t("MapPage.sidebar.obsRoads.attribute.label")}
+
- {attribute.endsWith('_count') ? (
- <>
+ {attribute.endsWith("_count") ? (
+ <>
+
+
+ {t("MapPage.sidebar.obsRoads.maxCount.label")}
+
+
+ setMapConfigFlag("obsRoads.maxCount", value)
+ }
+ />
+
+
+
+
+ >
+ ) : (
- Maximum value
- setMapConfigFlag('obsRoads.maxCount', value)}
+
-
-
- >
) :
attribute.endsWith('zone') ? (
<>
-
-
+
+
>
) :
(
<>
- Urban
+ {_.startCase(t("general.urban"))}
- Rural
+ {_.startCase(t("general.rural"))}
>
@@ -124,29 +168,29 @@ function LayerSidebar({
toggle
size="small"
id="obsEvents.show"
- style={{float: 'right'}}
+ style={{ float: "right" }}
checked={showEvents}
- onChange={() => setMapConfigFlag('obsEvents.show', !showEvents)}
+ onChange={() => setMapConfigFlag("obsEvents.show", !showEvents)}
/>
{showEvents && (
<>
- Urban
-
-
+ {_.startCase(t('general.urban'))}
+
+
- Rural
-
+ {_.startCase(t('general.rural'))}
+
>
)}
- )
+ );
}
export default connect(
@@ -158,6 +202,6 @@ export default connect(
//
),
}),
- {setMapConfigFlag: setMapConfigFlagAction}
+ { setMapConfigFlag: setMapConfigFlagAction }
//
-)(LayerSidebar)
+)(LayerSidebar);
diff --git a/frontend/src/pages/MapPage/RoadInfo.tsx b/frontend/src/pages/MapPage/RoadInfo.tsx
index 871279a..fb8938c 100644
--- a/frontend/src/pages/MapPage/RoadInfo.tsx
+++ b/frontend/src/pages/MapPage/RoadInfo.tsx
@@ -1,83 +1,95 @@
-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 } 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 styles from './styles.module.less'
+import styles from "./styles.module.less";
function selectFromColorMap(colormap, value) {
- let last = null
+ let last = null;
for (let i = 0; i < colormap.length; i += 2) {
if (colormap[i + 1] > value) {
- return colormap[i]
+ return colormap[i];
}
}
- return colormap[colormap.length - 1]
+ return colormap[colormap.length - 1];
}
-const UNITS = {distanceOvertaker: 'm', distanceStationary: 'm', speed: 'km/h'}
-const LABELS = {
- distanceOvertaker: 'Left',
- distanceStationary: 'Right',
- speed: 'Speed',
- count: 'No. of Measurements',
- min: 'Minimum',
- median: 'Median',
- max: 'Maximum',
- mean: 'Average',
-}
-const ZONE_COLORS = {urban: 'blue', rural: 'cyan', motorway: 'purple'}
-const CARDINAL_DIRECTIONS = ['north', 'north-east', 'east', 'south-east', 'south', 'south-west', 'west', 'north-west']
-const getCardinalDirection = (bearing) =>
- bearing == null
- ? 'unknown'
- : CARDINAL_DIRECTIONS[
- Math.floor(((bearing / 360.0) * CARDINAL_DIRECTIONS.length + 0.5) % CARDINAL_DIRECTIONS.length)
- ] + ' bound'
+const UNITS = {
+ distanceOvertaker: "m",
+ distanceStationary: "m",
+ speed: "km/h",
+};
+const ZONE_COLORS = { urban: "blue", rural: "cyan", motorway: "purple" };
+const CARDINAL_DIRECTIONS = [
+ "north",
+ "northEast",
+ "east",
+ "southEast",
+ "south",
+ "southWest",
+ "west",
+ "northWest",
+];
+const getCardinalDirection = (t, bearing) => {
+ if (bearing == null) {
+ return t("MapPage.roadInfo.cardinalDirections.unknown");
+ } else {
+ const n = CARDINAL_DIRECTIONS.length;
+ const i = Math.floor(((bearing / 360.0) * n + 0.5) % n);
+ const name = CARDINAL_DIRECTIONS[i];
+ return t(`MapPage.roadInfo.cardinalDirections.${name}`);
+ }
+};
-function RoadStatsTable({data}) {
+function RoadStatsTable({ data }) {
+ const { t } = useTranslation();
return (
- {['distanceOvertaker', 'distanceStationary', 'speed'].map((prop) => (
+ {["distanceOvertaker", "distanceStationary", "speed"].map((prop) => (
- {LABELS[prop]}
+ {t(`MapPage.roadInfo.${prop}`)}
))}
- {['count', 'min', 'median', 'max', 'mean'].map((stat) => (
+ {["count", "min", "median", "max", "mean"].map((stat) => (
- {LABELS[stat]}
- {['distanceOvertaker', 'distanceStationary', 'speed'].map((prop) => (
-
- {(data[prop]?.statistics?.[stat] * (prop === `speed` && stat != 'count' ? 3.6 : 1)).toFixed(
- stat === 'count' ? 0 : 2
- )}
- {stat !== 'count' && ` ${UNITS[prop]}`}
-
- ))}
+ {t(`MapPage.roadInfo.${stat}`)}
+ {["distanceOvertaker", "distanceStationary", "speed"].map(
+ (prop) => (
+
+ {(
+ data[prop]?.statistics?.[stat] *
+ (prop === `speed` && stat != "count" ? 3.6 : 1)
+ ).toFixed(stat === "count" ? 0 : 2)}
+ {stat !== "count" && ` ${UNITS[prop]}`}
+
+ )
+ )}
))}
- )
+ );
}
-function HistogramChart({bins, counts, zone}) {
+function HistogramChart({ bins, counts, zone }) {
const diff = bins[1] - bins[0]
- const colortype = zone=="rural" ? 3:5;
+ const colortype = zone === "rural" ? 3 : 5;
const data = _.zip(
bins.slice(0, bins.length - 1).map((v) => v + diff / 2),
counts
@@ -88,19 +100,19 @@ function HistogramChart({bins, counts, zone}) {
return (
`${Math.round(v * 100)} cm`},
+ type: "value",
+ axisLabel: { formatter: (v) => `${Math.round(v * 100)} cm` },
min: 0,
max: 2.5,
},
yAxis: {},
series: [
{
- type: 'bar',
+ type: "bar",
data,
barMaxWidth: 20,
@@ -108,20 +120,21 @@ function HistogramChart({bins, counts, zone}) {
],
}}
/>
- )
+ );
}
-export default function RoadInfo({clickLocation}) {
- const [direction, setDirection] = useState('forwards')
+export default function RoadInfo({ clickLocation }) {
+ const { t } = useTranslation();
+ const [direction, setDirection] = useState("forwards");
const onClickDirection = useCallback(
- (e, {name}) => {
- e.preventDefault()
- e.stopPropagation()
- setDirection(name)
+ (e, { name }) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setDirection(name);
},
[setDirection]
- )
+ );
const info = useObservable(
(_$, inputs$) =>
@@ -132,7 +145,7 @@ export default function RoadInfo({clickLocation}) {
? concat(
of(null),
from(
- api.get('/mapdetails/road', {
+ api.get("/mapdetails/road", {
query: {
...location,
radius: 100,
@@ -145,43 +158,60 @@ export default function RoadInfo({clickLocation}) {
),
null,
[clickLocation]
- )
+ );
if (!clickLocation) {
- return null
+ return null;
}
- const loading = info == null
+ const loading = info == null;
- const offsetDirection = info?.road?.oneway ? 0 : direction === 'forwards' ? 1 : -1 // TODO: change based on left-hand/right-hand traffic
+ const offsetDirection = info?.road?.oneway
+ ? 0
+ : direction === "forwards"
+ ? 1
+ : -1; // TODO: change based on left-hand/right-hand traffic
const content =
!loading && !info.road ? (
- 'No road found.'
+ "No road found."
) : (
<>
- {loading ? '...' : info?.road.name || 'Unnamed way'}
+
+ {loading
+ ? "..."
+ : info?.road.name || t("MapPage.roadInfo.unnamedWay")}
+
{info?.road.zone && (
)}
{info?.road.oneway && (
)}
{info?.road.oneway ? null : (
)}
@@ -190,12 +220,16 @@ export default function RoadInfo({clickLocation}) {
{info?.[direction]?.distanceOvertaker?.histogram && (
<>
- Overtaker distance distribution
-
+
+ {t("MapPage.roadInfo.overtakerDistanceDistribution")}
+
+
>
)}
>
- )
+ );
return (
<>
@@ -205,14 +239,22 @@ export default function RoadInfo({clickLocation}) {
id="route"
type="line"
paint={{
- 'line-width': ['interpolate', ['linear'], ['zoom'], 14, 6, 17, 12],
- 'line-color': '#18FFFF',
- 'line-opacity': 0.5,
+ "line-width": [
+ "interpolate",
+ ["linear"],
+ ["zoom"],
+ 14,
+ 6,
+ 17,
+ 12,
+ ],
+ "line-color": "#18FFFF",
+ "line-opacity": 0.5,
...{
- 'line-offset': [
- 'interpolate',
- ['exponential', 1.5],
- ['zoom'],
+ "line-offset": [
+ "interpolate",
+ ["exponential", 1.5],
+ ["zoom"],
12,
offsetDirection,
19,
@@ -230,5 +272,5 @@ export default function RoadInfo({clickLocation}) {
)}
>
- )
+ );
}
diff --git a/frontend/src/translations/de.yaml b/frontend/src/translations/de.yaml
index 4137cee..5823817 100644
--- a/frontend/src/translations/de.yaml
+++ b/frontend/src/translations/de.yaml
@@ -1,6 +1,8 @@
general:
loading: Lädt
unnamedTrack: Unbenannte Fahrt
+ urban: innerorts
+ rural: außerorts
public: Öffentlich
private: Privat
show: Anzeigen
@@ -116,3 +118,64 @@ NotFoundPage:
title: Seite nicht gefunden
description: Den Fehler kennst du sicher...
goBack: Zurückgehen
+
+
+MapPage:
+ sidebar:
+ baseMap:
+ style:
+ label: Stil der Basiskarte
+ positron: Positron
+ bright: OSM Bright
+
+ obsRoads:
+ title: Straßenabschnitte
+ showUntagged:
+ label: Abschnitte ohne Daten anzeigen
+ attribute:
+ label: Einfärben nach...
+ distance_overtaker_mean: Durchschnitt Überholabstand
+ distance_overtaker_min: Minimum Überholabstand
+ distance_overtaker_max: Maximum Überholabstand
+ distance_overtaker_median: Median Überholabstand
+ overtaking_event_count: Anzahl Überholvorgänge
+ usage_count: Anzahl Befahrungen
+ zone: Überholabstands-Zone
+ maxCount:
+ label: Maximalwert für Farbskala
+
+ obsEvents:
+ title: Überholvorgänge
+
+
+ roadInfo:
+ unnamedWay: Unbenannter Weg
+ oneway: Einbahnstraße
+ direction: Richtung
+
+ zone:
+ rural: außerorts
+ urban: innerorts
+ motorway: Autobahn
+
+ distanceOvertaker: Links
+ distanceStationary: Rechts
+ speed: Geschwindigkeit
+ count: Anzahl Messungen
+ min: Minimum
+ median: Median
+ max: Maximum
+ mean: Durchschnitt
+
+ overtakerDistanceDistribution: Verteilung der Überholabstände
+
+ cardinalDirections:
+ unknown: unbekannt
+ north: nordwärts
+ northEast: nordostwärts
+ east: ostwärts
+ southEast: südostwärts
+ south: südwärts
+ southWest: südwestwärts
+ west: westwärts
+ northWest: nordwestwärts
diff --git a/frontend/src/translations/en.yaml b/frontend/src/translations/en.yaml
index b3137fe..16b2dae 100644
--- a/frontend/src/translations/en.yaml
+++ b/frontend/src/translations/en.yaml
@@ -5,6 +5,8 @@ locales:
general:
loading: Loading
unnamedTrack: Unnamed track
+ urban: urban
+ rural: rural
public: Public
private: Private
show: Show
@@ -121,3 +123,63 @@ NotFoundPage:
title: Page not found
description: You know what that means...
goBack: Go back
+
+
+MapPage:
+ sidebar:
+ baseMap:
+ style:
+ label: Basemap Style
+ positron: Positron
+ bright: OSM Bright
+
+ obsRoads:
+ title: Road segments
+ showUntagged:
+ label: Include roads without data
+ attribute:
+ label: Color based on...
+ distance_overtaker_mean: Overtaker distance mean
+ distance_overtaker_min: Overtaker distance minimum
+ distance_overtaker_max: Overtaker distance maximum
+ distance_overtaker_median: Overtaker distance median
+ overtaking_event_count: Event count
+ usage_count: Usage count
+ zone: Overtaking distance zone
+ maxCount:
+ label: Maximum value for color scale
+
+ obsEvents:
+ title: Event points
+
+ roadInfo:
+ unnamedWay: Unnamed way
+ oneway: oneway
+ direction: Direction
+
+ zone:
+ rural: Rural
+ urban: Urban
+ motorway: Motorway
+
+ distanceOvertaker: Left
+ distanceStationary: Right
+ speed: Speed
+ count: No. of Measurements
+ min: Minimum
+ median: Median
+ max: Maximum
+ mean: Average
+
+ overtakerDistanceDistribution: Overtaker distance distribution
+
+ cardinalDirections:
+ unknown: unknown
+ north: north bound
+ northEast: north-east bound
+ east: east bound
+ southEast: south-east bound
+ south: south bound
+ southWest: south-west bound
+ west: west bound
+ northWest: north-west bound