Translate MapPage
This commit is contained in:
parent
a977e2d1c3
commit
248f8b4a6f
|
@ -1,53 +1,66 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import _ from 'lodash'
|
import _ from "lodash";
|
||||||
import {connect} from 'react-redux'
|
import { connect } from "react-redux";
|
||||||
import {List, Select, Input, Divider, Label, Checkbox, Header} from 'semantic-ui-react'
|
import {
|
||||||
|
List,
|
||||||
|
Select,
|
||||||
|
Input,
|
||||||
|
Divider,
|
||||||
|
Label,
|
||||||
|
Checkbox,
|
||||||
|
Header,
|
||||||
|
} from "semantic-ui-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MapConfig,
|
MapConfig,
|
||||||
setMapConfigFlag as setMapConfigFlagAction,
|
setMapConfigFlag as setMapConfigFlagAction,
|
||||||
initialState as defaultMapConfig,
|
initialState as defaultMapConfig,
|
||||||
} from 'reducers/mapConfig'
|
} from "reducers/mapConfig";
|
||||||
import {colorByDistance, colorByCount, viridisSimpleHtml} from 'mapstyles'
|
import { colorByDistance, colorByCount, viridisSimpleHtml } from "mapstyles";
|
||||||
import {ColorMapLegend, DiscreteColorMapLegend} from 'components'
|
import { ColorMapLegend, DiscreteColorMapLegend } from "components";
|
||||||
|
|
||||||
const BASEMAP_STYLE_OPTIONS = [
|
const BASEMAP_STYLE_OPTIONS = ["positron", "bright"];
|
||||||
{value: 'positron', key: 'positron', text: 'Positron'},
|
|
||||||
{value: 'bright', key: 'bright', text: 'OSM Bright'},
|
|
||||||
]
|
|
||||||
|
|
||||||
const ROAD_ATTRIBUTE_OPTIONS = [
|
const ROAD_ATTRIBUTE_OPTIONS = [
|
||||||
{value: 'distance_overtaker_mean', key: 'distance_overtaker_mean', text: 'Overtaker distance mean'},
|
"distance_overtaker_mean",
|
||||||
{value: 'distance_overtaker_min', key: 'distance_overtaker_min', text: 'Overtaker distance minimum'},
|
"distance_overtaker_min",
|
||||||
{value: 'distance_overtaker_max', key: 'distance_overtaker_max', text: 'Overtaker distance maximum'},
|
"distance_overtaker_max",
|
||||||
{value: 'distance_overtaker_median', key: 'distance_overtaker_median', text: 'Overtaker distance median'},
|
"distance_overtaker_median",
|
||||||
{value: 'overtaking_event_count', key: 'overtaking_event_count', text: 'Event count'},
|
"overtaking_event_count",
|
||||||
{value: 'usage_count', key: 'usage_count', text: 'Usage count'},
|
"usage_count",
|
||||||
{value: 'zone', key: 'zone', text: 'Overtaking distance zone'}
|
"zone",
|
||||||
]
|
];
|
||||||
|
|
||||||
function LayerSidebar({
|
function LayerSidebar({
|
||||||
mapConfig,
|
mapConfig,
|
||||||
setMapConfigFlag,
|
setMapConfigFlag,
|
||||||
}: {
|
}: {
|
||||||
mapConfig: MapConfig
|
mapConfig: MapConfig;
|
||||||
setMapConfigFlag: (flag: string, value: unknown) => void
|
setMapConfigFlag: (flag: string, value: unknown) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
baseMap: {style},
|
baseMap: { style },
|
||||||
obsRoads: {show: showRoads, showUntagged, attribute, maxCount},
|
obsRoads: { show: showRoads, showUntagged, attribute, maxCount },
|
||||||
obsEvents: {show: showEvents},
|
obsEvents: { show: showEvents },
|
||||||
} = mapConfig
|
} = mapConfig;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<List relaxed>
|
<List relaxed>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Basemap Style</List.Header>
|
<List.Header>{t("MapPage.sidebar.baseMap.style.label")}</List.Header>
|
||||||
<Select
|
<Select
|
||||||
options={BASEMAP_STYLE_OPTIONS}
|
options={BASEMAP_STYLE_OPTIONS.map((value) => ({
|
||||||
|
value,
|
||||||
|
key: value,
|
||||||
|
text: t(`MapPage.sidebar.baseMap.style.${value}`),
|
||||||
|
}))}
|
||||||
value={style}
|
value={style}
|
||||||
onChange={(_e, {value}) => setMapConfigFlag('baseMap.style', value)}
|
onChange={(_e, { value }) =>
|
||||||
|
setMapConfigFlag("baseMap.style", value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
@ -56,12 +69,12 @@ function LayerSidebar({
|
||||||
toggle
|
toggle
|
||||||
size="small"
|
size="small"
|
||||||
id="obsRoads.show"
|
id="obsRoads.show"
|
||||||
style={{float: 'right'}}
|
style={{ float: "right" }}
|
||||||
checked={showRoads}
|
checked={showRoads}
|
||||||
onChange={() => setMapConfigFlag('obsRoads.show', !showRoads)}
|
onChange={() => setMapConfigFlag("obsRoads.show", !showRoads)}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="obsRoads.show">
|
<label htmlFor="obsRoads.show">
|
||||||
<Header as="h4">Road segments</Header>
|
<Header as="h4">{t("MapPage.sidebar.obsRoads.title")}</Header>
|
||||||
</label>
|
</label>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
{showRoads && (
|
{showRoads && (
|
||||||
|
@ -69,49 +82,80 @@ function LayerSidebar({
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={showUntagged}
|
checked={showUntagged}
|
||||||
onChange={() => setMapConfigFlag('obsRoads.showUntagged', !showUntagged)}
|
onChange={() =>
|
||||||
label="Include roads without data"
|
setMapConfigFlag("obsRoads.showUntagged", !showUntagged)
|
||||||
|
}
|
||||||
|
label={t("MapPage.sidebar.obsRoads.showUntagged.label")}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Color based on</List.Header>
|
<List.Header>
|
||||||
|
{t("MapPage.sidebar.obsRoads.attribute.label")}
|
||||||
|
</List.Header>
|
||||||
<Select
|
<Select
|
||||||
fluid
|
fluid
|
||||||
options={ROAD_ATTRIBUTE_OPTIONS}
|
options={ROAD_ATTRIBUTE_OPTIONS.map((value) => ({
|
||||||
|
value,
|
||||||
|
key: value,
|
||||||
|
text: t(`MapPage.sidebar.obsRoads.attribute.${value}`),
|
||||||
|
}))}
|
||||||
value={attribute}
|
value={attribute}
|
||||||
onChange={(_e, {value}) => setMapConfigFlag('obsRoads.attribute', value)}
|
onChange={(_e, { value }) =>
|
||||||
|
setMapConfigFlag("obsRoads.attribute", value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
{attribute.endsWith('_count') ? (
|
{attribute.endsWith("_count") ? (
|
||||||
<>
|
<>
|
||||||
|
<List.Item>
|
||||||
|
<List.Header>
|
||||||
|
{t("MapPage.sidebar.obsRoads.maxCount.label")}
|
||||||
|
</List.Header>
|
||||||
|
<Input
|
||||||
|
fluid
|
||||||
|
type="number"
|
||||||
|
value={maxCount}
|
||||||
|
onChange={(_e, { value }) =>
|
||||||
|
setMapConfigFlag("obsRoads.maxCount", value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
<ColorMapLegend
|
||||||
|
map={_.chunk(
|
||||||
|
colorByCount(
|
||||||
|
"obsRoads.maxCount",
|
||||||
|
mapConfig.obsRoads.maxCount,
|
||||||
|
viridisSimpleHtml
|
||||||
|
).slice(3),
|
||||||
|
2
|
||||||
|
)}
|
||||||
|
twoTicks
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Maximum value</List.Header>
|
<DiscreteColorMapLegend
|
||||||
<Input
|
map={colorByDistance("distance_overtaker")[3].slice(2)}
|
||||||
fluid
|
|
||||||
type="number"
|
|
||||||
value={maxCount}
|
|
||||||
onChange={(_e, {value}) => setMapConfigFlag('obsRoads.maxCount', value)}
|
|
||||||
/>
|
/>
|
||||||
</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>
|
||||||
<Label size="small" style={{background: "blue",color:"white"}}>urban (1.5 m)</Label>
|
<Label size="small" style={{background: "blue",color:"white"}}>{t("general.urban")} (1.5 m)</Label>
|
||||||
<Label size="small" style={{background: "cyan", color:"black"}}>rural (2 m)</Label>
|
<Label size="small" style={{background: "cyan", color:"black"}}>{t("general.rural")}(2 m)</Label>
|
||||||
</List.Item></>
|
</List.Item></>
|
||||||
) :
|
) :
|
||||||
(
|
(
|
||||||
<>
|
<>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Urban</List.Header>
|
<List.Header>{_.startCase(t("general.urban"))}</List.Header>
|
||||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
|
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Rural</List.Header>
|
<List.Header>{_.startCase(t("general.rural"))}</List.Header>
|
||||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
|
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
|
||||||
</List.Item>
|
</List.Item>
|
||||||
</>
|
</>
|
||||||
|
@ -124,29 +168,29 @@ function LayerSidebar({
|
||||||
toggle
|
toggle
|
||||||
size="small"
|
size="small"
|
||||||
id="obsEvents.show"
|
id="obsEvents.show"
|
||||||
style={{float: 'right'}}
|
style={{ float: "right" }}
|
||||||
checked={showEvents}
|
checked={showEvents}
|
||||||
onChange={() => setMapConfigFlag('obsEvents.show', !showEvents)}
|
onChange={() => setMapConfigFlag("obsEvents.show", !showEvents)}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="obsEvents.show">
|
<label htmlFor="obsEvents.show">
|
||||||
<Header as="h4">Event points</Header>
|
<Header as="h4">{t("MapPage.sidebar.obsEvents.title")}</Header>
|
||||||
</label>
|
</label>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
{showEvents && (
|
{showEvents && (
|
||||||
<>
|
<>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Urban</List.Header>
|
<List.Header>{_.startCase(t('general.urban'))}</List.Header>
|
||||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
|
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Rural</List.Header>
|
<List.Header>{_.startCase(t('general.rural'))}</List.Header>
|
||||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
|
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
|
||||||
</List.Item>
|
</List.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -158,6 +202,6 @@ export default connect(
|
||||||
//
|
//
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
{setMapConfigFlag: setMapConfigFlagAction}
|
{ setMapConfigFlag: setMapConfigFlagAction }
|
||||||
//
|
//
|
||||||
)(LayerSidebar)
|
)(LayerSidebar);
|
||||||
|
|
|
@ -1,83 +1,95 @@
|
||||||
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 { Segment, Menu, Header, Label, Icon, Table } from "semantic-ui-react";
|
||||||
import {Layer, Source} from 'react-map-gl'
|
import { Layer, Source } from "react-map-gl";
|
||||||
import {of, from, concat} from 'rxjs'
|
import { of, from, concat } from "rxjs";
|
||||||
import {useObservable} from 'rxjs-hooks'
|
import { useObservable } from "rxjs-hooks";
|
||||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators'
|
import { switchMap, distinctUntilChanged } from "rxjs/operators";
|
||||||
import {Chart} from 'components'
|
import { Chart } from "components";
|
||||||
import {pairwise} from 'utils'
|
import { pairwise } from "utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import api from 'api'
|
import api from 'api'
|
||||||
import {colorByDistance, borderByZone} from 'mapstyles'
|
import {colorByDistance, borderByZone} from 'mapstyles'
|
||||||
|
|
||||||
import styles from './styles.module.less'
|
import styles from "./styles.module.less";
|
||||||
|
|
||||||
function selectFromColorMap(colormap, value) {
|
function selectFromColorMap(colormap, value) {
|
||||||
let last = null
|
let last = null;
|
||||||
for (let i = 0; i < colormap.length; i += 2) {
|
for (let i = 0; i < colormap.length; i += 2) {
|
||||||
if (colormap[i + 1] > value) {
|
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 UNITS = {
|
||||||
const LABELS = {
|
distanceOvertaker: "m",
|
||||||
distanceOvertaker: 'Left',
|
distanceStationary: "m",
|
||||||
distanceStationary: 'Right',
|
speed: "km/h",
|
||||||
speed: 'Speed',
|
};
|
||||||
count: 'No. of Measurements',
|
const ZONE_COLORS = { urban: "blue", rural: "cyan", motorway: "purple" };
|
||||||
min: 'Minimum',
|
const CARDINAL_DIRECTIONS = [
|
||||||
median: 'Median',
|
"north",
|
||||||
max: 'Maximum',
|
"northEast",
|
||||||
mean: 'Average',
|
"east",
|
||||||
}
|
"southEast",
|
||||||
const ZONE_COLORS = {urban: 'blue', rural: 'cyan', motorway: 'purple'}
|
"south",
|
||||||
const CARDINAL_DIRECTIONS = ['north', 'north-east', 'east', 'south-east', 'south', 'south-west', 'west', 'north-west']
|
"southWest",
|
||||||
const getCardinalDirection = (bearing) =>
|
"west",
|
||||||
bearing == null
|
"northWest",
|
||||||
? 'unknown'
|
];
|
||||||
: CARDINAL_DIRECTIONS[
|
const getCardinalDirection = (t, bearing) => {
|
||||||
Math.floor(((bearing / 360.0) * CARDINAL_DIRECTIONS.length + 0.5) % CARDINAL_DIRECTIONS.length)
|
if (bearing == null) {
|
||||||
] + ' bound'
|
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 (
|
return (
|
||||||
<Table size="small" compact>
|
<Table size="small" compact>
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
<Table.HeaderCell textAlign="right"></Table.HeaderCell>
|
<Table.HeaderCell textAlign="right"></Table.HeaderCell>
|
||||||
{['distanceOvertaker', 'distanceStationary', 'speed'].map((prop) => (
|
{["distanceOvertaker", "distanceStationary", "speed"].map((prop) => (
|
||||||
<Table.HeaderCell key={prop} textAlign="right">
|
<Table.HeaderCell key={prop} textAlign="right">
|
||||||
{LABELS[prop]}
|
{t(`MapPage.roadInfo.${prop}`)}
|
||||||
</Table.HeaderCell>
|
</Table.HeaderCell>
|
||||||
))}
|
))}
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{['count', 'min', 'median', 'max', 'mean'].map((stat) => (
|
{["count", "min", "median", "max", "mean"].map((stat) => (
|
||||||
<Table.Row key={stat}>
|
<Table.Row key={stat}>
|
||||||
<Table.Cell>{LABELS[stat]}</Table.Cell>
|
<Table.Cell> {t(`MapPage.roadInfo.${stat}`)}</Table.Cell>
|
||||||
{['distanceOvertaker', 'distanceStationary', 'speed'].map((prop) => (
|
{["distanceOvertaker", "distanceStationary", "speed"].map(
|
||||||
<Table.Cell key={prop} textAlign="right">
|
(prop) => (
|
||||||
{(data[prop]?.statistics?.[stat] * (prop === `speed` && stat != 'count' ? 3.6 : 1)).toFixed(
|
<Table.Cell key={prop} textAlign="right">
|
||||||
stat === 'count' ? 0 : 2
|
{(
|
||||||
)}
|
data[prop]?.statistics?.[stat] *
|
||||||
{stat !== 'count' && ` ${UNITS[prop]}`}
|
(prop === `speed` && stat != "count" ? 3.6 : 1)
|
||||||
</Table.Cell>
|
).toFixed(stat === "count" ? 0 : 2)}
|
||||||
))}
|
{stat !== "count" && ` ${UNITS[prop]}`}
|
||||||
|
</Table.Cell>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
))}
|
))}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table>
|
</Table>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function HistogramChart({bins, counts, zone}) {
|
function HistogramChart({ bins, counts, zone }) {
|
||||||
const diff = bins[1] - bins[0]
|
const diff = bins[1] - bins[0]
|
||||||
const colortype = zone=="rural" ? 3:5;
|
const colortype = zone === "rural" ? 3 : 5;
|
||||||
const data = _.zip(
|
const data = _.zip(
|
||||||
bins.slice(0, bins.length - 1).map((v) => v + diff / 2),
|
bins.slice(0, bins.length - 1).map((v) => v + diff / 2),
|
||||||
counts
|
counts
|
||||||
|
@ -88,19 +100,19 @@ function HistogramChart({bins, counts, zone}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Chart
|
<Chart
|
||||||
style={{height: 240}}
|
style={{ height: 240 }}
|
||||||
option={{
|
option={{
|
||||||
grid: {top: 30, bottom: 30, right: 30, left: 30},
|
grid: { top: 30, bottom: 30, right: 30, left: 30 },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'value',
|
type: "value",
|
||||||
axisLabel: {formatter: (v) => `${Math.round(v * 100)} cm`},
|
axisLabel: { formatter: (v) => `${Math.round(v * 100)} cm` },
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 2.5,
|
max: 2.5,
|
||||||
},
|
},
|
||||||
yAxis: {},
|
yAxis: {},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
data,
|
data,
|
||||||
|
|
||||||
barMaxWidth: 20,
|
barMaxWidth: 20,
|
||||||
|
@ -108,20 +120,21 @@ function HistogramChart({bins, counts, zone}) {
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RoadInfo({clickLocation}) {
|
export default function RoadInfo({ clickLocation }) {
|
||||||
const [direction, setDirection] = useState('forwards')
|
const { t } = useTranslation();
|
||||||
|
const [direction, setDirection] = useState("forwards");
|
||||||
|
|
||||||
const onClickDirection = useCallback(
|
const onClickDirection = useCallback(
|
||||||
(e, {name}) => {
|
(e, { name }) => {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
e.stopPropagation()
|
e.stopPropagation();
|
||||||
setDirection(name)
|
setDirection(name);
|
||||||
},
|
},
|
||||||
[setDirection]
|
[setDirection]
|
||||||
)
|
);
|
||||||
|
|
||||||
const info = useObservable(
|
const info = useObservable(
|
||||||
(_$, inputs$) =>
|
(_$, inputs$) =>
|
||||||
|
@ -132,7 +145,7 @@ export default function RoadInfo({clickLocation}) {
|
||||||
? concat(
|
? concat(
|
||||||
of(null),
|
of(null),
|
||||||
from(
|
from(
|
||||||
api.get('/mapdetails/road', {
|
api.get("/mapdetails/road", {
|
||||||
query: {
|
query: {
|
||||||
...location,
|
...location,
|
||||||
radius: 100,
|
radius: 100,
|
||||||
|
@ -145,43 +158,60 @@ export default function RoadInfo({clickLocation}) {
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
[clickLocation]
|
[clickLocation]
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!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 =
|
const content =
|
||||||
!loading && !info.road ? (
|
!loading && !info.road ? (
|
||||||
'No road found.'
|
"No road found."
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Header as="h3">{loading ? '...' : info?.road.name || 'Unnamed way'}</Header>
|
<Header as="h3">
|
||||||
|
{loading
|
||||||
|
? "..."
|
||||||
|
: info?.road.name || t("MapPage.roadInfo.unnamedWay")}
|
||||||
|
</Header>
|
||||||
|
|
||||||
{info?.road.zone && (
|
{info?.road.zone && (
|
||||||
<Label size="small" color={ZONE_COLORS[info?.road.zone]}>
|
<Label size="small" color={ZONE_COLORS[info?.road.zone]}>
|
||||||
{info?.road.zone}
|
{t(`MapPage.roadInfo.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 /> oneway
|
<Icon name="long arrow alternate right" fitted />{" "}
|
||||||
|
{t("MapPage.roadInfo.oneway")}
|
||||||
</Label>
|
</Label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{info?.road.oneway ? null : (
|
{info?.road.oneway ? null : (
|
||||||
<Menu size="tiny" fluid secondary>
|
<Menu size="tiny" fluid secondary>
|
||||||
<Menu.Item header>Direction</Menu.Item>
|
<Menu.Item header>{t("MapPage.roadInfo.direction")}</Menu.Item>
|
||||||
<Menu.Item name="forwards" active={direction === 'forwards'} onClick={onClickDirection}>
|
<Menu.Item
|
||||||
{getCardinalDirection(info?.forwards?.bearing)}
|
name="forwards"
|
||||||
|
active={direction === "forwards"}
|
||||||
|
onClick={onClickDirection}
|
||||||
|
>
|
||||||
|
{getCardinalDirection(t, info?.forwards?.bearing)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item name="backwards" active={direction === 'backwards'} onClick={onClickDirection}>
|
<Menu.Item
|
||||||
{getCardinalDirection(info?.backwards?.bearing)}
|
name="backwards"
|
||||||
|
active={direction === "backwards"}
|
||||||
|
onClick={onClickDirection}
|
||||||
|
>
|
||||||
|
{getCardinalDirection(t, info?.backwards?.bearing)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
)}
|
||||||
|
@ -190,12 +220,16 @@ export default function RoadInfo({clickLocation}) {
|
||||||
|
|
||||||
{info?.[direction]?.distanceOvertaker?.histogram && (
|
{info?.[direction]?.distanceOvertaker?.histogram && (
|
||||||
<>
|
<>
|
||||||
<Header as="h5">Overtaker distance distribution</Header>
|
<Header as="h5">
|
||||||
<HistogramChart {...info[direction]?.distanceOvertaker?.histogram} />
|
{t("MapPage.roadInfo.overtakerDistanceDistribution")}
|
||||||
|
</Header>
|
||||||
|
<HistogramChart
|
||||||
|
{...info[direction]?.distanceOvertaker?.histogram}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -205,14 +239,22 @@ export default function RoadInfo({clickLocation}) {
|
||||||
id="route"
|
id="route"
|
||||||
type="line"
|
type="line"
|
||||||
paint={{
|
paint={{
|
||||||
'line-width': ['interpolate', ['linear'], ['zoom'], 14, 6, 17, 12],
|
"line-width": [
|
||||||
'line-color': '#18FFFF',
|
"interpolate",
|
||||||
'line-opacity': 0.5,
|
["linear"],
|
||||||
|
["zoom"],
|
||||||
|
14,
|
||||||
|
6,
|
||||||
|
17,
|
||||||
|
12,
|
||||||
|
],
|
||||||
|
"line-color": "#18FFFF",
|
||||||
|
"line-opacity": 0.5,
|
||||||
...{
|
...{
|
||||||
'line-offset': [
|
"line-offset": [
|
||||||
'interpolate',
|
"interpolate",
|
||||||
['exponential', 1.5],
|
["exponential", 1.5],
|
||||||
['zoom'],
|
["zoom"],
|
||||||
12,
|
12,
|
||||||
offsetDirection,
|
offsetDirection,
|
||||||
19,
|
19,
|
||||||
|
@ -230,5 +272,5 @@ export default function RoadInfo({clickLocation}) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
general:
|
general:
|
||||||
loading: Lädt
|
loading: Lädt
|
||||||
unnamedTrack: Unbenannte Fahrt
|
unnamedTrack: Unbenannte Fahrt
|
||||||
|
urban: innerorts
|
||||||
|
rural: außerorts
|
||||||
public: Öffentlich
|
public: Öffentlich
|
||||||
private: Privat
|
private: Privat
|
||||||
show: Anzeigen
|
show: Anzeigen
|
||||||
|
@ -116,3 +118,64 @@ NotFoundPage:
|
||||||
title: Seite nicht gefunden
|
title: Seite nicht gefunden
|
||||||
description: Den Fehler kennst du sicher...
|
description: Den Fehler kennst du sicher...
|
||||||
goBack: Zurückgehen
|
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
|
||||||
|
|
|
@ -5,6 +5,8 @@ locales:
|
||||||
general:
|
general:
|
||||||
loading: Loading
|
loading: Loading
|
||||||
unnamedTrack: Unnamed track
|
unnamedTrack: Unnamed track
|
||||||
|
urban: urban
|
||||||
|
rural: rural
|
||||||
public: Public
|
public: Public
|
||||||
private: Private
|
private: Private
|
||||||
show: Show
|
show: Show
|
||||||
|
@ -121,3 +123,63 @@ NotFoundPage:
|
||||||
title: Page not found
|
title: Page not found
|
||||||
description: You know what that means...
|
description: You know what that means...
|
||||||
goBack: Go back
|
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
|
||||||
|
|
Loading…
Reference in a new issue