frontend: Include roads.json in homepage, and use grayscale theme for maps
This commit is contained in:
parent
912aebb9d5
commit
760ea4c013
|
@ -52,7 +52,7 @@ export function Map({children, ...props}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Layer({layerClass, getDefaultOptions, children, ...props}) {
|
export function Layer({layerClass, getDefaultOptions, children, ...props}) {
|
||||||
const context = React.useContext(MapLayerContext)
|
const context = React.useContext(MapContext)
|
||||||
|
|
||||||
const layer = React.useMemo(
|
const layer = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -64,13 +64,11 @@ export function Layer({layerClass, getDefaultOptions, children, ...props}) {
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(props)) {
|
layer.setProperties(props)
|
||||||
layer.set(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
context?.push(layer)
|
context?.addLayer(layer)
|
||||||
return () => context?.remove(layer)
|
return () => context?.removeLayer(layer)
|
||||||
}, [layer, context])
|
}, [layer, context])
|
||||||
|
|
||||||
if (typeof layer.getLayers === 'function') {
|
if (typeof layer.getLayers === 'function') {
|
||||||
|
@ -80,9 +78,8 @@ export function Layer({layerClass, getDefaultOptions, children, ...props}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TileLayer(props) {
|
export function TileLayer({osm, ...props}) {
|
||||||
return <Layer layerClass={OlTileLayer} getDefaultOptions={() => ({source: new OSM()})} {...props} />
|
return <Layer layerClass={OlTileLayer} getDefaultOptions={() => ({source: new OSM(osm)})} {...props} /> }
|
||||||
}
|
|
||||||
|
|
||||||
export function VectorLayer(props) {
|
export function VectorLayer(props) {
|
||||||
return <Layer layerClass={OlVectorLayer} {...props} />
|
return <Layer layerClass={OlVectorLayer} {...props} />
|
||||||
|
|
386
frontend/src/components/RoadsLayer.tsx
Normal file
386
frontend/src/components/RoadsLayer.tsx
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {useObservable} from 'rxjs-hooks'
|
||||||
|
import {of} from 'rxjs'
|
||||||
|
import {switchMap} from 'rxjs/operators'
|
||||||
|
import VectorSource from 'ol/source/Vector'
|
||||||
|
import GeoJSON from 'ol/format/GeoJSON'
|
||||||
|
import {Stroke, Style} from 'ol/style'
|
||||||
|
|
||||||
|
import Map from './Map'
|
||||||
|
|
||||||
|
import {paletteUrban, paletteRural, palettePercentage, palettePercentageInverted} from 'palettes'
|
||||||
|
|
||||||
|
// var criterion = "d_mean";
|
||||||
|
// var criterion = "p_above";
|
||||||
|
var criterion = 'p_below'
|
||||||
|
|
||||||
|
// var hist_xa = 0.0
|
||||||
|
// var hist_xb = 2.55
|
||||||
|
// var hist_dx = 0.25
|
||||||
|
// var hist_n = Math.ceil((hist_xb - hist_xa) / hist_dx)
|
||||||
|
|
||||||
|
// function histogramLabels() {
|
||||||
|
// var labels = Array(hist_n)
|
||||||
|
// for (var i = 0; i < hist_n; i++) {
|
||||||
|
// var xa = hist_xa + hist_dx * i
|
||||||
|
// var xb = xa + hist_dx
|
||||||
|
// var xc = xa + 0.5 * hist_dx
|
||||||
|
// labels[i] = (xa * 100).toFixed(0) + '-' + (xb * 100).toFixed(0)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return labels
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function histogramColors(palette) {
|
||||||
|
// var colors = Array(hist_n)
|
||||||
|
// for (var i = 0; i < hist_n; i++) {
|
||||||
|
// var xc = hist_xa + hist_dx * i
|
||||||
|
// colors[i] = palette.rgb_hex(xc)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return colors
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function histogram(samples) {
|
||||||
|
// var binCounts = new Array(hist_n).fill(0)
|
||||||
|
//
|
||||||
|
// for (var i = 0; i < samples.length; i++) {
|
||||||
|
// var v = samples[i]
|
||||||
|
// var j = Math.floor((v - hist_xa) / hist_dx)
|
||||||
|
// if (j >= 0 && j < hist_n) {
|
||||||
|
// binCounts[j]++
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return binCounts
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function annotation_verbose(feature) {
|
||||||
|
// var s = ''
|
||||||
|
//
|
||||||
|
// s += 'name: ' + feature.get('name') + '\n'
|
||||||
|
// s += 'way_id: ' + feature.get('way_id') + '\n'
|
||||||
|
// s += 'direction: ' + feature.get('direction') + '\n'
|
||||||
|
// s += 'zone: ' + feature.get('zone') + '\n'
|
||||||
|
// s += 'valid: ' + feature.get('valid') + '\n'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_limit')
|
||||||
|
// s += 'distance_overtaker_limit: ' + (d == null ? 'n/a' : d.toFixed(2)) + ' m \n'
|
||||||
|
//
|
||||||
|
// s += '<hr></hr>statistics\n'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_mean')
|
||||||
|
// s += 'distance_overtaker_mean: ' + (d == null ? 'n/a' : d.toFixed(2)) + ' m \n'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_median')
|
||||||
|
// s += 'distance_overtaker_median: ' + (d == null ? 'n/a' : d.toFixed(2)) + ' m \n'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_minimum')
|
||||||
|
// s += 'distance_overtaker_minimum: ' + (d == null ? 'n/a' : d.toFixed(2)) + ' m \n'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_n')
|
||||||
|
// s += 'distance_overtaker_n: ' + (d == null ? 'n/a' : d.toFixed(0)) + '\n'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_n_above_limit')
|
||||||
|
// s += 'distance_overtaker_n_above_limit: ' + (d == null ? 'n/a' : d.toFixed(0)) + '\n'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_n_below_limit')
|
||||||
|
// s += 'distance_overtaker_n_below_limit: ' + (d == null ? 'n/a' : d.toFixed(0)) + '\n'
|
||||||
|
//
|
||||||
|
// var n_below = feature.get('distance_overtaker_n_below_limit')
|
||||||
|
// var n = feature.get('distance_overtaker_n')
|
||||||
|
// var p = (n_below / n) * 100.0
|
||||||
|
// s += 'overtakers below limit: ' + (p == null ? 'n/a' : p.toFixed(1)) + ' %\n'
|
||||||
|
//
|
||||||
|
// return s
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// function annotation(feature) {
|
||||||
|
// var s = '<table>'
|
||||||
|
//
|
||||||
|
// s +=
|
||||||
|
// '<tr><td>Straßenname:</td><td><a href="https://www.openstreetmap.org/way/' +
|
||||||
|
// feature.get('way_id') +
|
||||||
|
// '" target="_blank">' +
|
||||||
|
// feature.get('name') +
|
||||||
|
// '</a></td></tr>'
|
||||||
|
// d = feature.get('distance_overtaker_limit')
|
||||||
|
// s += '<tr><td>Mindestüberholabstand:</td><td>' + (d == null ? 'n/a' : d.toFixed(2)) + ' m </td>'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_n')
|
||||||
|
// s += '<tr><td>Anzahl Messungen:</td><td>' + (d == null ? 'n/a' : d.toFixed(0)) + '</td></tr>'
|
||||||
|
//
|
||||||
|
// var n_below = feature.get('distance_overtaker_n_below_limit')
|
||||||
|
// var n = feature.get('distance_overtaker_n')
|
||||||
|
// var p = (n_below / n) * 100.0
|
||||||
|
// s +=
|
||||||
|
// '<tr><td>Unterschreitung Mindestabstand:</td><td>' +
|
||||||
|
// (p == null ? 'n/a' : p.toFixed(1)) +
|
||||||
|
// '% der Überholenden</td></tr>'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_mean')
|
||||||
|
// s += '<tr><td>Durchschnitt Überholabstand:</td><td>' + (d == null ? 'n/a' : d.toFixed(2)) + ' m </td></tr>'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_median')
|
||||||
|
// s += '<tr><td>Median Überholabstand:</td><td>' + (d == null ? 'n/a' : d.toFixed(2)) + ' m </td></tr>'
|
||||||
|
//
|
||||||
|
// d = feature.get('distance_overtaker_minimum')
|
||||||
|
// s += '<tr><td>Minimum Überholabstand:</td><td>' + (d == null ? 'n/a' : d.toFixed(2)) + ' m </td></tr>'
|
||||||
|
//
|
||||||
|
// s += '</table>'
|
||||||
|
//
|
||||||
|
// return s
|
||||||
|
// }
|
||||||
|
|
||||||
|
function styleFunction(feature, resolution, active = false) {
|
||||||
|
const {
|
||||||
|
distance_overtaker_n: n,
|
||||||
|
distance_overtaker_n_above_limit: n_above_limit,
|
||||||
|
distance_overtaker_n_below_limit: n_below_limit,
|
||||||
|
distance_overtaker_mean: mean,
|
||||||
|
distance_overtaker_median: median,
|
||||||
|
distance_overtaker_minimum: minimum,
|
||||||
|
zone,
|
||||||
|
valid,
|
||||||
|
} = feature.getProperties()
|
||||||
|
|
||||||
|
let palette
|
||||||
|
if (zone === 'urban') {
|
||||||
|
palette = paletteUrban
|
||||||
|
} else if (zone === 'rural') {
|
||||||
|
palette = paletteRural
|
||||||
|
} else {
|
||||||
|
palette = paletteUrban
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = [0, 0, 0, 255]
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
switch (criterion) {
|
||||||
|
case 'd_mean':
|
||||||
|
color = palette.rgba_css(mean)
|
||||||
|
break
|
||||||
|
case 'd_median':
|
||||||
|
color = palette.rgba_css(median)
|
||||||
|
break
|
||||||
|
case 'd_min':
|
||||||
|
color = palette.rgba_css(minimum)
|
||||||
|
break
|
||||||
|
case 'p_above':
|
||||||
|
color = palettePercentage.rgba_css(n > 0 ? (n_above_limit / n * 100) : undefined)
|
||||||
|
break
|
||||||
|
case 'p_below':
|
||||||
|
color = palettePercentageInverted.rgba_css(n > 0 ? (n_below_limit / n * 100) : undefined)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
color = [128, 128, 128, 255]
|
||||||
|
}
|
||||||
|
|
||||||
|
// var width = 2 + 1*Math.log10(n);
|
||||||
|
var width = active ? 6 : 3
|
||||||
|
// width =Math.max(2.0, width*1/resolution);
|
||||||
|
|
||||||
|
var style = new Style({
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: color,
|
||||||
|
width: width,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
|
// var map = new ol.Map({
|
||||||
|
// target: 'map',
|
||||||
|
// layers: [
|
||||||
|
// new ol.layer.Tile({
|
||||||
|
// source: new ol.source.OSM({
|
||||||
|
// url: 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
|
||||||
|
// crossOrigin: null,
|
||||||
|
// // url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
||||||
|
// }),
|
||||||
|
// }),
|
||||||
|
// ],
|
||||||
|
// view: new ol.View({
|
||||||
|
// center: ol.proj.fromLonLat([9.1798, 48.7759]),
|
||||||
|
// zoom: 13,
|
||||||
|
// }),
|
||||||
|
// })
|
||||||
|
|
||||||
|
export default function RoadsLayer() {
|
||||||
|
const dataSource = React.useMemo(
|
||||||
|
() =>
|
||||||
|
new VectorSource({
|
||||||
|
format: new GeoJSON(),
|
||||||
|
url: 'https://dev.openbikesensor.org/public/json/roads.json',
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
return <Map.VectorLayer source={dataSource} style={styleFunction} zIndex={1000} />
|
||||||
|
}
|
||||||
|
|
||||||
|
// var histogramColorsRural = histogramColors(paletteRural).reverse()
|
||||||
|
// var histogramColorsUrban = histogramColors(paletteUrban).reverse()
|
||||||
|
//
|
||||||
|
// var chartOptions = {
|
||||||
|
// series: [
|
||||||
|
// {
|
||||||
|
// name: 'Überholende',
|
||||||
|
// data: Array(hist_n).fill(0),
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// chart: {
|
||||||
|
// type: 'bar',
|
||||||
|
// height: 350,
|
||||||
|
// animations: {
|
||||||
|
// animateGradually: {
|
||||||
|
// enabled: false,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// plotOptions: {
|
||||||
|
// bar: {
|
||||||
|
// horizontal: false,
|
||||||
|
// columnWidth: '95%',
|
||||||
|
// endingShape: 'flat',
|
||||||
|
// distributed: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// dataLabels: {
|
||||||
|
// enabled: true,
|
||||||
|
// },
|
||||||
|
// stroke: {
|
||||||
|
// show: false,
|
||||||
|
// },
|
||||||
|
// xaxis: {
|
||||||
|
// title: {
|
||||||
|
// text: 'Überholabstand in Zentimeter',
|
||||||
|
// },
|
||||||
|
// categories: histogramLabels().reverse(),
|
||||||
|
// },
|
||||||
|
// yaxis: {
|
||||||
|
// title: {
|
||||||
|
// text: 'Anzahl Überholende',
|
||||||
|
// },
|
||||||
|
// labels: {
|
||||||
|
// show: false,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// fill: {
|
||||||
|
// opacity: 1,
|
||||||
|
// },
|
||||||
|
// legend: {
|
||||||
|
// show: false,
|
||||||
|
// },
|
||||||
|
// tooltip: {
|
||||||
|
// y: {
|
||||||
|
// formatter: function (val) {
|
||||||
|
// return val
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var chart = new ApexCharts(document.querySelector('#chart'), chartOptions)
|
||||||
|
// chart.render()
|
||||||
|
|
||||||
|
// var noFeatureActive = true
|
||||||
|
|
||||||
|
// map.on('singleclick', function (evt) {
|
||||||
|
// var feature = map.forEachFeatureAtPixel(evt.pixel, function (feature, layer) {
|
||||||
|
// return feature
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// var resolution = map.getView().getResolution()
|
||||||
|
//
|
||||||
|
// if (!noFeatureActive) {
|
||||||
|
// vectorLayer
|
||||||
|
// .getSource()
|
||||||
|
// .getFeatures()
|
||||||
|
// .forEach((f) => {
|
||||||
|
// f.setStyle(styleFunction(f, resolution, false))
|
||||||
|
// })
|
||||||
|
// noFeatureActive = true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (feature && dataSource.hasFeature(feature)) {
|
||||||
|
// console.log(annotation_verbose(feature))
|
||||||
|
// caption.innerHTML = annotation(feature)
|
||||||
|
// caption.style.alignItems = 'flex-start'
|
||||||
|
//
|
||||||
|
// var zone = feature.get('zone')
|
||||||
|
// var colors = undefined
|
||||||
|
// switch (zone) {
|
||||||
|
// case 'urban':
|
||||||
|
// colors = histogramColorsUrban
|
||||||
|
// break
|
||||||
|
// case 'rural':
|
||||||
|
// colors = histogramColorsRural
|
||||||
|
// break
|
||||||
|
// default:
|
||||||
|
// colors = histogramColorsUrban
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// chart.updateOptions({
|
||||||
|
// colors: colors,
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// var hist = histogram(feature.get('distance_overtaker_measurements')).reverse()
|
||||||
|
//
|
||||||
|
// chart.updateSeries([
|
||||||
|
// {
|
||||||
|
// name: 'Überholende',
|
||||||
|
// data: hist,
|
||||||
|
// },
|
||||||
|
// ])
|
||||||
|
//
|
||||||
|
// feature.setStyle(styleFunction(feature, resolution, true))
|
||||||
|
// noFeatureActive = false
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// function writeLegend(palette, target, ticks, postfix) {
|
||||||
|
// const div = document.getElementById(target)
|
||||||
|
// const canvas = document.createElement('canvas')
|
||||||
|
// const context = canvas.getContext('2d')
|
||||||
|
//
|
||||||
|
// const barWidth = palette.n
|
||||||
|
// const barLeft = 25
|
||||||
|
// const barHeight = 25
|
||||||
|
//
|
||||||
|
// canvas.width = 300
|
||||||
|
// canvas.height = 50
|
||||||
|
//
|
||||||
|
// const imgData = context.getImageData(0, 0, barWidth, barHeight)
|
||||||
|
// const data = imgData.data
|
||||||
|
//
|
||||||
|
// let k = 0
|
||||||
|
// for (let y = 0; y < barHeight; y++) {
|
||||||
|
// for (let x = 0; x < barWidth; x++) {
|
||||||
|
// for (let c = 0; c < 4; c++) {
|
||||||
|
// data[k] = palette.rgba_sampled[x][c]
|
||||||
|
// k += 1
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// context.putImageData(imgData, barLeft, 0)
|
||||||
|
//
|
||||||
|
// context.font = '12px Arial'
|
||||||
|
// context.textAlign = 'center'
|
||||||
|
// context.textBaseline = 'top'
|
||||||
|
// for (let i = 0; i < ticks.length; i++) {
|
||||||
|
// const v = ticks[i]
|
||||||
|
// const x = barLeft + ((v - palette.a) / (palette.b - palette.a)) * (palette.n - 1)
|
||||||
|
// const y = 25
|
||||||
|
// context.fillText(v.toFixed(2) + postfix, x, y)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const image = new Image()
|
||||||
|
// image.src = canvas.toDataURL()
|
||||||
|
// image.height = canvas.height
|
||||||
|
// image.width = canvas.width
|
||||||
|
// div.appendChild(image)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// writeLegend(palettePercentageInverted, 'colorbar', [0, 100.0], '%')
|
|
@ -4,4 +4,5 @@ export {default as FormattedDate} from './FormattedDate'
|
||||||
export {default as LoginButton} from './LoginButton'
|
export {default as LoginButton} from './LoginButton'
|
||||||
export {default as Map} from './Map'
|
export {default as Map} from './Map'
|
||||||
export {default as Page} from './Page'
|
export {default as Page} from './Page'
|
||||||
|
export {default as RoadsLayer} from './RoadsLayer'
|
||||||
export {default as StripMarkdown} from './StripMarkdown'
|
export {default as StripMarkdown} from './StripMarkdown'
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {fromLonLat} from 'ol/proj'
|
||||||
import {Duration} from 'luxon'
|
import {Duration} from 'luxon'
|
||||||
|
|
||||||
import api from '../api'
|
import api from '../api'
|
||||||
import {Map, Page} from '../components'
|
import {Map, Page, RoadsLayer} from '../components'
|
||||||
|
|
||||||
import {TrackListItem} from './TracksPage'
|
import {TrackListItem} from './TracksPage'
|
||||||
import styles from './HomePage.module.scss'
|
import styles from './HomePage.module.scss'
|
||||||
|
@ -22,18 +22,21 @@ function formatDuration(seconds) {
|
||||||
function WelcomeMap() {
|
function WelcomeMap() {
|
||||||
return (
|
return (
|
||||||
<Map className={styles.welcomeMap}>
|
<Map className={styles.welcomeMap}>
|
||||||
<Map.TileLayer />
|
<RoadsLayer />
|
||||||
<Map.View maxZoom={22} zoom={6} center={fromLonLat([10, 51])} />
|
<Map.TileLayer
|
||||||
|
osm={{
|
||||||
|
url: 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
|
||||||
|
crossOrigin: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <Map.View maxZoom={22} zoom={6} center={fromLonLat([10, 51])} /> */}
|
||||||
|
<Map.View maxZoom={22} zoom={13} center={fromLonLat([9.1798, 48.7759])} />
|
||||||
</Map>
|
</Map>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Stats() {
|
function Stats() {
|
||||||
const stats = useObservable(
|
const stats = useObservable(() => of(null).pipe(switchMap(() => api.fetch('/stats'))))
|
||||||
() => of(null).pipe(
|
|
||||||
switchMap(() => api.fetch('/stats'))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -42,7 +45,7 @@ function Stats() {
|
||||||
<Segment>
|
<Segment>
|
||||||
<Loader active={stats == null} />
|
<Loader active={stats == null} />
|
||||||
|
|
||||||
<Statistic.Group widths={2} size="mini" >
|
<Statistic.Group widths={2} size="mini">
|
||||||
<Statistic>
|
<Statistic>
|
||||||
<Statistic.Value>{Number(stats?.publicTrackLength / 1000).toFixed(1)}</Statistic.Value>
|
<Statistic.Value>{Number(stats?.publicTrackLength / 1000).toFixed(1)}</Statistic.Value>
|
||||||
<Statistic.Label>km track length</Statistic.Label>
|
<Statistic.Label>km track length</Statistic.Label>
|
||||||
|
@ -106,7 +109,6 @@ export default function HomePage() {
|
||||||
<MostRecentTrack />
|
<MostRecentTrack />
|
||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|
|
@ -157,7 +157,12 @@ export default function TrackMap({trackData, show, ...props}: {trackData: TrackD
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Map {...props}>
|
<Map {...props}>
|
||||||
<Map.TileLayer />
|
<Map.TileLayer
|
||||||
|
osm={{
|
||||||
|
url: 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
|
||||||
|
crossOrigin: null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Map.VectorLayer
|
<Map.VectorLayer
|
||||||
visible
|
visible
|
||||||
updateWhileAnimating={false}
|
updateWhileAnimating={false}
|
||||||
|
|
168
frontend/src/palettes.ts
Normal file
168
frontend/src/palettes.ts
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
export class Palette {
|
||||||
|
constructor(p, colorInvalid) {
|
||||||
|
this.colorInvalid = colorInvalid
|
||||||
|
this.resamplePalette(p, 256)
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba(v) {
|
||||||
|
if (v == null) {
|
||||||
|
return this.colorInvalid
|
||||||
|
}
|
||||||
|
let i = ((v - this.a) / (this.b - this.a)) * (this.n - 1)
|
||||||
|
i = Math.round(i)
|
||||||
|
i = Math.max(0, Math.min(this.n - 1, i))
|
||||||
|
return this.rgba_sampled[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
rgba_css(v) {
|
||||||
|
const color = this.rgba(v)
|
||||||
|
return 'rgba(' + [color[0], color[1], color[2], color[3]].join(',') + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb_css(v) {
|
||||||
|
const color = this.rgba(v)
|
||||||
|
return 'rgb(' + [color[0], color[1], color[2]].join(',') + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb_hex(v) {
|
||||||
|
const color = this.rgba(v)
|
||||||
|
|
||||||
|
const s = '#' + this.hex2digits(color[0]) + this.hex2digits(color[1]) + this.hex2digits(color[2])
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
hex2digits(v) {
|
||||||
|
const hex = v.toString(16)
|
||||||
|
return hex.length === 1 ? '0' + hex : hex
|
||||||
|
}
|
||||||
|
|
||||||
|
samplePalette(palette, d) {
|
||||||
|
let x = Object.keys(palette)
|
||||||
|
|
||||||
|
for (let i = 0; i < x.length; i++) {
|
||||||
|
x[i] = parseFloat(x[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x.sort(function (a, b) {
|
||||||
|
return a - b
|
||||||
|
})
|
||||||
|
|
||||||
|
const n = x.length
|
||||||
|
let y
|
||||||
|
|
||||||
|
if (d <= x[0]) {
|
||||||
|
y = palette[x[0]]
|
||||||
|
} else if (d >= x[n - 1]) {
|
||||||
|
y = palette[x[n - 1]]
|
||||||
|
} else {
|
||||||
|
let ia = 0
|
||||||
|
let ib = n - 1
|
||||||
|
|
||||||
|
while (ib - ia > 1) {
|
||||||
|
const ic = Math.round(0.5 * (ia + ib))
|
||||||
|
if (d < x[ic]) {
|
||||||
|
ib = ic
|
||||||
|
} else {
|
||||||
|
ia = ic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const xa = x[ia]
|
||||||
|
const xb = x[ib]
|
||||||
|
const w = (d - xa) / (xb - xa)
|
||||||
|
y = Array(4)
|
||||||
|
const ya = palette[xa]
|
||||||
|
const yb = palette[xb]
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
y[i] = Math.round(ya[i] * (1 - w) + yb[i] * w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
resamplePalette(palette, n) {
|
||||||
|
const x = Object.keys(palette)
|
||||||
|
|
||||||
|
for (let i = 0; i < x.length; i++) {
|
||||||
|
x[i] = parseFloat(x[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = Math.min(...x)
|
||||||
|
const b = Math.max(...x)
|
||||||
|
|
||||||
|
const p = new Array(n)
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const xi = a + (parseFloat(i) / (n - 1)) * (b - a)
|
||||||
|
p[i] = this.samplePalette(palette, xi)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.a = a
|
||||||
|
this.b = b
|
||||||
|
this.rgba_sampled = p
|
||||||
|
this.n = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const paletteUrban = new Palette(
|
||||||
|
{
|
||||||
|
0.0: [64, 0, 0, 255],
|
||||||
|
1.4999: [196, 0, 0, 255],
|
||||||
|
1.5: [196, 196, 0, 255],
|
||||||
|
2.0: [0, 196, 0, 255],
|
||||||
|
2.55: [0, 255, 0, 255],
|
||||||
|
},
|
||||||
|
[0, 0, 196, 255]
|
||||||
|
)
|
||||||
|
|
||||||
|
export const paletteRural = new Palette(
|
||||||
|
{
|
||||||
|
0.0: [64, 0, 0, 255],
|
||||||
|
1.9999: [196, 0, 0, 255],
|
||||||
|
2.0: [196, 196, 0, 255],
|
||||||
|
2.5: [0, 196, 0, 255],
|
||||||
|
2.55: [0, 255, 0, 255],
|
||||||
|
},
|
||||||
|
[0, 0, 196, 255]
|
||||||
|
)
|
||||||
|
|
||||||
|
export const paletteRural_ryg = new Palette(
|
||||||
|
{
|
||||||
|
0.0: [196, 0, 0, 255],
|
||||||
|
1.5: [196, 196, 0, 255],
|
||||||
|
2.0: [0, 196, 0, 255],
|
||||||
|
},
|
||||||
|
[0, 0, 196, 255]
|
||||||
|
)
|
||||||
|
|
||||||
|
export const paletteUrban_ryg = new Palette(
|
||||||
|
{
|
||||||
|
0.0: [196, 0, 0, 255],
|
||||||
|
2.0: [196, 196, 0, 255],
|
||||||
|
2.5: [0, 196, 0, 255],
|
||||||
|
},
|
||||||
|
[0, 0, 196, 255]
|
||||||
|
)
|
||||||
|
|
||||||
|
export const colorUndefinedDistance = [0, 0, 0, 0]
|
||||||
|
|
||||||
|
export const palettePercentage = new Palette(
|
||||||
|
{
|
||||||
|
0.0: [64, 0, 0, 255],
|
||||||
|
25.0: [196, 0, 0, 255],
|
||||||
|
90.0: [196, 196, 0, 255],
|
||||||
|
100.0: [0, 255, 0, 255],
|
||||||
|
},
|
||||||
|
[0, 0, 196, 255]
|
||||||
|
)
|
||||||
|
|
||||||
|
export const palettePercentageInverted = new Palette(
|
||||||
|
{
|
||||||
|
0.0: [0, 255, 0, 255],
|
||||||
|
10.0: [196, 196, 0, 255],
|
||||||
|
75.0: [196, 0, 0, 255],
|
||||||
|
100.0: [64, 0, 0, 255],
|
||||||
|
},
|
||||||
|
[0, 0, 196, 255]
|
||||||
|
)
|
Loading…
Reference in a new issue