summaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/apps
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt/odlux/apps')
-rw-r--r--sdnr/wt/odlux/apps/app-feature/pom.xml5
-rwxr-xr-xsdnr/wt/odlux/apps/app-installer/pom.xml5
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx5
-rw-r--r--sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts1
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/.babelrc17
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/package.json47
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/pom.xml160
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/actions/commonActions.ts80
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/actions/mapActions.ts67
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/components/ConnectionErrorPoup.tsx40
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/components/heightChart.tsx126
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/components/map.tsx329
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapContextMenu.tsx86
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/components/mapInfo.tsx171
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/config.ts48
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/mapHandler.ts82
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/rootHandler.ts48
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/hooks/d3.ts37
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/index.html31
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/model/GPSProfileResult.ts19
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/model/Height.tsx22
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/model/LatLon.ts22
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/pluginLineOfSight.tsx138
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/services/heightService.ts62
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/styles/index.css13
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/styles/mapbox-gl.css1
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/utils/map.ts103
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/utils/math.ts30
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src/views/main.tsx30
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java68
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml9
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src2/test/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/test/TestBundleRes.java46
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/src2/test/resources/test.js5
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/tsconfig.json37
-rw-r--r--sdnr/wt/odlux/apps/lineOfSightApp/webpack.config.js211
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/src/actions/commonLinkCalculationActions.ts55
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/src/handlers/linkCalculationAppRootHandler.ts60
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/src/index.html3
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/src/pluginLinkCalculation.tsx99
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx115
-rw-r--r--sdnr/wt/odlux/apps/linkCalculationApp/webpack.config.js14
-rw-r--r--sdnr/wt/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx2
42 files changed, 2401 insertions, 148 deletions
diff --git a/sdnr/wt/odlux/apps/app-feature/pom.xml b/sdnr/wt/odlux/apps/app-feature/pom.xml
index 738ac6964..17c0dab6e 100644
--- a/sdnr/wt/odlux/apps/app-feature/pom.xml
+++ b/sdnr/wt/odlux/apps/app-feature/pom.xml
@@ -106,6 +106,11 @@
<artifactId>sdnr-wt-odlux-app-linkCalculationApp</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-app-lineOfSightApp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
diff --git a/sdnr/wt/odlux/apps/app-installer/pom.xml b/sdnr/wt/odlux/apps/app-installer/pom.xml
index 1ac88593f..721d56488 100755
--- a/sdnr/wt/odlux/apps/app-installer/pom.xml
+++ b/sdnr/wt/odlux/apps/app-installer/pom.xml
@@ -132,6 +132,11 @@
<artifactId>sdnr-wt-odlux-app-linkCalculationApp</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-app-lineOfSightApp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
diff --git a/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx b/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
index 5d0757ab2..4a7a0d269 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
+++ b/sdnr/wt/odlux/apps/connectApp/src/components/networkElements.tsx
@@ -194,11 +194,12 @@ export class NetworkElementsListComponent extends React.Component<NetworkElement
<NetworkElementTable stickyHeader tableId="network-element-table" customActionButtons={[refreshNetworkElementsAction, ...canAdd ? [addRequireNetworkElementAction] : []]} columns={[
{ property: "nodeId", title: "Node Name", type: ColumnType.text },
{ property: "isRequired", title: "Required", type: ColumnType.boolean },
- { property: "status", title: "Connection Status", type: ColumnType.text },
+ { property: "status", title: "Connection Status", type: ColumnType.text, width:'15%' },
{ property: "host", title: "Host", type: ColumnType.text },
{ property: "port", title: "Port", type: ColumnType.numeric },
{ property: "coreModelCapability", title: "Core Model", type: ColumnType.text },
- { property: "deviceType", title: "Type", type: ColumnType.text },
+ { property: "deviceType", title: "Device Type", type: ColumnType.text },
+ { property: "deviceFunction", title: "Device Function", type: ColumnType.text, width: '15%' }
]} idProperty="id" {...this.props.networkElementsActions} {...this.props.networkElementsProperties} asynchronus createContextMenu={rowData => {
return this.getContextMenu(rowData);
diff --git a/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts
index 89070ab39..bc15afb31 100644
--- a/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts
+++ b/sdnr/wt/odlux/apps/connectApp/src/models/networkElementConnection.ts
@@ -30,6 +30,7 @@ export type NetworkElementConnection = {
status?: "Connected" | "mounted" | "unmounted" | "Connecting" | "Disconnected" | "idle";
coreModelCapability?: string;
deviceType?: string;
+ deviceFunction?: string;
nodeDetails?: {
availableCapabilites: {
capabilityOrigin: string;
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/.babelrc b/sdnr/wt/odlux/apps/lineOfSightApp/.babelrc
new file mode 100644
index 000000000..3d8cd1260
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/.babelrc
@@ -0,0 +1,17 @@
+{
+ "presets": [
+ ["@babel/preset-react"],
+ ["@babel/preset-env", {
+ "targets": {
+ "chrome": "66"
+ },
+ "spec": true,
+ "loose": false,
+ "modules": false,
+ "debug": false,
+ "useBuiltIns": "usage",
+ "forceAllTransforms": true
+ }]
+ ],
+ "plugins": []
+}
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/package.json b/sdnr/wt/odlux/apps/lineOfSightApp/package.json
new file mode 100644
index 000000000..dbba7ecfb
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "@odlux/line-of-sight-app",
+ "version": "0.1.0",
+ "description": "A react based modular UI to display event log from a database.",
+ "main": "index.js",
+ "scripts": {
+ "start": "webpack-dev-server --env debug",
+ "build": "webpack --env release --config webpack.config.js",
+ "build:dev": "webpack --env debug --config webpack.config.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://git.mfico.de/highstreet-technologies/odlux.git"
+ },
+ "keywords": [
+ "reactjs",
+ "redux",
+ "ui",
+ "framework"
+ ],
+ "author": "Aijana Schumann",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@odlux/framework": "*",
+ "@types/d3": "^6.7.0",
+ "@types/mapbox-gl": "^1.10.2",
+ "@types/node": "^12.0.0",
+ "d3": "^7.0.0",
+ "d3-polygon": "^3.0.1",
+ "mapbox-gl": "^1.11.0",
+ "object.values": "^1.1.1"
+ },
+ "peerDependencies": {
+ "@material-ui/core": "4.11.4",
+ "@material-ui/icons": "4.11.2",
+ "@types/classnames": "2.2.6",
+ "@types/flux": "3.1.8",
+ "@types/jquery": "3.3.10",
+ "@types/react": "17.0.3",
+ "@types/react-dom": "17.0.2",
+ "@types/react-router-dom": "5.1.7",
+ "jquery": "3.3.1",
+ "react": "17.0.1",
+ "react-dom": "17.0.1",
+ "react-router-dom": "5.2.0"
+ }
+}
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/pom.xml b/sdnr/wt/odlux/apps/lineOfSightApp/pom.xml
new file mode 100644
index 000000000..29184a78b
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/pom.xml
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ ============LICENSE_START=======================================================
+ ~ ONAP : ccsdk features
+ ~ ================================================================================
+ ~ Copyright (C) 2020 AT&T 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=======================================================
+ ~
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.ccsdk.parent</groupId>
+ <artifactId>binding-parent</artifactId>
+ <version>2.2.0-SNAPSHOT</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.onap.ccsdk.features.sdnr.wt</groupId>
+ <artifactId>sdnr-wt-odlux-app-lineOfSightApp</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>ccsdk-features :: ${project.artifactId}</name>
+ <licenses>
+ <license>
+ <name>Apache License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0</url>
+ </license>
+ </licenses>
+
+ <properties>
+ <maven.javadoc.skip>true</maven.javadoc.skip>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-core-model</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-core-provider</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src2/main/java</sourceDirectory>
+ <resources>
+ <resource>
+ <directory>dist</directory>
+ <targetPath>odlux</targetPath>
+ </resource>
+ <resource>
+ <directory>src2/main/resources</directory>
+ </resource>
+ <resource>
+ <directory>src2/test/resources</directory>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <artifactId>maven-clean-plugin</artifactId>
+ <configuration>
+ <filesets>
+ <fileset>
+ <directory>dist</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <fileset>
+ <directory>node</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <fileset>
+ <directory>node_modules</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <fileset>
+ <directory>../node_modules</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ <!-- eclipse bug build bin folder in basedir -->
+ <fileset>
+ <directory>bin</directory>
+ <followSymlinks>false</followSymlinks>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-test-source</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>add-test-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>src2/test/java</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>de.jacks-it-lab</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.7.2</version>
+ <executions>
+ <execution>
+ <id>install node and yarn</id>
+ <goals>
+ <goal>install-node-and-yarn</goal>
+ </goals>
+ <!-- optional: default phase is "generate-resources" -->
+ <phase>initialize</phase>
+ <configuration>
+ <nodeVersion>v12.13.0</nodeVersion>
+ <yarnVersion>v1.22.10</yarnVersion>
+ </configuration>
+ </execution>
+ <execution>
+ <id>yarn build</id>
+ <goals>
+ <goal>yarn</goal>
+ </goals>
+ <configuration>
+ <arguments>run build</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/actions/commonActions.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/actions/commonActions.ts
new file mode 100644
index 000000000..3cc8ea4a7
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/actions/commonActions.ts
@@ -0,0 +1,80 @@
+/**
+ * ============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 { Height } from "model/Height";
+import { isNumber } from "../utils/math";
+import { Action } from "../../../../framework/src/flux/action";
+ import { Dispatch } from "../../../../framework/src/flux/store";
+ import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
+
+ import { LatLon } from "../model/LatLon";
+
+
+ export class SetPassedInValuesAction extends Action{
+ constructor(public start: LatLon, public end: LatLon, public center: LatLon, public heightA : Height |null, public heightB: Height |null){
+ super();
+ }
+ }
+
+ export class SetReachableAction extends Action{
+ constructor(public reachable: boolean | null){
+ super();
+ }
+ }
+
+ export const SetPassedInValues = (values: (string|null)[]) => (dispatcher: Dispatch) =>{
+
+ const start: LatLon = {latitude: Number(values[0]), longitude: Number(values[1])}
+ const end: LatLon = {latitude: Number(values[2]), longitude: Number(values[3])};
+ const midpoint = calculateMidPoint(start.latitude, start.longitude, end.latitude, end.longitude);
+ const center: LatLon = {latitude: midpoint[1], longitude: midpoint[0]};
+ const heightA: Height | null = isNumber(values[4]) && isNumber(values[5]) ? {amsl:+values[4]!, antennaHeight: +values[5]!} : null;
+ const heightB: Height | null = isNumber(values[6]) && isNumber(values[7]) ? {amsl:+values[6]!, antennaHeight: +values[7]!} : null;
+
+
+ dispatcher(new SetPassedInValuesAction(start, end, center, heightA, heightB));
+ }
+
+ //taken from https://www.movable-type.co.uk/scripts/latlong.html
+const calculateMidPoint = (lat1: number, lon1: number, lat2: number, lon2: number) =>{
+
+ const dLon = degrees_to_radians(lon2 - lon1);
+
+ //convert to radians
+ lat1 = degrees_to_radians(lat1);
+ lat2 = degrees_to_radians(lat2);
+ lon1 = degrees_to_radians(lon1);
+
+ const Bx = Math.cos(lat2) * Math.cos(dLon);
+ const By = Math.cos(lat2) * Math.sin(dLon);
+ const lat3 = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt((Math.cos(lat1) + Bx) * (Math.cos(lat1) + Bx) + By * By));
+ const lon3 = lon1 + Math.atan2(By, Math.cos(lat1) + Bx);
+
+ return [radians_to_degrees(lon3), radians_to_degrees(lat3)];
+}
+
+const degrees_to_radians = (degrees: number) =>
+{
+return degrees * (Math.PI/180);
+}
+
+const radians_to_degrees = (radians:number) =>{
+
+ var pi = Math.PI;
+ return radians * (180/pi);
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/actions/mapActions.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/actions/mapActions.ts
new file mode 100644
index 000000000..37ef5ee25
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/actions/mapActions.ts
@@ -0,0 +1,67 @@
+/**
+ * ============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 { LatLon } from "../model/LatLon";
+import { Action } from "../../../../framework/src/flux/action";
+import { Height } from "model/Height";
+
+export class SetChartAction extends Action{
+ constructor(public startPoint: LatLon, public endPoint: LatLon, public heightA: Height, public heightB: Height){
+ super();
+ }
+}
+
+export class SetStartPointAction extends Action{
+ constructor(public startPoint: LatLon|null){
+ super();
+ }
+}
+
+export class SetEndpointAction extends Action{
+ constructor(public endPoint: LatLon|null){
+ super();
+ }
+}
+
+export class SetHeightA extends Action{
+ constructor(public height: Height){
+ super();
+ }
+}
+
+export class SetHeightB extends Action{
+ constructor(public height: Height){
+ super();
+ }
+}
+
+export class ClearSavedChartAction extends Action{
+ constructor(){
+ super();
+ }
+}
+
+export class SetMapCenterAction extends Action{
+ /**
+ *
+ */
+ constructor(public point: LatLon, public zoom: number) {
+ super();
+
+ }
+} \ No newline at end of file
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
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/config.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/config.ts
new file mode 100644
index 000000000..bc1e1ff99
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/config.ts
@@ -0,0 +1,48 @@
+/**
+ * ============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==========================================================================
+ */
+
+export const URL_BASEPATH="lineOfSight";
+
+export const TERRAIN_URL="/terrain"; //http://10.20.11.163:5200 /terrain
+
+export const TILE_URL="/tiles"; //http://tile.openstreetmap.org /tiles
+
+
+export const OSM_STYLE = {
+ 'version': 8,
+ 'sources': {
+ 'raster-tiles': {
+ 'type': 'raster',
+ 'tiles': [
+ TILE_URL+'/{z}/{x}/{y}.png'
+ ],
+ 'tileSize': 256,
+ 'attribution':
+ '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+ }
+ },
+ 'layers': [
+ {
+ 'id': 'simple-tiles',
+ 'type': 'raster',
+ 'source': 'raster-tiles',
+ 'minZoom': 0,
+ 'maxZoom': 18
+ }
+ ]
+}; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/mapHandler.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/mapHandler.ts
new file mode 100644
index 000000000..6d11977bf
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/mapHandler.ts
@@ -0,0 +1,82 @@
+/**
+ * ============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 { LatLon } from "../model/LatLon";
+import { IActionHandler } from "../../../../framework/src/flux/action";
+import { SetPassedInValuesAction, SetReachableAction } from "../actions/commonActions";
+import { ClearSavedChartAction, SetChartAction, SetEndpointAction, SetHeightA, SetHeightB, SetMapCenterAction, SetStartPointAction } from "../actions/mapActions";
+import { Height } from "model/Height";
+import { isNullOrUndefined } from "util";
+
+
+
+ export interface IMap {
+ center: LatLon;
+ zoom: number;
+ start: LatLon |null;
+ heightA: Height | null;
+ end: LatLon|null;
+ heightB: Height | null;
+ ready: boolean |null;
+ }
+
+ const initialState: IMap = {
+ center: {latitude:52.4003, longitude:13.0584},
+ zoom: 12,
+ start: null,
+ end: null,
+ ready: null,
+ heightA: null,
+ heightB: null
+
+ }
+
+ export const mapHandler: IActionHandler<IMap> = (state = initialState, action) => {
+ if (action instanceof SetPassedInValuesAction) {
+ state = { ...state, start: action.start, end: action.end, center: action.center, heightA: action.heightA, heightB: action.heightB };
+ }
+ else if(action instanceof SetReachableAction){
+ state = { ...state, ready: action.reachable };
+
+ }else if(action instanceof SetChartAction){
+ state = {...state, start:action.startPoint, end: action.endPoint, heightA: action.heightA, heightB: action.heightB}
+ }
+ else if(action instanceof SetStartPointAction){
+ state = {...state, start:action.startPoint}
+
+ }
+ else if(action instanceof SetEndpointAction){
+ state = {...state, end:action.endPoint}
+
+ }
+ else if(action instanceof SetHeightA){
+ state = {...state, heightA:action.height}
+
+ }
+ else if(action instanceof SetHeightB){
+ state = {...state, heightB:action.height}
+
+ }
+ else if(action instanceof ClearSavedChartAction){
+ state= {...state, start: null, end: null, heightA:null, heightB: null}
+ }else if(action instanceof SetMapCenterAction){
+ state={...state, zoom: action.zoom,center:action.point}
+ }
+
+ return state;
+ } \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/rootHandler.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/rootHandler.ts
new file mode 100644
index 000000000..e7d58c41f
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/handlers/rootHandler.ts
@@ -0,0 +1,48 @@
+/**
+* ============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==========================================================================
+*/
+// main state handler
+
+import { combineActionHandler } from '../../../../framework/src/flux/middleware';
+
+// ** do not remove **
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+import { IActionHandler } from '../../../../framework/src/flux/action';
+
+
+import { IMap, mapHandler } from './mapHandler';
+
+export interface ILineOfSightAppStateState {
+ map: IMap;
+}
+
+
+
+
+declare module '../../../../framework/src/store/applicationStore' {
+ interface IApplicationStoreState {
+ lineOfSight: ILineOfSightAppStateState;
+ }
+}
+
+const actionHandlers = {
+ map: mapHandler,
+};
+
+export const lineofSightRootHandler = combineActionHandler<ILineOfSightAppStateState>(actionHandlers);
+export default lineofSightRootHandler;
+
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/hooks/d3.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/hooks/d3.ts
new file mode 100644
index 000000000..dfebe8605
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/hooks/d3.ts
@@ -0,0 +1,37 @@
+/**
+ * ============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==========================================================================
+ */
+
+/* eslint-disable react-hooks/exhaustive-deps */
+import { useEffect, useRef } from 'react';
+import type { DependencyList } from 'react';
+
+import * as d3 from 'd3';
+
+
+type SelectionType = d3.Selection<SVGSVGElement, d3.BaseType, null, undefined>;
+
+export const useD3 = (renderChartFn: (selection: SelectionType) => void, dependencies: DependencyList) => {
+ const ref = useRef<SVGSVGElement>(null);
+
+ useEffect(() => {
+ if (ref.current) renderChartFn(d3.select(ref.current));
+ return () => { };
+ }, dependencies);
+
+ return ref;
+}
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/index.html b/sdnr/wt/odlux/apps/lineOfSightApp/src/index.html
new file mode 100644
index 000000000..6c1478e42
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/index.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <!-- <link rel="stylesheet" href="./vendor.css" > -->
+ <title>LineOfSightApp</title>
+</head>
+
+<body>
+ <div id="app"></div>
+ <script type="text/javascript" src="./require.js"></script>
+ <script type="text/javascript" src="./config.js"></script>
+
+ <script>
+ // run the application
+ require(["app","connectApp","faultApp", "networkMapApp", "lineOfSightApp", "linkCalculationApp"], function (app, connectApp, faultApp, networkMapApp, lineOfSightApp, linkCalculationApp) {
+ connectApp.register();
+ faultApp.register();
+ //configurationApp.register();
+ //linkCalculationApp.register();
+ networkMapApp.register();
+ lineOfSightApp.register();
+ app("./app.tsx").runApplication();
+ });
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/model/GPSProfileResult.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/model/GPSProfileResult.ts
new file mode 100644
index 000000000..567946bc6
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/model/GPSProfileResult.ts
@@ -0,0 +1,19 @@
+/**
+ * ============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==========================================================================
+ */
+
+export type GPSProfileResult = { height: number, gps: { latitude: number, longitude: number }, band: string, zone: number, easting: number, northing: number }; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/model/Height.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/model/Height.tsx
new file mode 100644
index 000000000..7014095cb
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/model/Height.tsx
@@ -0,0 +1,22 @@
+/**
+ * ============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==========================================================================
+ */
+
+export type Height = {
+ amsl: number;
+ antennaHeight: number;
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/model/LatLon.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/model/LatLon.ts
new file mode 100644
index 000000000..a447aa52a
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/model/LatLon.ts
@@ -0,0 +1,22 @@
+/**
+ * ============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==========================================================================
+ */
+
+export type LatLon ={
+ latitude: number,
+ longitude: number
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/pluginLineOfSight.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/pluginLineOfSight.tsx
new file mode 100644
index 000000000..b193cfb22
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/pluginLineOfSight.tsx
@@ -0,0 +1,138 @@
+/**
+ * ============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==========================================================================
+ */
+
+// app configuration and main entry point for the app
+
+import * as React from "react";
+import { faRoute } from '@fortawesome/free-solid-svg-icons'; // select app icon
+import applicationManager from '../../../framework/src/services/applicationManager';
+
+
+import { lineofSightRootHandler } from './handlers/rootHandler';
+import MainView from "./views/main";
+import applicationApi from "../../../framework/src/services/applicationApi";
+
+import { Redirect, Route, RouteComponentProps, Switch, useLocation, withRouter } from "react-router-dom";
+import connect, { Connect, IDispatcher } from "../../../framework/src/flux/connect";
+import { IApplicationStoreState } from "../../../framework/src/store/applicationStore";
+import { SetPassedInValues, SetReachableAction } from "./actions/commonActions";
+import { TERRAIN_URL, TILE_URL } from "./config";
+import { isNumber } from "./utils/math";
+
+const mapProps = (state: IApplicationStoreState) => ({
+});
+
+const mapDisp = (dispatcher: IDispatcher) => ({
+ setPassedInValues: (values: (string | null)[]) => dispatcher.dispatch(SetPassedInValues(values)),
+ setReachable: (reachable: boolean) => dispatcher.dispatch(new SetReachableAction(reachable))
+
+});
+
+let lastSearch = "";
+
+const useQuery = () => {
+ return new URLSearchParams(useLocation().search);
+}
+
+
+const LineOfSightApplicationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComponentProps<{ mountId?: string }> & Connect<typeof mapProps, typeof mapDisp>) => {
+
+ let query = useQuery();
+
+ // called when component finshed mounting
+ React.useEffect(() => {
+ extractAndDispatchUrlValues(props.location.search);
+
+ //check tiles/terrain connectivity
+ tryCheckConnection();
+
+ }, []);
+
+
+ const extractAndDispatchUrlValues = (url: string) => {
+
+ if (lastSearch !== url) {
+ lastSearch = url;
+
+ //if mandatory values aren't there, do nothing
+ if (areMandatoryParamsPresent(query)) {
+ const values = extractValuesFromURL(query);
+ props.setPassedInValues(values);
+ }
+ }
+ }
+
+ const tryCheckConnection =() =>{
+ const terrain = fetch(`${TERRAIN_URL}/`);
+ const tiles = fetch(`${TILE_URL}/10/0/0.png`);
+
+ Promise.all([terrain, tiles])
+ .then((result) => {
+ props.setReachable(true);
+
+ })
+ .catch(error=>{
+ console.error("services not reachable.");
+ console.error(error);
+ props.setReachable(false);
+
+ })
+
+ }
+
+ /***
+ *
+ * Checks if lat1, lon1, lat2, lon2 were passed in as url parameters
+ */
+ const areMandatoryParamsPresent = (query: URLSearchParams) => {
+
+ return isNumber(query.get("lat1")) && isNumber(query.get("lon1")) && isNumber(query.get("lat2")) && isNumber(query.get("lon2"))
+
+ }
+
+ const extractValuesFromURL = (query: URLSearchParams) => {
+
+ return [query.get("lat1"), query.get("lon1"), query.get("lat2"), query.get("lon2"), query.get("amslA"), query.get("antennaHeightA"), query.get("amslB"), query.get("antennaHeightB")]
+ }
+
+ return (
+ <MainView />
+ );
+});
+
+
+const LoSRouterApp = withRouter(connect(mapProps, mapDisp)((props: RouteComponentProps & Connect<typeof mapProps, typeof mapDisp>) => {
+
+ return (
+ <Switch>
+ <Route path={`${props.match.path}`} component={LineOfSightApplicationRouteAdapter} />
+ <Redirect to={`${props.match.path}`} />
+ </Switch>
+ )
+}));
+
+export function register() {
+ applicationManager.registerApplication({
+ name: "lineOfSight", // used as name of state as well
+ icon: faRoute,
+ rootActionHandler: lineofSightRootHandler,
+ rootComponent: LoSRouterApp,
+ menuEntry: "Line of Sight"
+ });
+}
+
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/services/heightService.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/services/heightService.ts
new file mode 100644
index 000000000..8b0535881
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/services/heightService.ts
@@ -0,0 +1,62 @@
+/**
+ * ============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 { GPSProfileResult } from "../model/GPSProfileResult";
+import { TERRAIN_URL } from "../config";
+import { LatLon } from "../model/LatLon";
+
+export const apiUrlBase="api/Query";
+
+export const getGPSProfile = async (start: LatLon, end: LatLon) => {
+ const url = `${TERRAIN_URL}/${apiUrlBase}/GPSProfileRecords`;
+
+ const result = await fetch(url, {
+ method: "POST",
+ body: JSON.stringify({ start, end }),
+ headers: {
+ 'Content-Type': 'application/json'
+ // 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ });
+ if (result.ok) {
+ const data = await result.json() as GPSProfileResult[];
+ return data;
+ }
+
+ return Number.NaN;
+}
+
+export const getGPSHeight = async (gpsCoord: LatLon) => {
+ const url = `${TERRAIN_URL}/${apiUrlBase}/GPSHeight`;
+
+ const result = await fetch(url, {
+ method: "POST",
+ body: JSON.stringify(gpsCoord),
+ headers: {
+ 'Content-Type': 'application/json'
+ // 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ });
+ if (result.ok) {
+ const data = await result.json() as { height: number };
+ return data.height;
+ }else{
+ return undefined;
+ }
+}
+
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/styles/index.css b/sdnr/wt/odlux/apps/lineOfSightApp/src/styles/index.css
new file mode 100644
index 000000000..ec2585e8c
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/styles/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/styles/mapbox-gl.css b/sdnr/wt/odlux/apps/lineOfSightApp/src/styles/mapbox-gl.css
new file mode 100644
index 000000000..03c479af9
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/styles/mapbox-gl.css
@@ -0,0 +1 @@
+.mapboxgl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:rgba(0,0,0,0);text-align:left}.mapboxgl-map:-webkit-full-screen{width:100%;height:100%}.mapboxgl-canary{background-color:salmon}.mapboxgl-canvas-container.mapboxgl-interactive,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer{cursor:pointer}.mapboxgl-canvas-container.mapboxgl-interactive:active,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas{touch-action:pan-x pan-y}.mapboxgl-canvas-container.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:pinch-zoom}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:none}.mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl-bottom-right,.mapboxgl-ctrl-top-left,.mapboxgl-ctrl-top-right{position:absolute;pointer-events:none;z-index:2}.mapboxgl-ctrl-top-left{top:0;left:0}.mapboxgl-ctrl-top-right{top:0;right:0}.mapboxgl-ctrl-bottom-left{bottom:0;left:0}.mapboxgl-ctrl-bottom-right{right:0;bottom:0}.mapboxgl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.mapboxgl-ctrl-top-left .mapboxgl-ctrl{margin:10px 0 0 10px;float:left}.mapboxgl-ctrl-top-right .mapboxgl-ctrl{margin:10px 10px 0 0;float:right}.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl{margin:0 0 10px 10px;float:left}.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl{margin:0 10px 10px 0;float:right}.mapboxgl-ctrl-group{border-radius:4px;background:#fff}.mapboxgl-ctrl-group:not(:empty){-moz-box-shadow:0 0 2px rgba(0,0,0,.1);-webkit-box-shadow:0 0 2px rgba(0,0,0,.1);box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (-ms-high-contrast:active){.mapboxgl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.mapboxgl-ctrl-group button{width:29px;height:29px;display:block;padding:0;outline:none;border:0;box-sizing:border-box;background-color:transparent;cursor:pointer}.mapboxgl-ctrl-group button+button{border-top:1px solid #ddd}.mapboxgl-ctrl button .mapboxgl-ctrl-icon{display:block;width:100%;height:100%;background-repeat:no-repeat;background-position:50%}@media (-ms-high-contrast:active){.mapboxgl-ctrl-icon{background-color:transparent}.mapboxgl-ctrl-group button+button{border-top:1px solid ButtonText}}.mapboxgl-ctrl button::-moz-focus-inner{border:0;padding:0}.mapboxgl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl button:disabled{cursor:not-allowed}.mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon{opacity:.25}.mapboxgl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.mapboxgl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.mapboxgl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.mapboxgl-ctrl-group button:focus:only-child{border-radius:inherit}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23999'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23aaa'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-waiting .mapboxgl-ctrl-icon{-webkit-animation:mapboxgl-spin 2s linear infinite;-moz-animation:mapboxgl-spin 2s infinite linear;-o-animation:mapboxgl-spin 2s infinite linear;-ms-animation:mapboxgl-spin 2s infinite linear;animation:mapboxgl-spin 2s linear infinite}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23999'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23666'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}}@-webkit-keyframes mapboxgl-spin{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@-moz-keyframes mapboxgl-spin{0%{-moz-transform:rotate(0deg)}to{-moz-transform:rotate(1turn)}}@-o-keyframes mapboxgl-spin{0%{-o-transform:rotate(0deg)}to{-o-transform:rotate(1turn)}}@-ms-keyframes mapboxgl-spin{0%{-ms-transform:rotate(0deg)}to{-ms-transform:rotate(1turn)}}@keyframes mapboxgl-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}a.mapboxgl-ctrl-logo{width:88px;height:23px;margin:0 0 -4px -4px;display:block;background-repeat:no-repeat;cursor:pointer;overflow:hidden;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg opacity='.3' stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg opacity='.9' fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}a.mapboxgl-ctrl-logo.mapboxgl-compact{width:23px}@media (-ms-high-contrast:active){a.mapboxgl-ctrl-logo{background-color:transparent;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){a.mapboxgl-ctrl-logo{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23fff' stroke-width='3' fill='%23fff'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/svg%3E")}}.mapboxgl-ctrl.mapboxgl-ctrl-attrib{padding:0 5px;background-color:hsla(0,0%,100%,.5);margin:0}@media screen{.mapboxgl-ctrl-attrib.mapboxgl-compact{min-height:20px;padding:0;margin:10px;position:relative;background-color:#fff;border-radius:3px 12px 12px 3px}.mapboxgl-ctrl-attrib.mapboxgl-compact:hover{padding:2px 24px 2px 4px;visibility:visible;margin-top:6px}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:hover,.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:hover{padding:2px 4px 2px 24px;border-radius:12px 3px 3px 12px}.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner{display:none}.mapboxgl-ctrl-attrib.mapboxgl-compact:hover .mapboxgl-ctrl-attrib-inner{display:block}.mapboxgl-ctrl-attrib.mapboxgl-compact:after{content:"";cursor:pointer;position:absolute;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E");background-color:hsla(0,0%,100%,.5);width:24px;height:24px;box-sizing:border-box;border-radius:12px}.mapboxgl-ctrl-bottom-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;right:0}.mapboxgl-ctrl-top-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;right:0}.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;left:0}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;left:0}}@media screen and (-ms-high-contrast:active){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' fill='%23fff'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}@media screen and (-ms-high-contrast:black-on-white){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}.mapboxgl-ctrl-attrib a{color:rgba(0,0,0,.75);text-decoration:none}.mapboxgl-ctrl-attrib a:hover{color:inherit;text-decoration:underline}.mapboxgl-ctrl-attrib .mapbox-improve-map{font-weight:700;margin-left:2px}.mapboxgl-attrib-empty{display:none}.mapboxgl-ctrl-scale{background-color:hsla(0,0%,100%,.75);font-size:10px;border:2px solid #333;border-top:#333;padding:0 5px;color:#333;box-sizing:border-box}.mapboxgl-popup{position:absolute;top:0;left:0;display:-webkit-flex;display:flex;will-change:transform;pointer-events:none}.mapboxgl-popup-anchor-top,.mapboxgl-popup-anchor-top-left,.mapboxgl-popup-anchor-top-right{-webkit-flex-direction:column;flex-direction:column}.mapboxgl-popup-anchor-bottom,.mapboxgl-popup-anchor-bottom-left,.mapboxgl-popup-anchor-bottom-right{-webkit-flex-direction:column-reverse;flex-direction:column-reverse}.mapboxgl-popup-anchor-left{-webkit-flex-direction:row;flex-direction:row}.mapboxgl-popup-anchor-right{-webkit-flex-direction:row-reverse;flex-direction:row-reverse}.mapboxgl-popup-tip{width:0;height:0;border:10px solid transparent;z-index:1}.mapboxgl-popup-anchor-top .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-top:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip{-webkit-align-self:flex-start;align-self:flex-start;border-top:none;border-left:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip{-webkit-align-self:flex-end;align-self:flex-end;border-top:none;border-right:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-bottom:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip{-webkit-align-self:flex-start;align-self:flex-start;border-bottom:none;border-left:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip{-webkit-align-self:flex-end;align-self:flex-end;border-bottom:none;border-right:none;border-top-color:#fff}.mapboxgl-popup-anchor-left .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-left:none;border-right-color:#fff}.mapboxgl-popup-anchor-right .mapboxgl-popup-tip{-webkit-align-self:center;align-self:center;border-right:none;border-left-color:#fff}.mapboxgl-popup-close-button{position:absolute;right:0;top:0;border:0;border-radius:0 3px 0 0;cursor:pointer;background-color:transparent}.mapboxgl-popup-close-button:hover{background-color:rgba(0,0,0,.05)}.mapboxgl-popup-content{position:relative;background:#fff;border-radius:3px;box-shadow:0 1px 2px rgba(0,0,0,.1);padding:10px 10px 15px;pointer-events:auto}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content{border-top-left-radius:0}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content{border-top-right-radius:0}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content{border-bottom-left-radius:0}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content{border-bottom-right-radius:0}.mapboxgl-popup-track-pointer{display:none}.mapboxgl-popup-track-pointer *{pointer-events:none;user-select:none}.mapboxgl-map:hover .mapboxgl-popup-track-pointer{display:flex}.mapboxgl-map:active .mapboxgl-popup-track-pointer{display:none}.mapboxgl-marker{position:absolute;top:0;left:0;will-change:transform}.mapboxgl-user-location-dot,.mapboxgl-user-location-dot:before{background-color:#1da1f2;width:15px;height:15px;border-radius:50%}.mapboxgl-user-location-dot:before{content:"";position:absolute;-webkit-animation:mapboxgl-user-location-dot-pulse 2s infinite;-moz-animation:mapboxgl-user-location-dot-pulse 2s infinite;-ms-animation:mapboxgl-user-location-dot-pulse 2s infinite;animation:mapboxgl-user-location-dot-pulse 2s infinite}.mapboxgl-user-location-dot:after{border-radius:50%;border:2px solid #fff;content:"";height:19px;left:-2px;position:absolute;top:-2px;width:19px;box-sizing:border-box;box-shadow:0 0 3px rgba(0,0,0,.35)}@-webkit-keyframes mapboxgl-user-location-dot-pulse{0%{-webkit-transform:scale(1);opacity:1}70%{-webkit-transform:scale(3);opacity:0}to{-webkit-transform:scale(1);opacity:0}}@-ms-keyframes mapboxgl-user-location-dot-pulse{0%{-ms-transform:scale(1);opacity:1}70%{-ms-transform:scale(3);opacity:0}to{-ms-transform:scale(1);opacity:0}}@keyframes mapboxgl-user-location-dot-pulse{0%{transform:scale(1);opacity:1}70%{transform:scale(3);opacity:0}to{transform:scale(1);opacity:0}}.mapboxgl-user-location-dot-stale{background-color:#aaa}.mapboxgl-user-location-dot-stale:after{display:none}.mapboxgl-user-location-accuracy-circle{background-color:rgba(29,161,242,.2);width:1px;height:1px;border-radius:100%}.mapboxgl-crosshair,.mapboxgl-crosshair .mapboxgl-interactive,.mapboxgl-crosshair .mapboxgl-interactive:active{cursor:crosshair}.mapboxgl-boxzoom{position:absolute;top:0;left:0;width:0;height:0;background:#fff;border:2px dotted #202020;opacity:.5}@media print{.mapbox-improve-map{display:none}} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/utils/map.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/utils/map.ts
new file mode 100644
index 000000000..abdd27ed2
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/utils/map.ts
@@ -0,0 +1,103 @@
+/**
+ * ============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 mapboxgl from "mapbox-gl";
+import { LatLon } from "../model/LatLon";
+
+
+export const addBaseSource = (map : mapboxgl.Map, name: string) =>{
+
+ if(!map.getSource(name))
+
+ map.addSource(name, {
+ type: 'geojson',
+ data: { type: "FeatureCollection", features: [] }
+ });
+
+}
+
+export const addPoint = (map : mapboxgl.Map, point: LatLon) =>{
+ const json = `{
+ "type": "Feature",
+ "properties": {},
+ "geometry": {
+ "type": "Point",
+ "coordinates":
+ [${point.longitude}, ${point.latitude}]
+ }
+ }`;
+
+
+ (map.getSource("route") as mapboxgl.GeoJSONSource).setData(JSON.parse(json));
+}
+
+export const addBaseLayer = (map: mapboxgl.Map, sourceName: string) =>{
+
+ if(!map.getLayer('line'))
+ map.addLayer({
+ 'id': 'line',
+ 'type': 'line',
+ 'source': sourceName,
+ 'layout': {
+ 'line-join': 'round',
+ 'line-cap': 'round'
+ },
+ 'paint': {
+ 'line-color': '#88A',
+ 'line-width': 6,
+ 'line-opacity': 0.75
+ }
+ });
+
+ if(!map.getLayer('points'))
+ map.addLayer({
+ id: 'points',
+ type: 'circle',
+ source: sourceName,
+ paint: {
+ 'circle-radius': 5,
+ 'circle-color': '#223b53',
+ 'circle-stroke-color': '#225ba3',
+ 'circle-stroke-width': 3,
+ 'circle-opacity': 0.5
+ }
+ });
+}
+
+export const calculateDistanceInMeter = (lat1: number, lon1: number, lat2: number, lon2: number) => {
+ const lonRad1 = toRad(lon1);
+ const latRad1 = toRad(lat1);
+ const lonRad2 = toRad(lon2);
+ const latRad2 = toRad(lat2);
+
+ const dLon = lonRad2 - lonRad1;
+ const dLat = latRad2 - latRad1;
+ const a = Math.pow(Math.sin(dLat / 2), 2) +
+ Math.cos(latRad1) * Math.cos(latRad2) *
+ Math.pow(Math.sin(dLon / 2), 2);
+
+ const c = 2 * Math.asin(Math.sqrt(a));
+
+ return 6378 * c;
+
+ }
+
+ function toRad(value: number) {
+ return (value * Math.PI) / 180;
+ }
+ \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/utils/math.ts b/sdnr/wt/odlux/apps/lineOfSightApp/src/utils/math.ts
new file mode 100644
index 000000000..9e0447b97
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/utils/math.ts
@@ -0,0 +1,30 @@
+/**
+ * ============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==========================================================================
+ */
+
+export const max = <T,>(a: T[], p: (v: T) => Number) => a.reduce<T>((m, x) => p(m) > p(x) ? m : x, a[0]);
+export const min = <T,>(a: T[], p: (v: T) => Number) => a.reduce<T>((m, x) => p(m) < p(x) ? m : x, a[0]);
+
+export const isNumber = (value: string|null) =>{
+
+ if(!value){
+ return false;
+ }else{
+ const num = Number(value);
+ return !isNaN(num);
+ }
+ } \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src/views/main.tsx b/sdnr/wt/odlux/apps/lineOfSightApp/src/views/main.tsx
new file mode 100644
index 000000000..bbe6f34e6
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src/views/main.tsx
@@ -0,0 +1,30 @@
+/**
+ * ============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 Map from '../components/map';
+
+function MainView() {
+ return (
+ <div className="App" style={{height:"100%"}}>
+ <Map />
+ </div>
+ );
+}
+
+export default MainView; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/lineOfSightApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java
new file mode 100644
index 000000000..43b072c4b
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java
@@ -0,0 +1,68 @@
+/*
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt
+ * =================================================================================================
+ * Copyright (C) 2019 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==========================================================================
+ */
+package org.onap.ccsdk.features.sdnr.wt.odlux.bundles;
+
+import org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundle;
+import org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundleLoader;
+
+public class MyOdluxBundle extends OdluxBundle {
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ }
+
+ @Override
+ public void clean() {
+ super.clean();
+ }
+
+ @Override
+ public String getResourceFileContent(String filename) {
+ return super.getResourceFileContent(filename);
+ }
+
+ @Override
+ public boolean hasResource(String filename) {
+ return super.hasResource(filename);
+ }
+
+ @Override
+ public void setBundleName(String bundleName) {
+ super.setBundleName(bundleName);
+ }
+
+ @Override
+ public void setLoader(OdluxBundleLoader loader) {
+ super.setLoader(loader);
+ }
+
+ @Override
+ public String getBundleName() {
+ return super.getBundleName();
+ }
+
+ @Override
+ public OdluxBundleLoader getLoader() {
+ return super.getLoader();
+ }
+
+ public MyOdluxBundle() {
+ super();
+ }
+}
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/lineOfSightApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 000000000..982379dda
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -0,0 +1,9 @@
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+ <reference id="loadersvc" availability="mandatory" activation="eager" interface="org.onap.ccsdk.features.sdnr.wt.odlux.model.bundles.OdluxBundleLoader"/>
+
+ <bean id="bundle" init-method="initialize" destroy-method="clean" class="org.onap.ccsdk.features.sdnr.wt.odlux.bundles.MyOdluxBundle">
+ <property name="loader" ref="loadersvc"/>
+ <property name="bundleName" value="lineOfSightApp"/>
+ <property name="index" value="130"/>
+ </bean>
+</blueprint> \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src2/test/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/test/TestBundleRes.java b/sdnr/wt/odlux/apps/lineOfSightApp/src2/test/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/test/TestBundleRes.java
new file mode 100644
index 000000000..c319bb189
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src2/test/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/test/TestBundleRes.java
@@ -0,0 +1,46 @@
+/*
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt
+ * =================================================================================================
+ * Copyright (C) 2019 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==========================================================================
+ */
+package org.onap.ccsdk.features.sdnr.wt.odlux.bundles.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.onap.ccsdk.features.sdnr.wt.odlux.OdluxBundleLoaderImpl;
+import org.onap.ccsdk.features.sdnr.wt.odlux.bundles.MyOdluxBundle;
+
+public class TestBundleRes {
+
+ @Test
+ public void test() {
+ OdluxBundleLoaderImpl loader = OdluxBundleLoaderImpl.getInstance();
+ MyOdluxBundle b = new MyOdluxBundle();
+ b.setLoader(loader);
+ b.setIndex(0);
+ b.setBundleName("abc");
+ b.initialize();
+ assertTrue(loader.getNumberOfBundles()==1);
+ assertNotNull(b.getLoader());
+ assertEquals("abc",b.getBundleName());
+ assertTrue(b.hasResource("test.js"));
+ assertNotNull(b.getResourceFileContent("test.js"));
+ b.clean();
+ assertTrue(loader.getNumberOfBundles()==0);
+ }
+
+}
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/src2/test/resources/test.js b/sdnr/wt/odlux/apps/lineOfSightApp/src2/test/resources/test.js
new file mode 100644
index 000000000..b47fdc39f
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/src2/test/resources/test.js
@@ -0,0 +1,5 @@
+asdac sad
+as
+d
+sad
+ sadfa \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/tsconfig.json b/sdnr/wt/odlux/apps/lineOfSightApp/tsconfig.json
new file mode 100644
index 000000000..a66b5d828
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/tsconfig.json
@@ -0,0 +1,37 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./src",
+ "outDir": "./dist",
+ "sourceMap": true,
+ "forceConsistentCasingInFileNames": true,
+ "allowSyntheticDefaultImports": false,
+ "allowUnreachableCode": false,
+ "allowUnusedLabels": false,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "strictNullChecks": true,
+ "pretty": true,
+ "newLine": "LF",
+ "module": "es2015",
+ "target": "es2016",
+ "moduleResolution": "node",
+ "experimentalDecorators": true,
+ "jsx": "preserve",
+ "lib": [
+ "dom",
+ "es2015",
+ "es2016"
+ ],
+ "types": [
+ "prop-types",
+ "react",
+ "react-dom"
+ ]
+ },
+ "exclude": [
+ "dist",
+ "node_modules"
+ ]
+}
diff --git a/sdnr/wt/odlux/apps/lineOfSightApp/webpack.config.js b/sdnr/wt/odlux/apps/lineOfSightApp/webpack.config.js
new file mode 100644
index 000000000..54ba1b499
--- /dev/null
+++ b/sdnr/wt/odlux/apps/lineOfSightApp/webpack.config.js
@@ -0,0 +1,211 @@
+/**
+ * Webpack 4 configuration file
+ * see https://webpack.js.org/configuration/
+ * see https://webpack.js.org/configuration/dev-server/
+ */
+
+"use strict";
+
+const path = require("path");
+const webpack = require("webpack");
+const CopyWebpackPlugin = require("copy-webpack-plugin");
+const TerserPlugin = require('terser-webpack-plugin');
+
+// const __dirname = (path => path.replace(/^([a-z]\:)/, c => c.toUpperCase()))(process.__dirname());
+
+module.exports = (env) => {
+ const distPath = path.resolve(__dirname, env === "release" ? "." : "../..", "dist");
+ const frameworkPath = path.resolve(__dirname, env === "release" ? "../../framework" : "../..", "dist");
+ return [{
+ name: "App",
+
+ mode: "none", //disable default behavior
+
+ target: "web",
+
+ context: path.resolve(__dirname, "src"),
+
+ entry: {
+ lineOfSightApp: ["./pluginLineOfSight.tsx"]
+ },
+
+ devtool: env === "release" ? false : "source-map",
+
+ resolve: {
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
+ },
+
+ output: {
+ path: distPath,
+ filename: "[name].js",
+ library: "[name]",
+ libraryTarget: "umd2",
+ chunkFilename: "[name].js"
+ },
+ module: {
+ rules: [{
+ test: /\.tsx?$/,
+ exclude: /node_modules/,
+ use: [{
+ loader: "babel-loader"
+ }, {
+ loader: "ts-loader"
+ }]
+ }, {
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [{
+ loader: "babel-loader"
+ }]
+ },
+ {
+ test: /\.(png|gif|jpg|svg)$/,
+ use: [{
+ loader: 'url-loader',
+ options: {
+ limit: 10000,
+ name: './icons/[hash].[ext]'
+ }
+ }]
+ },
+ {
+ test: /\.css$/i,
+ use: ["style-loader", "css-loader"],
+ },
+ ]
+ },
+
+ optimization: {
+ noEmitOnErrors: true,
+ namedModules: env !== "release",
+ minimize: env === "release",
+ minimizer: env !== "release" ? [] : [new TerserPlugin({
+ terserOptions: {
+ warnings: false, // false, true, "verbose"
+ compress: {
+ drop_console: true,
+ drop_debugger: true,
+ }
+ }
+ })],
+ },
+
+ plugins: [
+ new webpack.DllReferencePlugin({
+ context: path.resolve(__dirname, "../../framework/src"),
+ manifest: require(path.resolve(frameworkPath, "vendor-manifest.json")),
+ sourceType: "umd2"
+ }),
+ new webpack.DllReferencePlugin({
+ context: path.resolve(__dirname, "../../framework/src"),
+ manifest: require(path.resolve(frameworkPath, "app-manifest.json")),
+ sourceType: "umd2"
+ }),
+ ...(env === "release") ? [
+ new webpack.DefinePlugin({
+ "process.env": {
+ NODE_ENV: "'production'",
+ VERSION: JSON.stringify(require("./package.json").version)
+ }
+ }),
+ ] : [
+ new webpack.DefinePlugin({
+ "process.env": {
+ NODE_ENV: "'development'",
+ VERSION: JSON.stringify(require("./package.json").version)
+ }
+ }),
+ new CopyWebpackPlugin([{
+ from: 'index.html',
+ to: distPath
+ }]),
+ ]
+ ],
+
+ devServer: {
+ public: "http://localhost:3100",
+ contentBase: frameworkPath,
+
+ compress: true,
+ headers: {
+ "Access-Control-Allow-Origin": "*"
+ },
+ host: "0.0.0.0",
+ port: 3100,
+ disableHostCheck: true,
+ historyApiFallback: true,
+ inline: true,
+ hot: false,
+ quiet: false,
+ stats: {
+ colors: true
+ },
+ proxy: {
+ "/yang-schema/": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/userdata": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/userdata/": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/oauth2/": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/database/": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/restconf/": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/rests/": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/topology/": {
+ target: "http://localhost:3002",
+ secure: false
+ },
+ "/sitedoc/": {
+ target: "http://10.20.35.184:3002",
+ secure: false,
+ pathRewrite(pathname) {
+ return pathname.replace(/^\/sitedoc/, '/topology/stadok')
+ }
+ },
+ "/terrain/": {
+ target: "http://10.20.11.163:5200",
+ secure: false,
+ pathRewrite(pathname) {
+ return pathname.replace(/^\/terrain/, '/')
+ }
+ },
+ "/tiles/": {
+ target: "http://tile.openstreetmap.org",
+ secure: false,
+ pathRewrite(pathname) {
+ return pathname.replace(/^\/tiles/, '')
+ }
+ },
+ "/help/": {
+ target: "http://sdnr:8181",
+ secure: false
+ },
+ "/websocket": {
+ target: "http://sdnr:8181",
+ ws: true,
+ changeOrigin: true,
+ secure: false
+ }
+ }
+
+ }
+ }];
+}
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/src/actions/commonLinkCalculationActions.ts b/sdnr/wt/odlux/apps/linkCalculationApp/src/actions/commonLinkCalculationActions.ts
index 0849058dc..d499ec209 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/src/actions/commonLinkCalculationActions.ts
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/src/actions/commonLinkCalculationActions.ts
@@ -116,60 +116,45 @@ export class UpdateWorstMonthRainAction extends Action {
}
}
-export class UpdateEIRPAction extends Action {
- constructor(public eirpA: number,public eirpB: number) {
+
+export class UpdateAntennaGainAction extends Action {
+ constructor(public antennaGainA: number, public antennaGainB: number) {
super();
}
}
-export class UpdateAntennaGainAction extends Action {
- constructor(public antennaGainList: string[]) {
+export class updateAntennaNameAction extends Action {
+ constructor(public antennaNameA: string, public antennaNameB: string) {
super();
}
}
-export class UpdateAntennaListAction extends Action {
- constructor(public antennaList: string[]) {
+export class UpdateTxPowerAction extends Action {
+ constructor(public txPowerA: string | null, public txPowerB: string | null) {
super();
}
}
-export class UpdateAntennaAction extends Action {
- constructor(public antennaA: string | null, public antennaB : string | null) {
+export class UpdateRxSensitivityAction extends Action {
+ constructor(public rxSensitivityA: string | null, public rxSensitivityB: string | null) {
super();
}
}
-export class UpdateRadioAttributesAction extends Action {
- constructor(public som: number , public eirpA : number, public eirpB : number) {
+export class UpdateWaveguideLossAction extends Action {
+ constructor(public waveguideLossA: number, public waveguideLossB: number) {
super();
}
}
-export class UpdateTxPowerAction extends Action {
- constructor(public txPowerA: string | null , public txPowerB : string | null) {
+
+export class UpdateEIRPAction extends Action {
+ constructor(public eirpA: number, public eirpB: number) {
super();
}
}
-export class UpdateRxSensitivityAction extends Action {
- constructor(public rxSensitivityA: string | null , public rxSensitivityB : string | null) {
+export class UpdateRxPowerAction extends Action {
+ constructor(public rxPowerA: number, public rxPowerB: number) {
super();
}
}
-
-
-export const updateAntennaList = (frequency: number) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => {
- let antennaList: string[] = []
- let antennaDiameterList: string[] = []
- let antennaGainList :string[] =[]
- //switch case here frequency = 26? antennaList.push
- switch (frequency) {
- case 7: antennaList.push('ANDREW VHLPX2.5-7W', 'ANDREW VHLPX3-7W', 'ANDREW VHLPX4-7W', 'ANDREW VHLPX6-7W' ), antennaDiameterList.push('0.6','0.9','1.2','1.8'), antennaGainList.push('33.90','35.50','37.30','40.61'); break
- case 11: antennaList.push('ANDREW VHLPX2-11W', 'ANDREW VHLPX3-11W', 'ANDREW VHLPX4-11W'), antennaDiameterList.push('0.6','0.9','1.2'), antennaGainList.push('34.50','38.4','40.70');break
- case 15: antennaList.push('ANDREW VHLPX1-15', 'ANDREW VHLPX2-15', 'ANDREW VHLPX3-15', 'ANDREW VHLPX4-15'), antennaDiameterList.push('0.3','0.6','0.9','1.2'), antennaGainList.push('32.00','36.80','41.11','42.90');break
- case 23: antennaList.push('ANDREW VHLPX1-23', 'ANDREW VHLPX2-23', 'ANDREW VHLPX3-23', 'ANDREW VHLPX4-23'), antennaDiameterList.push('0.3','0.6','0.9','1.2'), antennaGainList.push('35.30','40.21','44.80','46.71');break
- case 26: antennaList.push('ANDREW VHLPX1-26', 'ANDREW VHLPX2-26', 'ANDREW VHLPX3-26'), antennaDiameterList.push('0.3','0.6','0.9'), antennaGainList.push('36.61','40.21','41.21','45.80');break
- case 28: antennaList.push('ANDREW VHLPX1-28', 'ANDREW VHLPX2-28'), antennaDiameterList.push('0.3','0.6'), antennaGainList.push('38.11','42.21');break
- case 38: antennaList.push('ANDREW VHLPX1-38', 'ANDREW VHLPX2-38'), antennaDiameterList.push('0.3','0.6'), antennaGainList.push('40.11','45.21');break
- case 42: antennaList.push('ANDREW VHLPX1-42-XXX/D', 'ANDREW VHLPX2-42-XXX/A'), antennaDiameterList.push('0.3','0.6'), antennaGainList.push('40.80','46.00');break
- case 80: antennaList.push('Radio Waves HPCPE-80', 'Radio Waves HPLP2-80'), antennaDiameterList.push('0.3','0.6'), antennaGainList.push('43.80','50.80');break
- }
- dispatcher(new UpdateAntennaListAction(antennaList))
- dispatcher(new UpdateAntennaGainAction(antennaGainList))
+export class UpdateSomAction extends Action {
+ constructor(public somA: number, public somB:number) {
+ super();
+ }
}
-
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/src/handlers/linkCalculationAppRootHandler.ts b/sdnr/wt/odlux/apps/linkCalculationApp/src/handlers/linkCalculationAppRootHandler.ts
index edfad052a..01512eb92 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/src/handlers/linkCalculationAppRootHandler.ts
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/src/handlers/linkCalculationAppRootHandler.ts
@@ -21,7 +21,7 @@ import { combineActionHandler } from '../../../../framework/src/flux/middleware'
// ** do not remove **
import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
import { IActionHandler } from '../../../../framework/src/flux/action';;
-import { UpdateLinkIdAction, UpdateFrequencyAction , UpdateLatLonAction, UpdateRainAttAction, UpdateRainValAction, updateHideForm, UpdateFslCalculation, UpdateSiteAction, UpdateDistanceAction, isCalculationServerReachableAction, UpdatePolAction, updateAltitudeAction, UpdateAbsorptionLossAction, UpdateWorstMonthRainAction, UpdateEIRPAction, UpdateAntennaAction, UpdateAntennaListAction, UpdateAntennaGainAction, UpdateTxPowerAction, UpdateRxSensitivityAction} from '../actions/commonLinkCalculationActions';
+import { UpdateLinkIdAction, UpdateFrequencyAction , UpdateLatLonAction, UpdateRainAttAction, UpdateRainValAction, updateHideForm, UpdateFslCalculation, UpdateSiteAction, UpdateDistanceAction, isCalculationServerReachableAction, UpdatePolAction, updateAltitudeAction, UpdateAbsorptionLossAction, UpdateWorstMonthRainAction, UpdateEIRPAction, UpdateAntennaGainAction, UpdateTxPowerAction, UpdateRxSensitivityAction, updateAntennaNameAction, UpdateWaveguideLossAction, UpdateRxPowerAction, UpdateSomAction} from '../actions/commonLinkCalculationActions';
declare module '../../../../framework/src/store/applicationStore' {
interface IApplicationStoreState {
@@ -60,15 +60,18 @@ export type ILinkCalculationAppStateState= {
eirpB: number,
antennaGainA: number,
antennaGainB :number,
- antennaList:string[],
- antennaGainList:string[],
- antennaA: string,
- antennaB:string,
- systemOperatingMargin : number,
+ antennaNameA: string,
+ antennaNameB:string,
+ systemOperatingMarginA : number,
+ systemOperatingMarginB : number,
txPowerA : string,
txPowerB: string,
rxSensitivityA : string,
- rxSensitivityB: string
+ rxSensitivityB: string,
+ waveguideLossA : number,
+ waveguideLossB: number,
+ rxPowerA :number,
+ rxPowerB: number
}
const initialState: ILinkCalculationAppStateState ={
@@ -98,19 +101,20 @@ const initialState: ILinkCalculationAppStateState ={
eirpB: 0,
antennaGainA :0,
antennaGainB :0,
- antennaList:[],
- antennaGainList:[],
- antennaA: '0',
- antennaB:'0',
- systemOperatingMargin : 0,
+ antennaNameA: '',
+ antennaNameB:'',
+ systemOperatingMarginA : 0,
+ systemOperatingMarginB : 0,
txPowerA : '0',
txPowerB: '0',
rxSensitivityA: '0',
- rxSensitivityB: '0'
+ rxSensitivityB: '0',
+ waveguideLossA : 0,
+ waveguideLossB: 0,
+ rxPowerA : 0,
+ rxPowerB: 0
}
-
-
export const LinkCalculationHandler: IActionHandler<ILinkCalculationAppStateState> = (state=initialState, action) => {
if(action instanceof UpdateLinkIdAction){
@@ -156,17 +160,12 @@ export const LinkCalculationHandler: IActionHandler<ILinkCalculationAppStateStat
else if (action instanceof UpdateWorstMonthRainAction){
state = Object.assign({}, state, {month:action.month})
}
- else if (action instanceof UpdateEIRPAction){
- state = Object.assign({}, state, {eirpA:action.eirpA, eirpB:action.eirpB})
- }
+
else if (action instanceof UpdateAntennaGainAction){
- state = Object.assign({}, state, {antennaGainList:action.antennaGainList})
- }
- else if (action instanceof UpdateAntennaListAction){
- state = Object.assign({}, state, {antennaList:action.antennaList})
+ state = Object.assign({}, state, {antennaGainA:action.antennaGainA,antennaGainB:action.antennaGainB})
}
- else if (action instanceof UpdateAntennaAction){
- state = Object.assign({}, state, {antennaA:action.antennaA == null ? state.antennaA : action.antennaA , antennaB: action.antennaB == null? state.antennaB : action.antennaB})
+ else if (action instanceof updateAntennaNameAction){
+ state = Object.assign({}, state, {antennaNameA:action.antennaNameA, antennaNameB: action.antennaNameB})
}
else if (action instanceof UpdateTxPowerAction){
state = Object.assign({}, state, {txPowerA:action.txPowerA == null ? state.txPowerA : action.txPowerA , txPowerB: action.txPowerB == null? state.txPowerB : action.txPowerB})
@@ -174,6 +173,19 @@ export const LinkCalculationHandler: IActionHandler<ILinkCalculationAppStateStat
else if (action instanceof UpdateRxSensitivityAction){
state = Object.assign({}, state, {rxSensitivityA:action.rxSensitivityA == null ? state.rxSensitivityA : action.rxSensitivityA , rxSensitivityB: action.rxSensitivityB == null? state.rxSensitivityB : action.rxSensitivityB})
}
+ else if (action instanceof UpdateWaveguideLossAction){
+ state = Object.assign({}, state, {waveguideLossA:action.waveguideLossA, waveguideLossB: action.waveguideLossB})
+ }
+ else if (action instanceof UpdateEIRPAction){
+ state = Object.assign({}, state, {eirpA:action.eirpA, eirpB:action.eirpB})
+ }
+ else if (action instanceof UpdateRxPowerAction){
+ state = Object.assign({}, state, {rxPowerA:action.rxPowerA, rxPowerB:action.rxPowerB})
+ }
+ else if (action instanceof UpdateSomAction){
+ state = Object.assign({}, state, {systemOperatingMarginA:action.somA , systemOperatingMarginB :action.somB})
+ }
+
return state
}
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/src/index.html b/sdnr/wt/odlux/apps/linkCalculationApp/src/index.html
index 2023ae365..edcbd2514 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/src/index.html
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/src/index.html
@@ -15,10 +15,11 @@
<script type="text/javascript" src="./config.js"></script>
<script>
// run the application
- require(["app","connectApp", "linkCalculationApp", "networkMapApp"], function (app, connectApp, linkCalculationApp,networkMapApp) {
+ require(["app","connectApp", "linkCalculationApp", "networkMapApp" , "lineOfSightApp"], function (app, connectApp, linkCalculationApp,networkMapApp, lineOfSightApp) {
connectApp.register();
linkCalculationApp.register();
networkMapApp.register();
+ lineOfSightApp.register();
app("./app.tsx").runApplication();
});
</script>
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/src/pluginLinkCalculation.tsx b/sdnr/wt/odlux/apps/linkCalculationApp/src/pluginLinkCalculation.tsx
index f86b22a5c..a15bf033d 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/src/pluginLinkCalculation.tsx
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/src/pluginLinkCalculation.tsx
@@ -24,11 +24,11 @@ import { withRouter, RouteComponentProps, Route, Switch, Redirect } from 'react-
import { faBookOpen } from '@fortawesome/free-solid-svg-icons'; // select app icon
import applicationManager from '../../../framework/src/services/applicationManager';
-import LinkCalculation from './views/linkCalculationComponent';
+import LinkCalculation from './views/linkCalculationComponent';
import LinkCalculationAppRootHandler from './handlers/linkCalculationAppRootHandler';
import connect, { Connect, IDispatcher } from '../../../framework/src/flux/connect';
import { IApplicationStoreState } from "../../../framework/src/store/applicationStore";
-import { UpdateLinkIdAction, UpdateLatLonAction, updateHideForm, UpdateSiteAction, UpdateDistanceAction, isCalculationServerReachableAction, updateAltitudeAction } from "./actions/commonLinkCalculationActions";
+import { UpdateLinkIdAction, UpdateLatLonAction, updateHideForm, UpdateSiteAction, UpdateDistanceAction, isCalculationServerReachableAction, updateAltitudeAction, updateAntennaNameAction, UpdateAntennaGainAction, UpdateWaveguideLossAction } from "./actions/commonLinkCalculationActions";
let currentLinkId: string | null = null;
@@ -41,23 +41,29 @@ const mapProps = (state: IApplicationStoreState) => ({
const mapDisp = (dispatcher: IDispatcher) => ({
updateLinkId: (mountId: string) => dispatcher.dispatch(new UpdateLinkIdAction(mountId)),
- updateSiteName: (siteNameA?:any, siteNameB?:any)=>{
+ updateSiteName: (siteNameA?: any, siteNameB?: any) => {
dispatcher.dispatch(new UpdateSiteAction(siteNameA, siteNameB))
},
- updateDistance :(distance:number) =>{
+ updateDistance: (distance: number) => {
dispatcher.dispatch(new UpdateDistanceAction(distance))
},
- updateLatLon : (Lat1:number, Lon1:number, Lat2:number, Lon2:number)=> {
+ updateLatLon: (Lat1: number, Lon1: number, Lat2: number, Lon2: number) => {
dispatcher.dispatch(new UpdateLatLonAction(Lat1, Lon1, Lat2, Lon2))
- dispatcher.dispatch(new updateHideForm (true))
+ dispatcher.dispatch(new updateHideForm(true))
},
- updateAltitude : (amslA:number, aglA:number, amslB:number, aglB:number) => {
- dispatcher.dispatch(new updateAltitudeAction(amslA,aglA,amslB,aglB))
+ updateAltitude: (amslA: number, aglA: number, amslB: number, aglB: number) => {
+ dispatcher.dispatch(new updateAltitudeAction(amslA, aglA, amslB, aglB))
+ },
+ updateAntennaName: (antennaNameA: string, antennaNameB: string) => {
+ dispatcher.dispatch(new updateAntennaNameAction(antennaNameA, antennaNameB))
+ },
+ updateAntennaGainAction: (antennaGainA: number, antennaGainB: number) => {
+ dispatcher.dispatch(new UpdateAntennaGainAction(antennaGainA, antennaGainB))
+ },
+ updateWaveguideLossAction: (waveguideLossA: number, waveguideLossB: number) => {
+ dispatcher.dispatch(new UpdateWaveguideLossAction(waveguideLossA, waveguideLossB))
}
- // UpdateConectivity : (reachable:boolean) => {
- // dispatcher.dispatch (new isCalculationServerReachableAction (reachable))
- // }
});
@@ -70,45 +76,68 @@ const LinkCalculationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComp
lastUrl = props.location.pathname;
linkId = getLinkId(lastUrl);
- const data= props.location.search
+ const data = props.location.search
+
-
- if (data !== undefined && data.length>0){
+ if (data !== undefined && data.length > 0) {
-
- const lat1 = data.split('&')[0].split('=')[1]
- const lon1 = data.split('&')[1].split('=')[1]
- const lat2 = data.split('&')[2].split('=')[1]
- const lon2 = data.split('&')[3].split('=')[1]
- const siteNameA = data.split('&')[4].split('=')[1]
- const siteNameB = data.split('&')[5].split('=')[1]
+ const lat1 = data.split('&')[0].split('=')[1]
+ const lon1 = data.split('&')[1].split('=')[1]
+ const lat2 = data.split('&')[2].split('=')[1]
+ const lon2 = data.split('&')[3].split('=')[1]
- const distance = data.split('&')[8].split('=')[1]
+ const siteNameA = data.split('&')[4].split('=')[1]
+ const siteNameB = data.split('&')[5].split('=')[1]
- const amslA = data.split('&')[9].split('=')[1]
- const aglA = data.split('&')[10].split('=')[1]
+ const distance = data.split('&')[8].split('=')[1]
- const amslB = data.split('&')[11].split('=')[1]
- const aglB = data.split('&')[12].split('=')[1]
+ const amslA = data.split('&')[9].split('=')[1]
+ const aglA = data.split('&')[10].split('=')[1]
+ const amslB = data.split('&')[11].split('=')[1]
+ const aglB = data.split('&')[12].split('=')[1]
- props.updateSiteName(String(siteNameA), String(siteNameB))
+ const antennaNameA = data.split('&')[13].split('=')[1].replace("%20", " ")
+ const antennaGainA = data.split('&')[14].split('=')[1]
+ const waveguideLossA = data.split('&')[15].split('=')[1]
+ const antennaNameB = data.split('&')[16].split('=')[1].replace("%20", " ")
+ const antennaGainB = data.split('&')[17].split('=')[1]
+ const waveguideLossB = data.split('&')[18].split('=')[1]
- props.updateDistance(Number(distance))
- props.updateLatLon(Number(lat1),Number(lon1),Number(lat2),Number(lon2))
+ if (siteNameA !== null && siteNameB !== null) {
+ props.updateSiteName(String(siteNameA), String(siteNameB))
+ }
- props.updateAltitude (Number(amslA), Number(aglA), Number(amslB), Number(aglB))
-
+ if (Number(distance) !== null) {
+ props.updateDistance(Number(distance))
+ }
+ if (Number(lat1) >= -90 && Number(lat2) >= -90 && Number(lat1) <= 90 && Number(lat2) <= 90 && Number(lon1) >= -180 && Number(lon2) >= -180 && Number(lon1) <= 180 && Number(lon2) <= 180) {
+ props.updateLatLon(Number(lat1), Number(lon1), Number(lat2), Number(lon2))
+ }
+ if (Number(amslA)> 0 && Number(amslB)> 0) {
+ if (Number(aglA)>= Number(amslA) && Number(aglB)>= Number(amslB)) {
+ props.updateAltitude(Number(amslA), Number(aglA), Number(amslB), Number(aglB))
+ }
+ }
+ if (antennaNameA && antennaNameB.length) {
+ props.updateAntennaName(String(antennaNameA), String(antennaNameB))
+ }
+ if (Number(antennaGainA) > 0 && Number(antennaGainA) > 0) {
+ props.updateAntennaGainAction(Number(antennaGainA), Number(antennaGainB))
+ }
+ if(Number(waveguideLossA) !== null, Number(waveguideLossB) !== null){
+ props.updateWaveguideLossAction(Number(waveguideLossA),Number(waveguideLossB) )
+ }
}
-
+
if (currentLinkId !== linkId) { // new element is loaded
currentLinkId = linkId;
props.updateLinkId(currentLinkId);
- }
+ }
}, []);
// called when component gets updated
@@ -126,7 +155,7 @@ const LinkCalculationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComp
const getLinkId = (lastUrl: string) => {
let index = lastUrl.lastIndexOf("linkCalculation/");
if (index >= 0) {
- linkId = lastUrl.substr(index+16);
+ linkId = lastUrl.substr(index + 16);
} else {
linkId = "";
}
@@ -134,7 +163,7 @@ const LinkCalculationRouteAdapter = connect(mapProps, mapDisp)((props: RouteComp
return linkId;
}
-
+
return (
<LinkCalculation />
);
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx b/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx
index 063926269..e3eaa6ba0 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/src/views/linkCalculationComponent.tsx
@@ -24,7 +24,7 @@ import { TextField, Tabs, Tab, Typography, AppBar, Button, Tooltip, Checkbox, Ta
import './Style.scss'
import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
-import { UpdateFrequencyAction, UpdateLatLonAction, UpdateRainAttAction, UpdateRainValAction, UpdateFslCalculation, isCalculationServerReachableAction, UpdatePolAction, UpdateDistanceAction, updateAltitudeAction, UpdateAbsorptionLossAction, UpdateWorstMonthRainAction, updateAntennaList, UpdateAntennaAction, UpdateRadioAttributesAction, UpdateTxPowerAction, UpdateRxSensitivityAction } from "../actions/commonLinkCalculationActions";
+import { UpdateFrequencyAction, UpdateLatLonAction, UpdateRainAttAction, UpdateRainValAction, UpdateFslCalculation, isCalculationServerReachableAction, UpdatePolAction, UpdateDistanceAction, updateAltitudeAction, UpdateAbsorptionLossAction, UpdateWorstMonthRainAction, UpdateTxPowerAction, UpdateRxSensitivityAction, UpdateEIRPAction, UpdateRxPowerAction, UpdateSomAction } from "../actions/commonLinkCalculationActions";
import { faPlaneArrival, faAlignCenter } from "@fortawesome/free-solid-svg-icons";
import ConnectionInfo from '../components/connectionInfo'
import { red } from "@material-ui/core/colors";
@@ -55,15 +55,22 @@ const mapProps = (state: IApplicationStoreState) => ({
absorptionOxygen: state.linkCalculation.calculations.absorptionOxygen,
absorptionWater: state.linkCalculation.calculations.absorptionWater,
month: state.linkCalculation.calculations.month,
- eirpSiteA: state.linkCalculation.calculations.eirpA,
- eirpSiteB: state.linkCalculation.calculations.eirpB,
+ eirpA: state.linkCalculation.calculations.eirpA,
+ eirpB: state.linkCalculation.calculations.eirpB,
antennaGainA: state.linkCalculation.calculations.antennaGainA,
antennaGainB: state.linkCalculation.calculations.antennaGainB,
- antennaList: state.linkCalculation.calculations.antennaList,
- antennaGainList: state.linkCalculation.calculations.antennaGainList,
- antennaA: state.linkCalculation.calculations.antennaA,
- antennaB: state.linkCalculation.calculations.antennaB,
- systemOperatingMargin : state.linkCalculation.calculations.systemOperatingMargin
+ antennaNameA: state.linkCalculation.calculations.antennaNameA,
+ antennaNameB: state.linkCalculation.calculations.antennaNameB,
+ systemOperatingMarginA: state.linkCalculation.calculations.systemOperatingMarginA,
+ systemOperatingMarginB: state.linkCalculation.calculations.systemOperatingMarginB,
+ waveguideLossA: state.linkCalculation.calculations.waveguideLossA,
+ waveguideLossB: state.linkCalculation.calculations.waveguideLossB,
+ rxPowerA: state.linkCalculation.calculations.rxPowerA,
+ rxPowerB: state.linkCalculation.calculations.rxPowerB,
+ txPowerA: state.linkCalculation.calculations.txPowerA,
+ txPowerB: state.linkCalculation.calculations.txPowerB,
+ rxSensitivityA: state.linkCalculation.calculations.rxSensitivityA,
+ rxSensitivityB: state.linkCalculation.calculations.rxSensitivityB
});
@@ -74,7 +81,6 @@ const mapDispatch = (dispatcher: IDispatcher) => ({
updateFrequency: (frequency: number) => {
dispatcher.dispatch(new UpdateFrequencyAction(frequency))
- dispatcher.dispatch(updateAntennaList(frequency))
},
updateLatLon: (Lat1: number, Lon1: number, Lat2: number, Lon2: number) => {
dispatcher.dispatch(new UpdateLatLonAction(Lat1, Lon1, Lat2, Lon2))
@@ -114,16 +120,20 @@ const mapDispatch = (dispatcher: IDispatcher) => ({
UpdateWorstMonthRain: (month: string) => {
dispatcher.dispatch(new UpdateWorstMonthRainAction(month))
},
- UpdateAntenas: (antennaA: string | null, antennaB: string | null) => {
- dispatcher.dispatch(new UpdateAntennaAction(antennaA, antennaB))
+ UpdateEIRP: (eirpA: number, eirpB: number) => {
+ dispatcher.dispatch(new UpdateEIRPAction(eirpA, eirpB))
},
- UpdateRadioAttributes :(som: number, eirpA: number, eirpB: number)=>{
- dispatcher.dispatch(new UpdateRadioAttributesAction(som,eirpA, eirpB))
+ UpdateRxPower: (rxPowerA: number, rxPowerB: number) => {
+ dispatcher.dispatch(new UpdateRxPowerAction(rxPowerA, rxPowerB))
},
- UpdateTxPower :(txPowerA: string | null, txPowerB: string | null)=>{
+ UpdateSom: (somA: number, somB: number) => {
+ dispatcher.dispatch(new UpdateSomAction(somA, somB))
+ },
+
+ UpdateTxPower: (txPowerA: string | null, txPowerB: string | null) => {
dispatcher.dispatch(new UpdateTxPowerAction(txPowerA, txPowerB))
- },
- UpdateRxSensitivity :(rxSensitivityA : string | null, rxSensitivityB : string | null)=>{
+ },
+ UpdateRxSensitivity: (rxSensitivityA: string | null, rxSensitivityB: string | null) => {
dispatcher.dispatch(new UpdateRxSensitivityAction(rxSensitivityA, rxSensitivityB))
}
});
@@ -145,6 +155,7 @@ interface initialState {
attenuationMethodError: string,
worstmonth: boolean,
showWM: string,
+ rainMethodErrorState: string
}
class LinkCalculation extends React.Component<linkCalculationProps, initialState> {
@@ -164,6 +175,7 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
antennaTypeError: '',
worstmonth: false,
showWM: '',
+ rainMethodErrorState: '0'
};
}
@@ -246,10 +258,10 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
}
}
- linkBudget = (antennaA: string, antennaB: string, transmissionPowerA: number, transmissionPowerB: number) => {
- fetch(BASE_URL + '/linkbudget/' + antennaA + '/' + antennaB + '/' + transmissionPowerA + '/' + transmissionPowerB)
- .then(res=>res.json())
- .then(result => {this.props.UpdateRadioAttributes(result.systemOperatingMargin, result.eirpA, result.eirpB)})
+ linkBudget = (lat1: number, lon1: number, lat2: number, lon2: number, distance: number, frequency: number, absorptionMethod: string, polarization: string, antennaGainA: number, antennaGainB: number, waveguideLossA: number, waveguideLossB: number, transmissionPowerA: number, transmissionPowerB: number, rxSensitivityA: number, rxSensitivityB: number) => {
+ fetch(BASE_URL + '/linkbudget/' + lat1 + ',' + lon1 + ',' + lat2 + ',' + lon2 + '/' + distance + '/' + frequency + '/' + absorptionMethod + '/' + polarization.toUpperCase() + '/' + antennaGainA + '/' + antennaGainB + '/' + waveguideLossA + '/' + waveguideLossB + '/' + transmissionPowerA + '/' + transmissionPowerB + '/' + rxSensitivityA + '/' + rxSensitivityB)
+ .then(res => res.json())
+ .then(result => { this.props.UpdateEIRP(result.eirpA, result.eirpB); this.props.UpdateRxPower(result.receivedPowerA, result.receivedPowerB); this.props.UpdateSom(result.systemOperatingMarginA, result.systemOperatingMarginA) })
}
formValid = () => {
@@ -260,7 +272,10 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
this.props.lon2 === 0 ? this.setState({ longitude2Error: 'Enter a number between -180 to 180' }) : null
this.props.frequency === 0 ? this.setState({ frequencyError: 'Select a frequency' }) : this.setState({ frequencyError: '' })
- this.state.rainMethodDisplay === null && this.props.rainVal === 0 ? this.setState({ rainMethodError: 'Select the rain method' }) : this.setState({ rainMethodError: '' })
+ // this.state.rainMethodDisplay === null && this.props.rainVal === 0 ? this.setState({ rainMethodError: 'Select the rain method' }) : this.setState({ rainMethodError: '' })
+
+ this.state.rainMethodErrorState === '0' ? this.setState({ rainMethodError: 'Select the rain method' }) : this.setState({ rainMethodError: '' })
+
this.state.absorptionMethod === '0' ? this.setState({ attenuationMethodError: 'Select the attenuation method' }) : this.setState({ attenuationMethodError: '' })
console.log(this.state);
console.log(this.props.lat1 !== 0 && this.props.lat2 !== 0 && this.props.lon1 !== 0 && this.props.lon2 !== 0 && this.props.frequency !== 0 && this.state.rainMethodError === '' && this.state.attenuationMethodError === '');
@@ -282,21 +297,18 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
this.setState({ showWM: ' ' })
this.props.UpdateWorstMonthRain('')
this.AbsorptionAtt(this.props.lat1, this.props.lon1, this.props.lat2, this.props.lon2, this.props.distance, this.props.frequency, this.state.worstmonth, this.state.absorptionMethod)
+ this.linkBudget(this.props.lat1, this.props.lon1, this.props.lat2, this.props.lon2, this.props.distance, this.props.frequency, this.state.absorptionMethod, this.props.polarization!, this.props.antennaGainA, this.props.antennaGainB, this.props.waveguideLossA, this.props.waveguideLossB, Number(this.props.txPowerA), Number(this.props.txPowerB), Number(this.props.rxSensitivityA), Number(this.props.rxSensitivityB))
if (this.state.rainMethodDisplay === true) {
this.manualRain(this.props.rainVal, this.props.frequency, this.props.distance, this.props.polarization!);
}
else {
- // this.updateRainValue(this.props.lat1, this.props.lon1, this.props.lat2, this.props.lon2, this.state.worstmonth)
this.rainAttCal(this.props.lat1, this.props.lon1, this.props.lat2, this.props.lon2, this.props.frequency, this.props.distance, this.props.polarization!, this.state.worstmonth);
}
}
else {
this.AbsorptionAtt(this.props.lat1, this.props.lon1, this.props.lat2, this.props.lon2, this.props.distance, this.props.frequency, this.state.worstmonth, this.state.absorptionMethod)
-
- // this.updateRainValue(this.props.lat1, this.props.lon1, this.props.lat2, this.props.lon2, this.state.worstmonth)
-
this.rainAttCal(this.props.lat1, this.props.lon1, this.props.lat2, this.props.lon2, this.props.frequency, this.props.distance, this.props.polarization!, this.state.worstmonth);
}
}
@@ -432,7 +444,7 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
</div>
<div>
<div style={{ marginTop: 5 }}>Frequency</div>
- <div style={{ marginTop: 5 }}> {<select aria-label="select-frequency-in-ghz" className={this.state.frequencyError.length > 0 ? 'error' : 'input'} onChange={(e) => { this.props.updateFrequency(Number(e.target.value)); e.target.value === '0' ? this.setState({ frequencyError: 'select a frequency' }) : this.setState({ frequencyError: '' }); this.props.UpdateAntenas('0', '0') }}>
+ <div style={{ marginTop: 5 }}> {<select aria-label="select-frequency-in-ghz" className={this.state.frequencyError.length > 0 ? 'error' : 'input'} onChange={(e) => { this.props.updateFrequency(Number(e.target.value)); e.target.value === '0' ? this.setState({ frequencyError: 'select a frequency' }) : this.setState({ frequencyError: '' }) }}>
<option value='0' aria-label="none-value" >Select Freq</option>
<option value='7' aria-label="7" >7 GHz</option>
@@ -452,7 +464,7 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
</div>
<div>
<div>Rain Model</div>
- <div> {<select aria-label="select-rain-method" className={this.state.rainMethodError.length > 0 ? 'error' : 'input'} onChange={(e) => { e.target.value === 'itu' ? this.setState({ rainMethodDisplay: false }) : this.setState({ rainMethodDisplay: true }); e.target.value === '0' ? this.setState({ rainMethodError: 'select a Rain model' }) : this.setState({ rainMethodError: '' }) }}>
+ <div> {<select aria-label="select-rain-method" className={this.state.rainMethodError.length > 0 ? 'error' : 'input'} onChange={(e) => {if (e.target.value !== '') { this.setState({ rainMethodErrorState: e.target.value, rainMethodError: '' }) }; e.target.value === 'itu' ? this.setState({ rainMethodDisplay: false }) : this.setState({ rainMethodDisplay: true }) }}>
<option value='0' aria-label="none-value" >Select Rain Method</option>
<option value='itu' aria-label="itur8377">ITU-R P.837-7</option>
<option value='manual' aria-label="manual-entry">Specific Rain</option>
@@ -471,7 +483,7 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
</div>
<div>
<div>Absorption Model</div>
- <div> {<select aria-label="select-absorption-method" className={this.state.attenuationMethodError.length > 0 ? 'error' : 'input'} onChange={(e) => { if (e.target.value !== '') { this.setState({ absorptionMethod: e.target.value }); this.setState({ attenuationMethodError: '' }) } }}>
+ <div> {<select aria-label="select-absorption-method" className={this.state.attenuationMethodError.length > 0 ? 'error' : 'input'} onChange={(e) => { if (e.target.value !== '') { this.setState({ absorptionMethod: e.target.value, attenuationMethodError: '' }) } }}>
<option value='0' aria-label="none-value" >Select Absorption Method</option>
<option value='ITURP67612' aria-label="iturp67612" >ITU-R P.676-12</option>
<option value='ITURP67611' aria-label="iturp67611" >ITU-R P.676-11</option>
@@ -489,14 +501,15 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
</div>
<div>
<div>System Operating Margin</div>
- <div aria-label="system-operating-margin">{this.props.systemOperatingMargin} dB</div>
+ <div aria-label="system-operating-margin">{this.props.systemOperatingMarginA.toFixed(3)} dB</div>
+ <div aria-label="system-operating-margin">{this.props.systemOperatingMarginB.toFixed(3)} dB</div>
</div>
<div>
- <div>Radio Transmitted Power</div>
- <div> {<form><input aria-label="site-a-transmitted-power" type="number" style={{ width: 70, height: 15, fontSize: 14 }} onChange={(e) => {if (e.target.value !== '') this.props.UpdateTxPower(e.target.value,null) }}
+ <div>Radio Transmitted Power</div>
+ <div> {<form><input aria-label="site-a-transmitted-power" type="number" style={{ width: 70, height: 15, fontSize: 14 }} onChange={(e) => { if (e.target.value !== '') this.props.UpdateTxPower(e.target.value, null) }}
>
</input> dBm </form>} </div>
- <div> {<form><input aria-label="site-b-transmitted-power" type="number" style={{ width: 70, height: 15, fontSize: 14 }} onChange={(e) => { if (e.target.value !== '') this.props.UpdateTxPower(null,e.target.value) }}
+ <div> {<form><input aria-label="site-b-transmitted-power" type="number" style={{ width: 70, height: 15, fontSize: 14 }} onChange={(e) => { if (e.target.value !== '') this.props.UpdateTxPower(null, e.target.value) }}
>
</input> dBm </form>} </div>
</div>
@@ -509,41 +522,43 @@ class LinkCalculation extends React.Component<linkCalculationProps, initialState
>
</input> dBm </form>} </div>
</div>
+ <div>
+ <div>Rx power</div>
+ <div aria-label="site-a-effective-isotropic-radiated-power">{this.props.rxPowerA.toFixed(3)} dBm</div>
+ <div aria-label="site-b-effective-isotropic-radiated-power">{this.props.rxPowerB.toFixed(3)} dBm</div>
+ </div>
</div>
<div className='antennaContainer'>
<div>
<div></div>
<div className='antennaFont'>Antenna Settings</div>
</div>
+
<div>
<div>Antenna</div>
+ <div aria-label="site-a-amsl">{this.props.antennaNameA} </div>
+ <div aria-label="site-b-amsl">{this.props.antennaNameB}</div>
+ </div>
- <div> {<select aria-label="site-a-select-antenna" value={this.props.antennaA} style={{ width: 160, height: 22, fontSize: 13 }} className={this.state.antennaTypeError.length > 0 ? 'error' : 'input'} onChange={(e) => { if (e.target.value !== '') { this.props.UpdateAntenas(e.target.value, null); this.setState({ antennaTypeError: '' }) } }}>
- <option value='0' aria-label="none-value" >Select Antenna</option>
- {this.props.antennaList.map(antenna => <option value={antenna}>{antenna}</option>)}
-
- </select>} <div style={{ fontSize: 12, color: 'red' }}>{this.state.antennaTypeError}</div>
- </div>
- <div> {<select aria-label="site-b-select-antenna" value={this.props.antennaB} style={{ width: 160, height: 22, fontSize: 13 }} className={this.state.antennaTypeError.length > 0 ? 'error' : 'input'} onChange={(e) => { if (e.target.value !== '') { this.props.UpdateAntenas(null, e.target.value); this.setState({ antennaTypeError: '' }) } }}>
- <option value='0' aria-label="none-value" >Select Antenna</option>
- {this.props.antennaList.map(antenna => <option value={antenna}>{antenna}</option>)}
- </select>} <div style={{ fontSize: 12, color: 'red' }}>{this.state.antennaTypeError}</div>
- </div>
- </div>
<div>
<div>EIRP</div>
- <div aria-label="site-a-effective-isotropic-radiated-power">{this.props.eirpSiteA} dBm</div>
- <div aria-label="site-b-effective-isotropic-radiated-power">{this.props.eirpSiteB} dBm</div>
+ <div aria-label="site-a-effective-isotropic-radiated-power">{this.props.eirpA.toFixed(3)} dBm</div>
+ <div aria-label="site-b-effective-isotropic-radiated-power">{this.props.eirpB.toFixed(3)} dBm</div>
</div>
-
+
<div>
<div>Gain</div>
- <div aria-label="site-a-antenna-gain" > {this.props.antennaGainList[this.props.antennaList.indexOf(this.props.antennaA)]} dBi</div>
- <div aria-label="site-b-antenna-gain">{this.props.antennaGainList[this.props.antennaList.indexOf(this.props.antennaB)]} dBi</div>
-
+ <div aria-label="site-a-antenna-gain" > {this.props.antennaGainA} dBi</div>
+ <div aria-label="site-b-antenna-gain">{this.props.antennaGainB} dBi</div>
</div>
<div>
+ <div>Waveguide Loss</div>
+ <div aria-label="site-a-waveguide-loss" > {this.props.waveguideLossA} dB</div>
+ <div aria-label="site-b-waveguide-loss">{this.props.waveguideLossB} dB</div>
+ </div>
+
+ <div>
<div></div>
<div>{<button aria-label="calculate-button" style={{ color: '#222', fontFamily: 'Arial', boxAlign: 'center', display: 'inline-block', insetInlineStart: '20', alignSelf: 'center' }}
onClick={(e) => this.buttonHandler()} >Calculate</button>} </div>
diff --git a/sdnr/wt/odlux/apps/linkCalculationApp/webpack.config.js b/sdnr/wt/odlux/apps/linkCalculationApp/webpack.config.js
index 515c5826b..55d98b4d7 100644
--- a/sdnr/wt/odlux/apps/linkCalculationApp/webpack.config.js
+++ b/sdnr/wt/odlux/apps/linkCalculationApp/webpack.config.js
@@ -165,6 +165,20 @@ module.exports = (env) => {
ws: true,
changeOrigin: true,
secure: false
+ },
+ "/terrain": {
+ target: "http://10.20.11.163:5200",
+ secure: false,
+ pathRewrite(pathname) {
+ return pathname.replace(/^\/terrain/, '')
+ }
+ },
+ "/terrain/": {
+ target: "http://10.20.11.163:5200",
+ secure: false,
+ pathRewrite(pathname) {
+ return pathname.replace(/^\/terrain/, '/')
+ }
}
}
diff --git a/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx b/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx
index 61b781384..b6c14a9ce 100644
--- a/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx
+++ b/sdnr/wt/odlux/apps/performanceHistoryApp/src/components/ltpSelection.tsx
@@ -34,7 +34,7 @@ const useStyles = makeStyles(theme => ({
border: "1px solid #ced4da",
fontSize: 16,
width: "auto",
- padding: "5px 26px 5px 12px",
+ padding: "5px 5px 5px 5px",
transition: theme.transitions.create(["border-color", "box-shadow"]),
},
center: {