From 3ba5eb125ac8890968e4437b098e39195d699434 Mon Sep 17 00:00:00 2001 From: Aijana Schumann Date: Thu, 5 Aug 2021 08:50:16 +0200 Subject: Update ODLUX Add LineOfSightApp, update Framework, Connect, Performance and LinkCalculatorApp Issue-ID: CCSDK-3417 Signed-off-by: Aijana Schumann Change-Id: I651a2fb771d2963aea70f916c70c8fdfd3443e87 --- .../apps/lineOfSightApp/src/components/map.tsx | 329 +++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 sdnr/wt/odlux/apps/lineOfSightApp/src/components/map.tsx (limited to 'sdnr/wt/odlux/apps/lineOfSightApp/src/components/map.tsx') diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/components/map.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/map.tsx new file mode 100644 index 000000000..6f29d5993 --- /dev/null +++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/map.tsx @@ -0,0 +1,329 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import * as React from 'react' +import * as mapboxgl from 'mapbox-gl'; +import { render } from 'react-dom'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { OSM_STYLE, URL_BASEPATH } from '../config'; +import { GPSProfileResult } from '../model/GPSProfileResult'; +import MapContextMenu from './mapContextMenu'; +import { getGPSProfile } from '../services/heightService'; +import { max, min } from '../utils/math'; +import { HeightChart } from './heightChart'; +import { makeStyles } from '@material-ui/core'; +import { ClearSavedChartAction, SetChartAction, SetEndpointAction, SetHeightA, SetHeightB, SetMapCenterAction, SetStartPointAction } from '../actions/mapActions'; +import { LatLon } from '../model/LatLon'; +import MapInfo from './mapInfo'; +import { Height } from 'model/Height'; +import { PictureAsPdf } from '@material-ui/icons'; +import ConnectionErrorPoup from './ConnectionErrorPoup'; +import { addBaseLayer, addBaseSource, addPoint } from '../utils/map'; +import { SetReachableAction } from '../actions/commonActions'; + +import 'mapbox-gl/dist/mapbox-gl.css'; + +type mapProps = RouteComponentProps & Connect; + +const mapStateToProps = (state: IApplicationStoreState) => ({ + center: state.lineOfSight.map.center, + zoom: state.lineOfSight.map.zoom, + start: state.lineOfSight.map.start, + end: state.lineOfSight.map.end, + heightA: state.lineOfSight.map.heightA, + heightB: state.lineOfSight.map.heightB, + ready: state.lineOfSight.map.ready +}); + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + ClearChartAction: () => dispatcher.dispatch(new ClearSavedChartAction), + SetMapPosition: (point: LatLon, zoom: number) => dispatcher.dispatch(new SetMapCenterAction(point, zoom)), + SetHeightStart: (height: Height) => dispatcher.dispatch(new SetHeightA(height)), + SetHeightEnd: (height: Height) => dispatcher.dispatch(new SetHeightB(height)), + setStartPosition: (position: LatLon|null) => dispatcher.dispatch(new SetStartPointAction(position)), + setEndPosition: (position: LatLon|null) => dispatcher.dispatch(new SetEndpointAction(position)), + setReachable : (reachable: boolean |null) => dispatcher.dispatch(new SetReachableAction(reachable)), + + + +}) + + +let map: mapboxgl.Map; + +const styles = makeStyles({ + chart: { + position: "absolute", + top: 0, + bottom: 0, + left: 0, + right: 0 + + } + }); + + +const Map: React.FC = (props) => { + + //const [start, setStart] = React.useState(); + //const [end, setEnd] = React.useState(); + const [data, setData] = React.useState(Number.NaN); + const [dataMin, setDataMin] = React.useState(); + const [dataMax, setDataMax] = React.useState(); + const [isMapLoaded, setMapLoaded] = React.useState(false); + + +const mapRef = React.useRef<{ map: mapboxgl.Map | null }>({ map: null }); +const mapContainerRef = React.useRef(null); + + + +const classes = styles(); + +const heightA = props.heightA !== null ? props.heightA.amsl + props.heightA.antennaHeight : 0; +const heightB = props.heightB !== null ? props.heightB.amsl + props.heightB.antennaHeight : 0; + +const {start, end} = props; + +const handleResize = () =>{ + + if (map) { + // wait a moment until resizing actually happened + window.setTimeout(() => map.resize(), 500); + } + +} + +//on mount +React.useEffect(()=>{ + + window.addEventListener("menu-resized", handleResize); + + + return () =>{ + console.log("unmount") + window.removeEventListener("menu-resized", handleResize); + + const center = mapRef.current.map?.getCenter(); + const mapZoom = mapRef.current.map?.getZoom(); + if(center){ + props.SetMapPosition({latitude: center.lat, longitude:center.lng}, mapZoom!); + } + + props.setReachable(null); + } + +},[]); + + + React.useEffect(()=>{ + + if(props.ready){ + setupMap(); + } + + },[props.ready]); + + React.useEffect(() => { + if (props.ready && isMapLoaded) { + drawChart(); + updateLosUrl(); + } + + }, [start, end, isMapLoaded]); + + const drawChart = () =>{ + if(start && end){ + + addBaseSource(map, 'route'); + addBaseLayer(map, 'route'); + + const json = `{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [${start.longitude}, ${start.latitude}], + [${end.longitude}, ${end.latitude}] + ]} + }`; + + + + (map.getSource("route") as mapboxgl.GeoJSONSource).setData(JSON.parse(json)); + + + getGPSProfile({ latitude: start.latitude, longitude: start.longitude }, { latitude: end.latitude, longitude: end.longitude }).then(data => { + if (Array.isArray(data)) { + setDataMin(min(data, d => d.height)); + setDataMax(max(data, d => d.height)); + } + setData(data); + }); + } + else if (start || end){ + + const point = start!==null ? start: end!; + addBaseSource(map, 'route'); + addBaseLayer(map, 'route'); + addPoint(map, point); + + } + else { + //delete layers and source + //used instead of clearing source data because it has better performance + //(setting data to empty results in a noticable lag of line being cleared) + mapRef.current.map?.getLayer('line') && mapRef.current.map?.removeLayer('line') && mapRef.current.map?.removeLayer('points') && mapRef.current.map?.removeSource('route'); + + } + } + + const updateLosUrl = () =>{ + + if(start && end){ + + const locationPart = `lat1=${start.latitude}&lon1=${start.longitude}&lat2=${end.latitude}&lon2=${end.longitude}`; + + let heightPart = ''; + + if(props.heightA && props.heightB){ + heightPart = `&amslA=${props.heightA.amsl}&antennaHeightA=${props.heightA.antennaHeight}&amslB=${props.heightB.amsl}&antennaHeightB=${props.heightB.antennaHeight}`; + + } + + props.history.replace(`/${URL_BASEPATH}/los?${locationPart}${heightPart}`) + + }else if(!start && !end){ + props.history.replace(`/${URL_BASEPATH}`); + } + } + + + const updateHeightA = (value:number, value2: number) =>{ + props.SetHeightStart({amsl: value, antennaHeight: value2}); + } + + const updateHeightB = (value:number, value2: number) =>{ + props.SetHeightEnd({amsl: value, antennaHeight: value2}); + } + + const OnEndPosition = (position: mapboxgl.LngLat) =>{ + props.setEndPosition({latitude: position.lat, longitude: position.lng}) + } + + const OnStartPosition = (position: mapboxgl.LngLat) =>{ + props.setStartPosition({latitude: position.lat, longitude: position.lng}) + } + + + const setupMap = () => { + + let lat = props.center.latitude + let lon = props.center.longitude; + let zoom = props.zoom; + + map = new mapboxgl.Map({ + container: mapContainerRef.current!, + style: OSM_STYLE as any, + center: [lon, lat], + zoom: zoom, + accessToken: '' + }); + + mapRef.current.map = map; + + map.on('load', (ev) => { + + map.setMaxZoom(18); + setMapLoaded(true); + + //add source, layer + + addBaseSource(map, 'route'); + addBaseLayer(map, 'route'); + + }); + + let currentPopup: mapboxgl.Popup | null = null; + map.on('contextmenu', (e) => { + + if (currentPopup) + currentPopup.remove(); + + //change height if start/end changes + //??? -> show value? / reset after chart display? + + const popupNode = document.createElement("div"); + render( + { OnStartPosition(p); if (currentPopup) currentPopup.remove(); }} + onEnd={(p) => { OnEndPosition(p); if (currentPopup) currentPopup.remove(); }} + onHeightA={(p,p1)=> updateHeightA(p, p1)} + onHeightB={(p, p1)=> updateHeightB(p, p1)} />, + popupNode); + + currentPopup = new mapboxgl.Popup() + .setLngLat(e.lngLat) + .setDOMContent(popupNode) + .addTo(map); + }); + + map.on('moveend', mapMoveEnd); + + }; + + const mapMoveEnd = () =>{ + const mapZoom = Number(map.getZoom().toFixed(2)); + const lat = Number(map.getCenter().lat.toFixed(4)); + const lon = Number(map.getCenter().lng.toFixed(4)); + + props.SetMapPosition({latitude: lat, longitude: lon}, mapZoom); + + } + + + + return <> +
+ + + + {typeof data === "object" + ? ( + < div className={classes.chart} onClick={() => { + setData(Number.NaN); + setDataMax(undefined); + setDataMin(undefined); + props.ClearChartAction(); + }}> + +
+ ) + : null + } + + + +} + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Map)); + + -- cgit 1.2.3-korg