diff --git a/package-lock.json b/package-lock.json
index fc2b68f..a0ac8e3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9282,6 +9282,11 @@
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
+ "mgrs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/mgrs/-/mgrs-1.0.0.tgz",
+ "integrity": "sha1-+5FYjnjJACVnI5XLQLJffNatGCk="
+ },
"microevent.ts": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz",
@@ -10064,6 +10069,11 @@
"rbush": "^3.0.1"
}
},
+ "ol-layerswitcher": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/ol-layerswitcher/-/ol-layerswitcher-3.8.3.tgz",
+ "integrity": "sha512-UwUhalf/sGXjz3rvr0EjwsaUVlJAhyJCfcIPciKk1QdNbMKq/2ZXNKGafOjwP2eDxiqhkvnhpIrDGD8+gQ19Cg=="
+ },
"ol-mapbox-style": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-6.3.1.tgz",
@@ -11632,6 +11642,15 @@
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
},
+ "proj4": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.7.0.tgz",
+ "integrity": "sha512-UVhulf8m70/dREOBrJagWq8cDYUgjQUWILRqys/gqo/+ZLeNB/04zbtPhJbz8+cCPzZNQMychfBaWUCP60U9mQ==",
+ "requires": {
+ "mgrs": "1.0.0",
+ "wkt-parser": "^1.2.4"
+ }
+ },
"promise": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz",
@@ -16200,6 +16219,11 @@
}
}
},
+ "wkt-parser": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.2.4.tgz",
+ "integrity": "sha512-ZzKnc7ml/91fOPh5bANBL4vUlWPIYYv11waCtWTkl2TRN+LEmBg60Q1MA8gqV4hEp4MGfSj9JiHz91zw/gTDXg=="
+ },
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
diff --git a/package.json b/package.json
index b1d03fc..a9b351f 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,8 @@
"luxon": "^1.25.0",
"node-sass": "^4.14.1",
"ol": "^6.5.0",
+ "ol-layerswitcher": "^3.8.3",
+ "proj4": "^2.7.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
diff --git a/src/components/Map/index.js b/src/components/Map/index.js
index 49bcd3b..85bfc28 100644
--- a/src/components/Map/index.js
+++ b/src/components/Map/index.js
@@ -1,14 +1,20 @@
import React from 'react'
import OlMap from 'ol/Map'
-import View from 'ol/View'
+import OlView from 'ol/View'
import OlTileLayer from 'ol/layer/Tile'
+import OlVectorLayer from 'ol/layer/Vector'
+import OlGroupLayer from 'ol/layer/Group'
import {fromLonLat} from 'ol/proj'
import OSM from 'ol/source/OSM'
+import OlLayerSwitcher from 'ol-layerswitcher'
+
import 'ol/ol.css'
+import "ol-layerswitcher/dist/ol-layerswitcher.css";
const MapContext = React.createContext()
+const MapLayerContext = React.createContext()
export function Map({children, ...props}) {
const ref = React.useRef()
@@ -18,11 +24,11 @@ export function Map({children, ...props}) {
React.useLayoutEffect(() => {
const map = new OlMap({
target: ref.current,
- view: new View({
- maxZoom: 22,
- center: fromLonLat([10, 51]),
- zoom: 5,
- }),
+ // view: new View({
+ // maxZoom: 22,
+ // center: fromLonLat([10, 51]),
+ // zoom: 5,
+ // }),
})
setMap(map)
@@ -36,29 +42,106 @@ export function Map({children, ...props}) {
return (
<>
- {children}
+ {map && (
+
+ {children}
+
+ )}
>
)
}
-export function TileLayer() {
- const map = React.useContext(MapContext)
+export function Layer({layerClass, getDefaultOptions, children, ...props}) {
+ const context = React.useContext(MapLayerContext)
const layer = React.useMemo(
() =>
- new OlTileLayer({
- source: new OSM(),
+ new layerClass({
+ ...(getDefaultOptions ? getDefaultOptions() : {}),
+ ...props,
+ }),
+ []
+ )
+
+ for (const [k, v] of Object.entries(props)) {
+ layer.set(k, v)
+ }
+
+ React.useEffect(() => {
+ context?.push(layer)
+ return () => context?.remove(layer)
+ }, [layer, context])
+
+ if (typeof layer.getLayers === 'function') {
+ return {children}
+ } else {
+ return null
+ }
+}
+
+export function TileLayer(props) {
+ return ({source: new OSM()})} {...props} />
+}
+
+export function VectorLayer(props) {
+ return
+}
+
+export function GroupLayer(props) {
+ return
+}
+
+function FitView({extent}) {
+ const map = React.useContext(MapContext)
+
+ React.useEffect(() => {
+ if (extent && map) {
+ map.getView().fit(extent)
+ }
+ }, [extent, map])
+
+ return null
+}
+
+function View({...options}) {
+ const map = React.useContext(MapContext)
+
+ const view = React.useMemo(
+ () =>
+ new OlView({
+ ...options,
}),
[]
)
React.useEffect(() => {
- map?.addLayer(layer)
- return () => map?.removeLayer(layer)
- })
+ if (view && map) {
+ map.setView(view)
+ }
+ }, [view, map])
+
return null
}
+function LayerSwitcher({...options}) {
+ const map = React.useContext(MapContext)
+
+ const control = React.useMemo(() => new OlLayerSwitcher(options), [])
+
+ React.useEffect(() => {
+ map?.addControl(control)
+ return () => map?.removeControl(control)
+ }, [control, map])
+
+ return null
+}
+
+Map.FitView = FitView
+Map.GroupLayer = GroupLayer
+Map.LayerSwitcher = LayerSwitcher
Map.TileLayer = TileLayer
+Map.VectorLayer = VectorLayer
+Map.View = View
+
export default Map
diff --git a/src/pages/TrackPage.tsx b/src/pages/TrackPage.tsx
index d93d6cf..3d612dd 100644
--- a/src/pages/TrackPage.tsx
+++ b/src/pages/TrackPage.tsx
@@ -8,10 +8,21 @@ import {pluck, distinctUntilChanged, map, switchMap, startWith} from 'rxjs/opera
import {useObservable} from 'rxjs-hooks'
import {Settings, DateTime, Duration} from 'luxon'
+import {Vector as VectorSource} from 'ol/source';
+import {Geometry, LineString, Point} from 'ol/geom';
+import Feature from 'ol/Feature';
+import {fromLonLat} from 'ol/proj';
+import proj4 from 'proj4';
+import {register} from 'ol/proj/proj4';
+import {Fill, Stroke, Style, Text, Circle} from 'ol/style';
+
import api from '../api'
import {Map, Page} from '../components'
import type {Track, TrackData, TrackComment} from '../types'
+proj4.defs('projLayer1', '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs');
+register(proj4);
+
// TODO: remove
Settings.defaultLocale = 'de-DE'
@@ -148,6 +159,174 @@ function TrackComments({comments, login, hideLoader}) {
)
}
+const isValidTrackPoint = (point: TrackPoint): boolean =>
+ point.latitude != null && point.longitude != null && (point.latitude !== 0 || point.longitude !== 0)
+
+function TrackMap({track, trackData, ...props}) {
+ const {
+ trackVectorSource,
+ trackPointsD1,
+ trackPointsD2,
+ trackPointsUntaggedD1,
+ trackPointsUntaggedD2,
+ viewExtent,
+ } = React.useMemo(() => {
+ const trackPointsD1: Feature[] = []
+ const trackPointsD2: Feature[] = []
+ const trackPointsUntaggedD1: Feature[] = []
+ const trackPointsUntaggedD2: Feature[] = []
+ const points: Coordinate[] = []
+ const filteredPoints: TrackPoint[] = trackData?.points.filter(isValidTrackPoint) ?? []
+
+ for (const dataPoint of filteredPoints) {
+ const {longitude, latitude, flag, d1, d2} = dataPoint
+
+ const p = fromLonLat([longitude, latitude])
+ points.push(p)
+
+ const geometry = new Point(p)
+
+ if (flag && d1) {
+ trackPointsD1.push(new Feature({distance: d1, geometry}))
+ }
+
+ if (flag && d2) {
+ trackPointsD2.push(new Feature({distance: d2, geometry}))
+ }
+
+ if (!flag && d1) {
+ trackPointsUntaggedD1.push(new Feature({distance: d1, geometry}))
+ }
+
+ if (!flag && d2) {
+ trackPointsUntaggedD2.push(new Feature({distance: d2, geometry}))
+ }
+ }
+
+ //Simplify to 1 point per 2 meter
+ const trackVectorSource = new VectorSource({
+ features: [new Feature(new LineString(points).simplify(2))],
+ })
+
+ const viewExtent = points.length ? trackVectorSource.getExtent() : null
+ return {trackVectorSource, trackPointsD1, trackPointsD2, trackPointsUntaggedD1, trackPointsUntaggedD2, viewExtent}
+ }, [trackData?.points])
+
+
+ const trackLayerStyle = React.useMemo(
+ () =>
+ new Style({
+ stroke: new Stroke({
+ width: 3,
+ color: 'rgb(30,144,255)',
+ }),
+ }),
+ []
+ )
+
+ return (
+
+ )
+}
+
+function pointStyleFunction(feature, resolution) {
+ let distance = feature.get('distance')
+ let radius = 200 / resolution
+
+ return new Style({
+ image: new Circle({
+ radius: radius < 20 ? radius : 20,
+ fill: evaluateDistanceForFillColor(distance),
+ stroke: evaluateDistanceForStrokeColor(distance),
+ }),
+ text: createTextStyle(distance, resolution),
+ })
+}
+
+const evaluateDistanceForFillColor = function (distance) {
+ const redFill = new Fill({color: 'rgba(255, 0, 0, 0.2)'})
+ const orangeFill = new Fill({color: 'rgba(245,134,0,0.2)'})
+ const greenFill = new Fill({color: 'rgba(50, 205, 50, 0.2)'})
+
+ switch (evaluateDistanceColor(distance)) {
+ case 'red':
+ return redFill
+ case 'orange':
+ return orangeFill
+ case 'green':
+ return greenFill
+ }
+}
+
+const evaluateDistanceForStrokeColor = function (distance) {
+ const redStroke = new Stroke({color: 'rgb(255, 0, 0)'})
+ const orangeStroke = new Stroke({color: 'rgb(245,134,0)'})
+ const greenStroke = new Stroke({color: 'rgb(50, 205, 50)'})
+
+ switch (evaluateDistanceColor(distance)) {
+ case 'red':
+ return redStroke
+ case 'orange':
+ return orangeStroke
+ case 'green':
+ return greenStroke
+ }
+}
+
+ const WARN_DISTANCE= 200
+ const MIN_DISTANCE= 150
+
+
+const evaluateDistanceColor = function (distance) {
+ if (distance < MIN_DISTANCE) {
+ return 'red'
+ } else if (distance < WARN_DISTANCE) {
+ return 'orange'
+ } else {
+ return 'green'
+ }
+}
+
+const createTextStyle = function (distance, resolution) {
+ return new Text({
+ textAlign: 'center',
+ textBaseline: 'middle',
+ font: 'normal 18px/1 Arial',
+ text: resolution < 6 ? '' + distance : '',
+ fill: new Fill({color: evaluateDistanceColor(distance)}),
+ stroke: new Stroke({color: 'white', width: 2}),
+ offsetX: 0,
+ offsetY: 0,
+ })
+}
+
+function PointLayer({features, title, visible}) {
+ return
+}
+
const TrackPage = connect((state) => ({login: state.login}))(function TrackPage({login}) {
const {slug} = useParams()
@@ -200,9 +379,7 @@ const TrackPage = connect((state) => ({login: state.login}))(function TrackPage(
-
+