diff options
author | Aijana Schumann <aijana.schumann@highstreet-technologies.com> | 2021-08-05 08:50:16 +0200 |
---|---|---|
committer | Aijana Schumann <aijana.schumann@highstreet-technologies.com> | 2021-08-05 08:50:16 +0200 |
commit | 3ba5eb125ac8890968e4437b098e39195d699434 (patch) | |
tree | ac58a39013fc9ab2cd468da83e4c41cd2d62b600 /sdnr/wt/odlux/apps/lineOfSightApp/src/components | |
parent | 437f67407aece6f7aed8e989638b0d64075f0c0a (diff) |
Update ODLUX
Add LineOfSightApp, update Framework, Connect, Performance and LinkCalculatorApp
Issue-ID: CCSDK-3417
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Change-Id: I651a2fb771d2963aea70f916c70c8fdfd3443e87
Diffstat (limited to 'sdnr/wt/odlux/apps/lineOfSightApp/src/components')
5 files changed, 752 insertions, 0 deletions
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/components/ConnectionErrorPoup.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/ConnectionErrorPoup.tsx new file mode 100644 index 000000000..7d9339fc0 --- /dev/null +++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/ConnectionErrorPoup.tsx @@ -0,0 +1,40 @@ +/** + * ============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 { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { Paper, Typography } from "@material-ui/core" +import * as React from "react" + +type props = { reachable: boolean|null}; + + +const ConnectionErrorPoup: React.FunctionComponent<props> = (props) => { + + return (props.reachable === false ? + <Paper style={{padding:5, position: 'absolute', top: 160, width: 230, left:"40%", zIndex:1}}> + <div style={{display: 'flex', flexDirection: 'column'}}> + <div style={{'alignSelf': 'center', marginBottom:5}}> <Typography> <FontAwesomeIcon icon={faExclamationTriangle} /> Connection Error</Typography></div> + <Typography>Service unavailable</Typography> + </div> + </Paper> : null +) + +} + +export default ConnectionErrorPoup;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/components/heightChart.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/heightChart.tsx new file mode 100644 index 000000000..3030fe7dd --- /dev/null +++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/heightChart.tsx @@ -0,0 +1,126 @@ +/** + * ============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 type { FC } from 'react'; +import * as d3 from 'd3'; + +import { useD3 } from "../hooks/d3"; +import { GPSProfileResult } from "../model/GPSProfileResult"; +import { max } from '../utils/math'; + +type HeightMapProps = { + data: GPSProfileResult[]; + dataMin: GPSProfileResult; + dataMax: GPSProfileResult; + width: number; + height: number; + heightPosA: number; + heightPosB: number; +} + +const HeightChart: FC<HeightMapProps> = (props) => { + const { data, dataMin, dataMax, heightPosA, heightPosB } = props; + let ref: React.RefObject<SVGSVGElement> + + const drawSvg = () => { + ref = useD3( + (svg) => { + const margin = 100; + const width = Number(svg.attr("width")) - margin; + const height = Number(svg.attr("height")) - margin; + + // Add X axis + const x = d3.scaleBand() + .range([0, width]) + .domain(data.map(d => (`${d.gps.latitude},${d.gps.latitude}`))) + .padding(0.2); + + const maxHeight = max([dataMax.height, heightPosA, heightPosB], d => d) + + // Add Y axis + const y = d3.scaleLinear() + .domain([dataMin.height, maxHeight]) + .range([height, 0]); + + svg.append("g") + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .call(d3.axisLeft(y)); + + // Bars + svg.selectAll("myBar") + .data(data) + .join("rect") + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr("x", d => x(`${d.gps.latitude},${d.gps.latitude}`) || '') + .attr("y", d => y(d.height)) + .attr("width", x.bandwidth()) + .attr("fill", "#69b3a2b0") + .attr("height", d => height - y(d.height)) // always equal to 0 + + const firstX = `${data[0].gps.latitude},${data[0].gps.latitude}` + const lastX = `${data[data.length - 1].gps.latitude},${data[data.length - 1].gps.latitude}`; + + //add line + const x1 = x(firstX)!; + const x2 = x(lastX)!; + + svg.append("line") + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr("x1", x1) + .attr("y1", y(props.heightPosA)) + .attr("x2", x2) + .attr("y2", y(props.heightPosB)) + + .style("stroke", "#88A") + .attr("stroke-width", "3px") + + //append circle on start and end + + svg.append("circle") + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr('cx', x1) + .attr('cy', y(props.heightPosA)) + .attr('r', 10) + .attr('stroke', '#223b53') + .attr('fill', '#225ba3'); + + svg.append("circle") + .attr('transform', `translate(${margin / 2}, ${margin / 2})`) + .attr('cx', x2) + .attr('cy', y(props.heightPosB)) + .attr('r', 10) + .attr('stroke', '#223b53') + .attr('fill', '#225ba3'); + }, + [data] + ); + } + + drawSvg(); + + + + return ( + <svg ref={ref!} width={props.width} height={props.height} /> + + ); +} + +export { HeightChart }; 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<typeof mapStateToProps, typeof mapDispatchToProps>; + +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<mapProps> = (props) => { + + //const [start, setStart] = React.useState<mapboxgl.LngLat| undefined>(); + //const [end, setEnd] = React.useState<mapboxgl.LngLat| undefined>(); + const [data, setData] = React.useState<GPSProfileResult[] | number>(Number.NaN); + const [dataMin, setDataMin] = React.useState<GPSProfileResult|undefined>(); + const [dataMax, setDataMax] = React.useState<GPSProfileResult|undefined>(); + const [isMapLoaded, setMapLoaded] = React.useState<boolean>(false); + + +const mapRef = React.useRef<{ map: mapboxgl.Map | null }>({ map: null }); +const mapContainerRef = React.useRef<HTMLDivElement>(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( + <MapContextMenu pos={e.lngLat} + onStart={(p) => { 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 <> + <div id="map" style={{ width: "100%", height:'100%', position: 'relative' }} ref={mapContainerRef} > + <MapInfo minHeight={dataMin} maxHeight={dataMax} /> + <ConnectionErrorPoup reachable={props.ready} /> + + {typeof data === "object" + ? ( + < div className={classes.chart} onClick={() => { + setData(Number.NaN); + setDataMax(undefined); + setDataMin(undefined); + props.ClearChartAction(); + }}> + <HeightChart heightPosA={heightA} heightPosB={heightB} width={mapContainerRef.current?.clientWidth!} height={mapContainerRef.current?.clientHeight!} data={data} dataMin={dataMin!} dataMax={dataMax!} /> + </div> + ) + : null + } + + </div> + </> +} + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Map)); + + diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapContextMenu.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapContextMenu.tsx new file mode 100644 index 000000000..0fc51cabf --- /dev/null +++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapContextMenu.tsx @@ -0,0 +1,86 @@ +/** + * ============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 { Button, InputAdornment, makeStyles, TextField, Tooltip } from "@material-ui/core"; +import * as React from "react"; +import { FC, useEffect, useState } from "react"; +import { getGPSHeight } from "../services/heightService"; + +type MapContextMenuProps = { + pos: mapboxgl.LngLat; + onStart: (pos: mapboxgl.LngLat) => void; + onEnd: (pos: mapboxgl.LngLat) => void; + onHeightA: (height: number, antennaHeight: number) => void; + onHeightB: (height: number, antennaHeight: number) => void; + + } + + const styles = makeStyles({ + flexContainer: {display: "flex", flexDirection:"row"}, + textField:{width:60}, + button:{marginRight:5, marginTop:5, flexGrow:2} + }); + + const MapContextMenu: FC<MapContextMenuProps> = (props) => { + const { pos, onStart, onEnd } = props; + const [height, setHeight] = useState<number | undefined>(undefined); + const [value1, setValue1] = useState<string>(''); + const [value2, setValue2] = useState<string>(''); + + const classes = styles(); + + useEffect(() => { + getGPSHeight({ longitude: pos.lng, latitude: pos.lat }).then(setHeight); + }, [pos.lat, pos.lng]); + + const handleChangeHeight = (e:React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>, id: "heightA"|"heightB") =>{ + + //sanitize non numbers + const onlyNums = e.target.value.replace(/[^0-9]/g, ''); + + if(id==="heightA"){ + setValue1(onlyNums); + }else{ + setValue2(onlyNums); + } + } + + return ( + <div> + <div>Height: {height} m</div> + <div> + <div className={classes.flexContainer}> + <Button className={classes.button} variant="contained" onClick={() => { onStart(pos); props.onHeightA(height!,+value1); }}>Start</Button> + <Tooltip title="Please add the antenna height in meters above sea level."> + <TextField className={classes.textField} value={value1} onChange={(e)=>handleChangeHeight(e,"heightA")} InputProps={{endAdornment: <InputAdornment position="start">m</InputAdornment>}}/> + </Tooltip> + </div> + <div className={classes.flexContainer}> + <Button className={classes.button} variant="contained" onClick={() => { onEnd(pos); props.onHeightB(height!,+value2);}}>End</Button> + <Tooltip title="Please add the antenna height in meters above sea level."> + <TextField className={classes.textField} value={value2} onChange={(e)=>handleChangeHeight(e,"heightB")} InputProps={{endAdornment: <InputAdornment position="start">m</InputAdornment>}}/> + </Tooltip> + </div> + </div> + + </div> + ); + }; + + + export default MapContextMenu;
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapInfo.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapInfo.tsx new file mode 100644 index 000000000..43a6e478a --- /dev/null +++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapInfo.tsx @@ -0,0 +1,171 @@ +/** + * ============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 { Accordion, AccordionDetails, AccordionSummary, makeStyles, Paper, Typography } from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { GPSProfileResult } from '../model/GPSProfileResult'; +import * as React from 'react'; +import { calculateDistanceInMeter } from '../utils/map'; +import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; + +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, +}); + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + + +}) + +type props = Connect<typeof mapStateToProps, typeof mapDispatchToProps> & { + minHeight: GPSProfileResult | undefined; + maxHeight: GPSProfileResult | undefined; +}; + +const styles = (props: any) => makeStyles({ + accordion: {padding: 5, position: 'absolute', top: 10, width: props.width, marginLeft: 10, zIndex:1}, + container: { display: 'flex', flexDirection: "column", marginLeft:10, padding: 5 }, + caption:{width:'40%'}, + subTitleRow:{ width: '60%'}, + titleRowElement:{width: '40%', fontWeight: "bold"}, + secondRow:{width:'25%'}, + thirdRow:{width:'20%'} + }); + +const MapInfo: React.FC<props> = (props) =>{ + + const [expanded, setExpanded] = React.useState(false); + const [width, setWidth] = React.useState(470); + const [length, setLength] = React.useState<string | undefined>(); + + const classes = styles({width: width})(); + + const {start, end, center, zoom, heightA, heightB, minHeight, maxHeight} = props; + + React.useEffect(()=>{ + + if(start && end){ + setLength(calculateDistanceInMeter(start.latitude, start.longitude, end.latitude, end.longitude).toFixed(3)) + + }else{ + setLength(undefined) + } + + }, [start, end]) + + const handleChange = (event: any, isExpanded: boolean) => { + setExpanded(isExpanded); + }; + + + + + return <Accordion className={classes.accordion} expanded={expanded} onChange={handleChange}> + <AccordionSummary + expandIcon={<ExpandMoreIcon />} + aria-controls="panel1a-content" + id="panel1a-header" + > + <Typography >Map Info</Typography> + </AccordionSummary> + <AccordionDetails className={classes.container}> + + + <Typography style={{ fontWeight: "bold", flex: "1" }} >Map Center</Typography> + + <div > + <div style={{ display: 'flex', flexDirection: "row" }}> + <Typography className={classes.caption}> Longitude</Typography><Typography>{center.longitude}</Typography></div> + <div style={{ display: 'flex', flexDirection: "row" }}> + <Typography className={classes.caption}> Latitude</Typography><Typography>{center.latitude}</Typography></div> + <div style={{ display: 'flex', flexDirection: "row" }}> + <Typography className={classes.caption}> Zoom</Typography><Typography> {zoom}</Typography></div> + + </div> + <Typography style={{ fontWeight: "bold", flex: "1", marginTop:5 }} >Link</Typography> + + <div> + <div style={{ display: 'flex', flexDirection: "row", marginLeft:"38%" }}> + <Typography className={classes.titleRowElement}> Start</Typography> + <Typography className={classes.titleRowElement}> End</Typography> + </div> + + <div style={{ display: 'flex', flexDirection: "row" }}> + + <Typography className={classes.caption}> Longitude</Typography> + <Typography className={classes.secondRow}> {start?.longitude.toFixed(3)}</Typography> + <Typography className={classes.secondRow}> {end?.longitude.toFixed(3)}</Typography></div> + + + <div style={{ display: 'flex', flexDirection: "row" }}> + + <Typography className={classes.caption}> Latitude</Typography> + <Typography className={classes.secondRow}> {start?.latitude.toFixed(3)}</Typography> + <Typography className={classes.secondRow}> {end?.latitude.toFixed(3)}</Typography></div> + + + + <div style={{ display: 'flex', flexDirection: "row" }}> + + <Typography className={classes.caption}> Meassured height [m]</Typography> + <Typography className={classes.secondRow}> {heightA?.amsl}</Typography> + <Typography className={classes.secondRow}> {heightB?.amsl}</Typography> + </div> + + <div style={{ display: 'flex', flexDirection: "row" }}> + + <Typography className={classes.caption}> Antenna height [m] </Typography> + <Typography className={classes.secondRow}> {heightA?.antennaHeight}</Typography> + <Typography className={classes.secondRow}> {heightB?.antennaHeight}</Typography> + </div> + + <div style={{ display: 'flex', flexDirection: "row" }}> + + <Typography className={classes.caption}> Length [m]</Typography> + <Typography className={classes.secondRow}> {length}</Typography> + + </div> + + <div style={{ display: 'flex', flexDirection: "row" }}> + + <Typography className={classes.caption}> Max height @ position </Typography> + <Typography className={classes.thirdRow}> {maxHeight? maxHeight.height+' m': ''}</Typography> + <Typography className={classes.thirdRow}> {maxHeight?.gps.longitude.toFixed(3)}</Typography> + <Typography className={classes.thirdRow}> {maxHeight?.gps.latitude.toFixed(3)}</Typography> + </div> + + <div style={{ display: 'flex', flexDirection: "row" }}> + + <Typography className={classes.caption}> Min height @ position</Typography> + <Typography className={classes.thirdRow}> {minHeight? minHeight.height +' m': ''}</Typography> + <Typography className={classes.thirdRow}> {minHeight?.gps.longitude.toFixed(3)}</Typography> + <Typography className={classes.thirdRow}> {minHeight?.gps.latitude.toFixed(3)}</Typography> + </div> + + </div> +</AccordionDetails> +</Accordion> +} + +export default connect(mapStateToProps, mapDispatchToProps)(MapInfo);
\ No newline at end of file |