aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKAPIL SINGAL <ks220y@att.com>2020-08-31 12:49:34 +0000
committerGerrit Code Review <gerrit@onap.org>2020-08-31 12:49:34 +0000
commita4ceb7aa23cab5d291d0784095492b001709981f (patch)
tree25e7c23ef3acc6c201c0d784ea658c155cec2a64
parentb9aba01d54fa0f20c87dcac0f1aaa51bd8128652 (diff)
parent7058ffa19dde75c14eb89270c1a57926c0bce4cc (diff)
Merge "Add networkMap"
-rw-r--r--sdnr/wt/odlux/apps/app-feature/pom.xml10
-rwxr-xr-xsdnr/wt/odlux/apps/app-installer/pom.xml5
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/.babelrc17
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/icons/README.md29
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/icons/apartment.pngbin0 -> 1717 bytes
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts2
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.pngbin0 -> 2603 bytes
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts2
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/icons/lamp.pngbin0 -> 2233 bytes
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts2
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/package.json44
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/pom.xml176
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/App.tsx32
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts50
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/actions/detailsAction.ts164
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/actions/mapActions.ts83
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/actions/popupActions.ts39
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx59
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/denseTable.tsx121
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/details/details.tsx205
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx101
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx153
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx606
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx94
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/config.ts50
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts38
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/handlers/detailsReducer.ts70
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts81
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/handlers/popupReducer.ts50
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts47
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/index.html30
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/model/Feature.ts25
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/model/count.ts19
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/model/historyEntry.ts22
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/model/link.ts30
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/model/site.ts43
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx101
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/styles/index.css13
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/styles/mapbox-gl.css1
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts398
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/utils/mapUtils.ts135
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src/utils/utils.ts25
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java68
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml9
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src2/test/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/test/TestBundleRes.java46
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/src2/test/resources/test.js5
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/tsconfig.json37
-rw-r--r--sdnr/wt/odlux/apps/networkMapApp/webpack.config.js177
-rw-r--r--sdnr/wt/odlux/framework/pom.xml2
-rw-r--r--sdnr/wt/odlux/installer/pom.xml8
-rw-r--r--sdnr/wt/odlux/pom.xml1
51 files changed, 3523 insertions, 2 deletions
diff --git a/sdnr/wt/odlux/apps/app-feature/pom.xml b/sdnr/wt/odlux/apps/app-feature/pom.xml
index 2aaf573d1..e5575a537 100644
--- a/sdnr/wt/odlux/apps/app-feature/pom.xml
+++ b/sdnr/wt/odlux/apps/app-feature/pom.xml
@@ -97,10 +97,18 @@
<artifactId>sdnr-wt-odlux-app-configurationApp</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-app-networkMapApp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>sdnr-wt-odlux-app-linkCalculationApp</artifactId>
<version>${project.version}</version>
- </dependency>
+ </dependency>
+
+
+
</dependencies>
</project>
diff --git a/sdnr/wt/odlux/apps/app-installer/pom.xml b/sdnr/wt/odlux/apps/app-installer/pom.xml
index 1bf55ed30..dc919f01f 100755
--- a/sdnr/wt/odlux/apps/app-installer/pom.xml
+++ b/sdnr/wt/odlux/apps/app-installer/pom.xml
@@ -124,6 +124,11 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-app-networkMapApp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
<artifactId>sdnr-wt-odlux-app-linkCalculationApp</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/sdnr/wt/odlux/apps/networkMapApp/.babelrc b/sdnr/wt/odlux/apps/networkMapApp/.babelrc
new file mode 100644
index 000000000..3d8cd1260
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/.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/networkMapApp/icons/README.md b/sdnr/wt/odlux/apps/networkMapApp/icons/README.md
new file mode 100644
index 000000000..b26fbc29b
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/icons/README.md
@@ -0,0 +1,29 @@
+Copyright of icons is as followes:
+
+<!--
+ * ============LICENSE_START========================================================================
+ * apartment.png - Material Icons
+ * =================================================================================================
+ * Copyright (C) 2020 Google. 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==========================================================================
+ */
+ -->
+
+datacenter.png and lamp.png
+
+Taken from MS Word
+
+According to https://support.microsoft.com/en-us/office/insert-icons-in-microsoft-office-e2459f17-3996-4795-996e-b9a13486fa79 (date: October 9th, 2019)
+"These icons are free to use; there's no royalty or copyright."
+
+ \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png b/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png
new file mode 100644
index 000000000..d4a1c5e7c
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png
Binary files differ
diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts
new file mode 100644
index 000000000..bf398f5a4
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/icons/apartment.png.d.ts
@@ -0,0 +1,2 @@
+declare const apartment: string;
+export default apartment; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png
new file mode 100644
index 000000000..eb2a6278d
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png
Binary files differ
diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts
new file mode 100644
index 000000000..a58a9f5af
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/icons/datacenter.png.d.ts
@@ -0,0 +1,2 @@
+declare const datacenter: string;
+export default datacenter; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png b/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png
new file mode 100644
index 000000000..f5ea00138
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png
Binary files differ
diff --git a/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts b/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts
new file mode 100644
index 000000000..9634b1275
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/icons/lamp.png.d.ts
@@ -0,0 +1,2 @@
+declare const lamp: string;
+export default lamp; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/package.json b/sdnr/wt/odlux/apps/networkMapApp/package.json
new file mode 100644
index 000000000..0f05ffb6c
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "@odlux/networkmap-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/mapbox-gl": "^1.10.2",
+ "mapbox-gl": "^1.11.0",
+ "object.values": "^1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "16.9.19",
+ "@types/react-dom": "16.9.5",
+ "@types/react-router-dom": "4.3.1",
+ "@material-ui/core": "4.9.0",
+ "@material-ui/icons": "4.5.1",
+ "@types/classnames": "2.2.6",
+ "@types/flux": "3.1.8",
+ "@types/jquery": "3.3.10",
+ "jquery": "3.3.1",
+ "react": "16.12.0",
+ "react-dom": "16.12.0",
+ "react-router-dom": "4.3.1"
+
+ }
+}
diff --git a/sdnr/wt/odlux/apps/networkMapApp/pom.xml b/sdnr/wt/odlux/apps/networkMapApp/pom.xml
new file mode 100644
index 000000000..285bb7057
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/pom.xml
@@ -0,0 +1,176 @@
+<?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>odlparent</artifactId>
+ <version>2.0.1-SNAPSHOT</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.onap.ccsdk.features.sdnr.wt</groupId>
+ <artifactId>sdnr-wt-odlux-app-networkMapApp</artifactId>
+ <version>1.0.1-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>
+ <checkstyle.skip>true</checkstyle.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>v10.16.3</nodeVersion>
+ <yarnVersion>v1.19.0</yarnVersion>
+ </configuration>
+ </execution>
+ <execution>
+ <id>yarn build</id>
+ <goals>
+ <goal>yarn</goal>
+ </goals>
+ <configuration>
+ <arguments>run build</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>org.onap.ccsdk.features.sdnr.wt.odlux.model.*,com.opensymphony.*</Import-Package>
+ <Private-Package/>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/App.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/App.tsx
new file mode 100644
index 000000000..6caab5147
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/App.tsx
@@ -0,0 +1,32 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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'
+import Details from './components/details/details'
+
+function MainView() {
+ return (
+ <div className="App" style={{display: 'flex', flexDirection:'row', flexGrow:1, height:"100%"}}>
+ <Map />
+ <Details />
+ </div>
+ );
+}
+
+export default MainView;
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts b/sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts
new file mode 100644
index 000000000..448ae8386
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/actions/connectivityAction.ts
@@ -0,0 +1,50 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { Action } from "../../../../framework/src/flux/action";
+import { Dispatch } from "../../../../framework/src/flux/store";
+import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
+
+
+export class IsTopologyServerReachableAction extends Action{
+ constructor(public reachable: boolean){
+ super();
+ }
+}
+
+export class IsTileServerReachableAction extends Action{
+ constructor(public reachable: boolean){
+ super();
+ }
+}
+
+export const verifyResponse = (response: Response) =>{
+
+ if(response.ok){
+ return response
+ }else{
+ throw Error(`Connection Error: ${response.status} | ${response.statusText} | ${response.url}`)
+ }
+}
+
+export const handleConnectionError = (error: Error) => (dispatcher: Dispatch, getState: () => IApplicationStoreState)=>{
+ const {network:{connectivity: {isToplogyServerAvailable}}} = getState();
+ if(isToplogyServerAvailable){
+ dispatcher(new IsTopologyServerReachableAction(false))
+ }
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/actions/detailsAction.ts b/sdnr/wt/odlux/apps/networkMapApp/src/actions/detailsAction.ts
new file mode 100644
index 000000000..8a005bcaf
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/actions/detailsAction.ts
@@ -0,0 +1,164 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { Action } from '../../../../framework/src/flux/action';
+import { requestRest } from '../../../../framework/src/services/restService';
+
+
+import { site, Device } from "../model/site";
+import { link } from '../model/link';
+import { HistoryEntry } from "../model/historyEntry";
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+import { Dispatch } from '../../../../framework/src/flux/store';
+
+export class SelectSiteAction extends Action {
+ constructor(public site: site){
+ super()
+ }
+}
+
+export class SelectLinkAction extends Action {
+ constructor(public link: link){
+ super();
+ }
+}
+
+export class ClearDetailsAction extends Action{
+ constructor(){
+ super();
+ }
+}
+
+export class AddToHistoryAction extends Action {
+ constructor(public entry: HistoryEntry){
+ super();
+ }
+}
+
+export class ClearHistoryAction extends Action {
+ constructor(){
+ super();
+ }
+}
+
+export class IsBusyCheckingDeviceListAction extends Action{
+ constructor(public isBusy: boolean){
+ super();
+ }
+}
+
+export class FinishedLoadingDeviceListAction extends Action{
+ constructor(public devices: Device[]){
+ super();
+ }
+}
+
+export class ClearLoadedDevicesAction extends Action{
+ constructor(){
+ super();
+ }
+}
+
+let running=false;
+
+export const UpdateDetailsView = (nodeId: string) =>(dispatcher: Dispatch, getState: () => IApplicationStoreState) =>{
+ const {network:{details:{checkedDevices}}} = getState();
+ if(checkedDevices!==null){
+ const index = checkedDevices.findIndex(item=>item.name===nodeId)
+ if(index!==-1)
+ requestRest<any>("/rests/operational/network-topology:network-topology/topology/topology-netconf/node/"+nodeId, { method: "GET" })
+ .then(result =>{
+ if(result!==null){
+ checkedDevices[index].status = result.node[0]["netconf-node-topology:connection-status"];
+
+ }else{
+ checkedDevices[index].status = "Not connected";
+ }
+ dispatcher(new FinishedLoadingDeviceListAction(checkedDevices));
+
+ });
+
+ }
+}
+
+export const CheckDeviceList = (list: Device[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) =>{
+if(running) return;
+running=true;
+ dispatcher(new IsBusyCheckingDeviceListAction(true));
+
+ const promises = list.map((device)=>{
+ if(device.simulatorId){
+ return requestRest<any>("/rests/operational/network-topology:network-topology/topology/topology-netconf/node/"+device.simulatorId, { method: "GET" })
+
+ }else{
+ return requestRest<any>("/rests/operational/network-topology:network-topology/topology/topology-netconf/node/"+device.name, { method: "GET" })
+
+ }
+
+ })
+
+ Promise.all(promises).then((result)=>{
+ running=false;
+
+
+ result.forEach((res: any, index)=>{
+ console.log("value")
+ console.log(res);
+ if(res !==null && res.node!==null){
+
+ list[index].status = res.node[0]["netconf-node-topology:connection-status"];
+ }else{
+ list[index].status = "Not connected";
+ }
+ });
+
+ dispatcher(new FinishedLoadingDeviceListAction(list));
+ dispatcher(new IsBusyCheckingDeviceListAction(false));
+
+ })
+ .catch(err=>{
+ console.error(err);
+
+ dispatcher(new IsBusyCheckingDeviceListAction(false));
+
+ });
+
+ /* result.forEach((res: Promise<any>, index)=>{
+ console.log("value")
+ console.log(res);
+ console.log(res.value);
+ if(res.value!==null){
+ list[index].status = res.value.node[0]["netconf-node-topology:connection-status"];
+ }else{
+ list[index].status = "Not connected";
+ }*/
+
+
+
+
+
+
+ //get devices
+ //wait on all to finish
+ //update array
+
+
+
+
+
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/actions/mapActions.ts b/sdnr/wt/odlux/apps/networkMapApp/src/actions/mapActions.ts
new file mode 100644
index 000000000..b8af40b0c
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/actions/mapActions.ts
@@ -0,0 +1,83 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { Action } from '../../../../framework/src/flux/action';
+import { Dispatch } from '../../../../framework/src/flux/store';
+
+
+import { link } from "../model/link";
+import { site } from "../model/site";
+import { Feature } from '../model/Feature';
+import { URL_API } from '../config';
+
+
+export class HighlightLinkAction extends Action{
+ constructor(public link: link){
+ super();
+ }
+}
+
+export class HighlightSiteAction extends Action{
+ constructor(public site: site){
+ super();
+ }
+}
+
+export class RemoveHighlightingAction extends Action {
+ constructor(){
+ super();
+ }
+}
+
+export class ZoomToSearchResultAction extends Action{
+ constructor(public lat: number, public lon: number){
+ super();
+ }
+}
+
+export class AddAlarmAction extends Action{
+ constructor(public element: Feature){
+ super();
+ }
+}
+
+export class SetCoordinatesAction extends Action{
+ constructor(public lat: number, public lon: number, public zoom: number){
+ super();
+ }
+}
+
+export class SetStatistics extends Action{
+ constructor(public siteCount: string, public linkCount: string){
+ super();
+ }
+}
+
+export class SetIconSwitchAction extends Action{
+ constructor(public enable:boolean){
+ super();
+ }
+}
+
+export const findSiteToAlarm = (alarmedNodeId: string) => (dispatcher: Dispatch) =>{
+ fetch(URL_API+"/site/geojson/device/"+alarmedNodeId)
+ .then(res => res.json())
+ .then(result=>{
+ dispatcher(new AddAlarmAction(result));
+ });
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/actions/popupActions.ts b/sdnr/wt/odlux/apps/networkMapApp/src/actions/popupActions.ts
new file mode 100644
index 000000000..ff8d07921
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/actions/popupActions.ts
@@ -0,0 +1,39 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { Action } from '../../../../framework/src/flux/action';
+
+export class SetPopupPositionAction extends Action {
+ constructor(public top: number, public left: number){
+ super()
+ }
+}
+
+export class SelectMultipleLinksAction extends Action {
+ constructor(public ids: string[]) {
+ super();
+ }
+}
+
+
+
+export class SelectMultipleSitesAction extends Action {
+ constructor(public ids: string[]) {
+ super();
+ }
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx
new file mode 100644
index 000000000..d1e2d978f
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/connectionInfo.tsx
@@ -0,0 +1,59 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
+import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/connect";
+import { Paper, Typography } from "@material-ui/core";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
+
+
+type props = Connect<typeof mapStateToProps, typeof mapDispatchToProps>;
+
+const ConnectionInfo: React.FunctionComponent<props> = (props) => {
+
+ return ((props.isTopoServerReachable === false || props.isTileServerReachable === false )? <Paper style={{padding:5, position: 'absolute', top: 160, width: 230, left:"40%"}}>
+ <div style={{display: 'flex', flexDirection: 'column'}}>
+ <div style={{'alignSelf': 'center', marginBottom:5}}> <Typography> <FontAwesomeIcon icon={faExclamationTriangle} /> Connection Error</Typography></div>
+ {props.isTileServerReachable === false && <Typography> Tile data can't be loaded.</Typography>}
+ {props.isTopoServerReachable === false && <Typography > Network data can't be loaded.</Typography>}
+ </div>
+ </Paper> : null
+)
+
+}
+
+const mapStateToProps = (state: IApplicationStoreState) => ({
+ isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable,
+ isTileServerReachable: state.network.connectivity.isTileServerAvailable
+
+});
+
+
+
+const mapDispatchToProps = (dispatcher: IDispatcher) => ({
+
+ //zoomToSearchResult: (lat: number, lon: number) => dispatcher.dispatch(new ZoomToSearchResultAction(lat, lon))
+
+});;
+
+
+export default connect(mapStateToProps,mapDispatchToProps)(ConnectionInfo)
+
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/denseTable.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/denseTable.tsx
new file mode 100644
index 000000000..9846a22c0
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/denseTable.tsx
@@ -0,0 +1,121 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 Table from '@material-ui/core/Table';
+import TableBody from '@material-ui/core/TableBody';
+import TableCell from '@material-ui/core/TableCell';
+import TableContainer from '@material-ui/core/TableContainer';
+import TableHead from '@material-ui/core/TableHead';
+import TableRow from '@material-ui/core/TableRow';
+import Paper from '@material-ui/core/Paper';
+import { makeStyles, Button, Tooltip } from '@material-ui/core';
+
+type props = { headers: string[], height:number, navigate?(applicationName: string, path?: string):void, onLinkClick?(id: string): void, data: any[], hover: boolean, onClick?(id: string): void, actions?:boolean };
+
+
+const styles = makeStyles({
+ container: {
+ overflow:"auto"
+ },
+ button: {
+ margin: 0,
+ padding: "6px 6px",
+ minWidth: 'unset'
+ }
+
+ });
+
+
+const DenseTable: React.FunctionComponent<props> = (props) => {
+
+ const classes = styles();
+
+ const handleClick = (event: any, id: string) =>{
+ event.preventDefault();
+ props.onClick !== undefined && props.onClick(id);
+
+ }
+
+ const handleHover = (event: any, id: string) =>{
+ event.preventDefault();
+
+ }
+
+ return (
+ <Paper style={{borderRadius:"0px"}}>
+ <div style={{ height:props.height, overflow:"auto"}}>
+ <Table stickyHeader size="small" aria-label="a dense table" >
+ <TableHead>
+ <TableRow>
+ {
+ props.headers.map((data) => {
+ return <TableCell>{data}</TableCell>
+ })
+ }
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {props.data.map((row, index) => {
+
+
+ var filteredRows = Object.keys(row).filter(function(e) { if(e!=="simulatorId") return row });
+
+ //var filteredRows = Object.keys(row).filter(function(e) { if(e!=="simulatorId") return row[e] });
+ var values = Object.keys(row).map(function(e) { if(e!=="simulatorId"){ return row[e];} else return undefined });
+
+
+ return (
+ <TableRow key={index} hover={props.hover} onMouseOver={e => handleHover(e,row.name)} onClick={ e => handleClick(e, row.name)}>
+
+ {
+ values.map((data:any) => {
+
+ if(data!== undefined)
+ return <TableCell > {data} </TableCell>
+ else
+ return null;
+ })
+ }
+ {
+
+ props.actions && <TableCell >
+<div style={{display:"flex"}}>
+ <Tooltip title="Configure">
+ <Button className={classes.button} disabled={row.status!=="connected"} onClick={(e: any) =>{ e.preventDefault(); e.stopPropagation(); props.navigate && props.navigate("configuration", row.simulatorId ? row.simulatorId : row.name)}}>C</Button>
+ </Tooltip>
+ <Tooltip title="Fault">
+ <Button className={classes.button} onClick={(e: any) =>{ e.preventDefault(); e.stopPropagation(); props.navigate && props.navigate("fault", row.simulatorId ? row.simulatorId : row.name)}}>F</Button>
+ </Tooltip>
+ </div>
+ </TableCell>
+
+ }
+ </TableRow>)
+ })
+ }
+
+ </TableBody>
+ </Table>
+ </div>
+ </Paper>
+ );
+
+}
+
+export default DenseTable; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/details/details.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/details.tsx
new file mode 100644
index 000000000..a2e51d30f
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/details.tsx
@@ -0,0 +1,205 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 connect, { IDispatcher, Connect } from '../../../../../framework/src/flux/connect';
+
+import { site, Device } from '../../model/site';
+import Typography from '@material-ui/core/Typography';
+import { link } from '../../model/link';
+import { Breadcrumbs, Link, Paper } from '@material-ui/core';
+import SiteDetails from './siteDetails';
+import LinkDetails from './linkDetails';
+import { URL_API, URL_BASEPATH } from '../../config';
+import { SelectSiteAction, SelectLinkAction, AddToHistoryAction, ClearHistoryAction, CheckDeviceList, ClearDetailsAction } from '../../actions/detailsAction';
+import { HistoryEntry } from '../../model/historyEntry';
+import { HighlightLinkAction, HighlightSiteAction, RemoveHighlightingAction } from '../../actions/mapActions';
+import { isSite } from '../../utils/utils';
+import { IApplicationStoreState } from '../../../../../framework/src/store/applicationStore';
+import { NavigateToApplication } from '../../../../../framework/src/actions/navigationActions';
+import { RouteComponentProps, withRouter } from 'react-router-dom';
+
+
+const Details: React.FunctionComponent<porps> = (props) => {
+
+ const [message, setMessage] = React.useState("No data selected.");
+
+
+ //on mount
+ React.useEffect(() => {
+ const detailsId = getDetailsIdFromUrl();
+ if (detailsId !== null && props.data?.name !== detailsId) {
+ loadDetailsData(detailsId)
+ }
+
+ }, []);
+
+ // if url changed
+ React.useEffect(() => {
+ const detailsId = getDetailsIdFromUrl();
+ console.log(detailsId)
+ if (detailsId !== null && props.data?.name !== detailsId) {
+ loadDetailsData(detailsId)
+ }
+ else if(detailsId===null){
+ setMessage("No data selected.");
+ props.clearDetails();
+ props.undoMapSelection();
+ }
+
+ }, [props.location.pathname]);
+
+ //update url if new element loaded
+ React.useEffect(() => {
+ if (props.data !== null) {
+ const currentUrl = window.location.href;
+ const parts = currentUrl.split(URL_BASEPATH);
+ const detailsPath = parts[1].split("/details/");
+ props.history.replace(`/${URL_BASEPATH}${detailsPath[0]}/details/${props.data.name}`)
+ }
+
+ }, [props.data])
+
+ const onLinkClick = async (id: string) => {
+ const result = await fetch(`${URL_API}/link/${id}`);
+ if(result.ok){
+ const resultAsJson = await result.json();
+ const link = resultAsJson as link;
+ props.selectLink(link);
+ props.addHistory({ id: props.data!.name, data: props.data! });
+ props.highlightLink(link);
+
+ }
+ }
+
+ const backClick = (e: any) => {
+ if (isSite(props.breadcrumbs[0].data)) {
+ props.selectSite(props.breadcrumbs[0].data)
+ props.highlightSite(props.breadcrumbs[0].data);
+
+ } else {
+ props.selectLink(props.breadcrumbs[0].data);
+ props.highlightLink(props.breadcrumbs[0].data);
+
+ }
+
+ props.clearHistory();
+ e.preventDefault();
+ }
+
+ const createDetailPanel = (data: site | link) => {
+
+ if (isSite(data)) {
+ return <SiteDetails navigate={props.navigateToApplication} updatedDevices={props.updatedDevices} loadDevices={props.loadDevices} site={data} onLinkClick={onLinkClick} />
+ } else {
+ return <LinkDetails link={data} />
+ }
+ }
+
+ const getDetailsIdFromUrl = () =>{
+ const currentUrl = window.location.href;
+ const parts = currentUrl.split(URL_BASEPATH);
+ const detailsPath = parts[1].split("/details/")
+ return detailsPath[1] ? detailsPath[1] : null;
+ }
+
+ const loadDetailsData = (id: string) =>{
+
+ fetch(`${URL_API}/link/${id}`)
+ .then(res => {
+ if (res.ok)
+ return res.json()
+ else
+ return Promise.reject()
+
+ })
+ .then(result => {
+ props.selectLink(result)
+ props.highlightLink(result);
+
+ })
+ .catch(error => {
+
+ fetch(`${URL_API}/site/${id}`)
+ .then(res => {
+ if (res.ok)
+ return res.json()
+ else return Promise.reject();
+ })
+ .then(result => {
+ props.selectSite(result);
+ props.highlightSite(result);
+ })
+ .catch(error =>{
+ setMessage("No element with name " + id + " found");
+ props.clearDetails();
+ props.undoMapSelection();
+ });
+ })
+ }
+
+
+ return (<div style={{ width: '30%', background: "#bbbdbf", padding: "20px", alignSelf:"stretch" }}>
+ <Paper style={{ height:"100%"}} id="site-details-panel" >
+ {
+ props.breadcrumbs.length > 0 &&
+ <Breadcrumbs style={{ marginLeft: "15px", marginTop: "5px" }} aria-label="breadcrumb">
+ <Link color="inherit" href="/" onClick={backClick}>
+ {props.breadcrumbs[0].id}
+ </Link>
+ <Link>
+ {props.data?.name}
+ </Link>
+ </Breadcrumbs>
+ }
+ {
+ props.data !== null ?
+ createDetailPanel(props.data)
+ : <Typography style={{ marginTop: "5px" }} align="center" variant="body1">{message}</Typography>
+
+ }
+ </Paper>
+ </div>)
+}
+
+type porps = RouteComponentProps & Connect<typeof mapStateToProps, typeof mapDispatchToProps>;
+
+//select always via details?
+const mapStateToProps = (state: IApplicationStoreState) => ({
+ data: state.network.details?.data,
+ breadcrumbs: state.network.details.history,
+ updatedDevices: state.network.details.checkedDevices
+});
+
+const mapDispatchToProps = (dispatcher: IDispatcher) => ({
+ selectSite: (site: site) => dispatcher.dispatch(new SelectSiteAction(site)),
+ selectLink: (link: link) => dispatcher.dispatch(new SelectLinkAction(link)),
+ clearDetails: () => dispatcher.dispatch(new ClearDetailsAction()),
+ addHistory: (newEntry: HistoryEntry) => dispatcher.dispatch(new AddToHistoryAction(newEntry)),
+ clearHistory: () => dispatcher.dispatch(new ClearHistoryAction()),
+ highlightLink: (link: link) => dispatcher.dispatch(new HighlightLinkAction(link)),
+ highlightSite: (site: site) => dispatcher.dispatch(new HighlightSiteAction(site)),
+ loadDevices: async (networkElements: Device[]) => { await dispatcher.dispatch(CheckDeviceList(networkElements)) },
+ navigateToApplication: (applicationName: string, path?: string) => dispatcher.dispatch(new NavigateToApplication(applicationName, path, "test3")),
+ undoMapSelection: () => dispatcher.dispatch(new RemoveHighlightingAction())
+
+})
+
+
+export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Details)); \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx
new file mode 100644
index 000000000..de1bf6b16
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/linkDetails.tsx
@@ -0,0 +1,101 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { link } from '../../model/link';
+import { TextField, Tabs, Tab, Typography, AppBar, Button, Link } from '@material-ui/core';
+import DenseTable from '../denseTable';
+import { LatLonToDMS } from '../../utils/mapUtils';
+
+type panelId = "siteA" | "siteB";
+type props = { link: link };
+
+const LinkDetails: React.FunctionComponent<props> = (props) => {
+
+ const [value, setValue] = React.useState<panelId>("siteA");
+ const [height, setHeight] = React.useState(330);
+
+ const handleResize = () =>{
+ console.log("resize")
+ const el = document.getElementById('site-details-panel')?.getBoundingClientRect();
+ const el2 = document.getElementById('site-tabs')?.getBoundingClientRect();
+
+ if(el && el2){
+ if(props.link.type==="microwave")
+ setHeight(el!.height - el2!.y -30);
+ else
+ setHeight(el!.height - el2!.y +20);
+
+ }
+ }
+
+ //on mount
+ React.useEffect(()=>{
+ handleResize();
+
+ //window.addEventListener("resize", handleResize);
+ },[]);
+
+ React.useEffect(()=>{
+ handleResize();
+ }, [props.link])
+
+ const onHandleTabChange = (event: React.ChangeEvent<{}>, newValue: panelId) => {
+ setValue(newValue);
+ }
+
+ const onCalculateLinkClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) =>{
+ e.preventDefault();
+ const siteA= props.link.locationA;
+ const siteB =props.link.locationB;
+ const nameA = props.link.siteA;
+ const nameB = props.link.siteB;
+ const distance = props.link.length > 0 ? props.link.length : props.link.calculatedLength;
+ const azimuthA = props.link.azimuthA;
+ const azimuthB = props.link.azimuthB;
+ window.open(`/#/linkCalculation?lat1=${siteA.lat}&lon1=${siteA.lon}&lat2=${siteB.lat}&lon2=${siteB.lon}&siteA=${nameA}&siteB=${nameB}&azimuthA=${azimuthA}&azimuthB=${azimuthB}&distance=${distance}`)
+
+ }
+
+ const data = [
+
+ {name:"Site Name", val1: props.link.siteA, val2: props.link.siteB},
+ {name:"Latitude", val1: LatLonToDMS(props.link.locationA.lat), val2: LatLonToDMS(props.link.locationB.lat)},
+ {name:"Longitude", val1: LatLonToDMS(props.link.locationA.lon, true), val2: LatLonToDMS(props.link.locationB.lon, true)},
+ {name:"Azimuth in °", val1: props.link.azimuthA.toFixed(2), val2: props.link.azimuthB.toFixed(2)}
+];
+
+ return (<div style={{ paddingLeft: "15px", paddingRight: "15px", paddingTop: "0px", display: 'flex', flexDirection: 'column' }}>
+ <h2>{props.link.name}</h2>
+ <TextField disabled style={{ marginTop: "5px" }} value="Unkown" label="Operator" />
+ <TextField disabled style={{ marginTop: "5px" }} value={props.link.type} label="Type" />
+ <TextField disabled style={{ marginTop: "5px" }} value={props.link.length.toFixed(2)} label="Distance planned in km" />
+ <TextField disabled style={{ marginTop: "5px" }} value={props.link.calculatedLength.toFixed(2)} label="Distance calculated in km" />
+
+ <AppBar position="static" id="site-tabs" style={{ marginTop: "20px", background: '#2E3B55' }}>
+ <Typography style={{ margin:"5px"}}>SITE DETAILS</Typography>
+ </AppBar>
+ <DenseTable height={height} hover={false} headers={["", "Site A", "Site B"]} data={data} />
+ {
+ props.link.type==="microwave" && <Button style={{marginTop:20}} fullWidth variant="contained" color="primary" onClick={onCalculateLinkClick}>Calculate link</Button>
+ }
+ </div>)
+}
+
+export default LinkDetails; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx
new file mode 100644
index 000000000..a95666e38
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/details/siteDetails.tsx
@@ -0,0 +1,153 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { TextField, Tabs, Tab, Typography, AppBar, Button, Tooltip } from '@material-ui/core';
+
+
+import MaterialTable, { ColumnModel, ColumnType, MaterialTableCtorType } from "../../../../../framework/src/components/material-table";
+
+
+import { site, Device } from '../../model/site';
+import DenseTable from '../denseTable';
+import { LatLonToDMS } from '../../utils/mapUtils';
+
+type minLinks = { name: string, azimuth: string}
+
+const FaultAlarmNotificationTable = MaterialTable as MaterialTableCtorType<minLinks>;
+
+
+type panelId="links" | "nodes";
+type props = { site: site, updatedDevices: Device[]|null, navigate(applicationName: string, path?: string):void, onLinkClick(id: string): void, loadDevices(devices:Device[]): void };
+
+const SiteDetails: React.FunctionComponent<props> = (props) => {
+
+ const [value, setValue] = React.useState<panelId>("links");
+ const [height, setHeight] = React.useState(330);
+
+ const handleResize = () =>{
+ //console.log("resize")
+ const el = document.getElementById('site-details-panel')?.getBoundingClientRect();
+ const el2 = document.getElementById('site-tabs')?.getBoundingClientRect();
+
+ if(el && el2){
+ setHeight(el!.height - el2!.y +20);
+ }
+
+ }
+
+ //on mount
+ React.useEffect(()=>{
+ handleResize();
+
+ window.addEventListener("resize", ()=>{console.log("really got resized.")});
+ },[]);
+
+ // on update
+ React.useEffect(()=>{
+
+ props.loadDevices(props.site.devices);
+ handleResize();
+
+ }, [props.site])
+
+ const onHandleTabChange = (event: React.ChangeEvent<{}>, newValue: panelId) => {
+ setValue(newValue);
+ }
+
+ const linkRows: minLinks[] = props.site.links.map(link=>
+ {
+ return {name: link.name, azimuth: link.azimuthB.toFixed(2) }
+ });
+
+
+
+ return (<div style={{ padding: '15px', display: "flex", flexDirection:"column", minWidth:0, minHeight:0 }}>
+ <h2 >{props.site.name}</h2>
+ {
+ props.site.operator !== '' && props.site.operator !== null ?
+ <TextField disabled={true} value={props.site.operator} label="Operator" /> :
+ <TextField disabled={true} value="Unkown" label="Operator" style={{ marginTop: "5px" }} />
+ }
+ {
+ props.site.type !== undefined && props.site.type.length > 0 &&
+ <TextField disabled={true} value={props.site.type} label="Type" style={{ marginTop: "5px" }} />
+ }
+ {
+ props.site.address !== undefined && props.site.address.length > 0 &&
+ <TextField disabled={true} value={props.site.address} label="Adress" style={{ marginTop: "5px" }} />
+ }
+ {
+ props.site.heighAGLInMeters !== undefined && props.site.heighAGLInMeters > 0 &&
+ <TextField disabled={true} value={props.site.heighAGLInMeters} label="AMSL in meters" style={{ marginTop: "5px" }} />
+ }
+ {
+ props.site.antennaHeightAGLInMeters !== undefined && props.site.antennaHeightAGLInMeters > 0 &&
+ <TextField disabled={true} value={props.site.antennaHeightAGLInMeters} label="Atenna above ground in meters" style={{ marginTop: "5px" }} />
+ }
+
+ <TextField style={{ marginTop: "5px" }} disabled={true} value={LatLonToDMS(props.site.geoLocation.lat)} label="Latitude" />
+ <TextField style={{ marginTop: "5px" }} disabled={true} value={LatLonToDMS(props.site.geoLocation.lon, true)} label="Longitude" />
+
+ <AppBar position="static" style={{ marginTop: "5px", background: '#2E3B55' }}>
+ <Tabs id="site-tabs" value={value} onChange={onHandleTabChange} aria-label="simple tabs example">
+ <Tab label="Links" value="links" />
+ <Tab label="Nodes" value="nodes" />
+ </Tabs>
+ </AppBar>
+ {
+ value === "links" &&
+ <>
+ {
+ props.site.links.length === 0 &&
+ <Typography variant="body1" style={{ marginTop: '10px' }}>No links available.</Typography>
+ }
+
+ {
+ props.site.links.length > 0 &&
+ <DenseTable height={height} hover={true} headers={["Link Name", "Azimuth in °"]} data={linkRows} onClick={props.onLinkClick} ></DenseTable>
+ /**
+ *
+ * */
+
+
+ }
+
+ </>
+
+ }
+ {
+ value === "nodes" &&
+ <>
+ {
+ props.site.devices.length === 0 &&
+ <Typography variant="body1" style={{ marginTop: '10px' }}>No nodes available.</Typography>
+ }
+
+ {
+ props.site.devices.length>0 && props.updatedDevices !== null &&
+ <DenseTable navigate={props.navigate} height={height} hover={false} headers={["ID","Name","Type", "Manufacturer","Owner","Status", "Ports", "Actions"]} actions={true} data={props.updatedDevices!} />
+ }
+ </>
+ }
+ </div>
+ )
+
+}
+
+export default SiteDetails; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx
new file mode 100644
index 000000000..1aabb92c6
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/map.tsx
@@ -0,0 +1,606 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 InfoIcon from '@material-ui/icons/Info';
+import { RouteComponentProps, withRouter } from 'react-router-dom';
+
+
+import { site } from '../model/site';
+import { SelectSiteAction, ClearHistoryAction, SelectLinkAction } from '../actions/detailsAction';
+import { OSM_STYLE, URL_API, URL_BASEPATH, URL_TILE_API } from '../config';
+import { link } from '../model/link';
+import MapPopup from './mapPopup';
+import { SetPopupPositionAction, SelectMultipleLinksAction, SelectMultipleSitesAction } from '../actions/popupActions';
+import { Feature } from '../model/Feature';
+import { HighlightLinkAction, HighlightSiteAction, SetCoordinatesAction, SetStatistics } from '../actions/mapActions';
+import { addDistance, getUniqueFeatures } from '../utils/mapUtils';
+import { location } from '../handlers/mapReducer'
+import { Typography, Paper, Tooltip } from '@material-ui/core';
+import { elementCount } from '../model/count';
+import lamp from '../../icons/lamp.png';
+import apartment from '../../icons/apartment.png';
+import datacenter from '../../icons/datacenter.png';
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect';
+import { verifyResponse, IsTileServerReachableAction, handleConnectionError } from '../actions/connectivityAction';
+import ConnectionInfo from './connectionInfo'
+import { ApplicationStore } from '../../../../framework/src/store/applicationStore';
+import { showIconLayers, addBaseLayers, swapLayersBack } from '../utils/mapLayers';
+
+
+
+
+
+type coordinates = { lat: number, lon: number, zoom: number }
+
+let alarmElements: Feature[] = [];
+let map: mapboxgl.Map;
+let isLoadingInProgress = false;
+let notLoadedBoundingBoxes: mapboxgl.LngLatBounds[] = [];
+
+let lastBoundingBox: mapboxgl.LngLatBounds | null = null;
+let myRef = React.createRef<HTMLDivElement>();
+
+
+class Map extends React.Component<mapProps, { isPopupOpen: boolean }> {
+
+ constructor(props: mapProps) {
+ super(props);
+ //any state stuff
+ this.state = { isPopupOpen: false }
+
+ }
+
+ componentDidMount() {
+
+ window.addEventListener("menu-resized", this.handleResize);
+
+ fetch(URL_TILE_API + '/10/0/0.png')
+ .then(res => {
+ if (res.ok) {
+ this.setupMap();
+ } else {
+ this.props.setTileServerLoaded(false);
+ console.error("tileserver " + URL_TILE_API + "can't be reached.")
+ }
+ })
+ .catch(err => {
+ this.props.setTileServerLoaded(false);
+ console.error("tileserver " + URL_TILE_API + "can't be reached.")
+ });
+
+ fetch(URL_API + "/info")
+ .then(result => verifyResponse(result))
+ .catch(error => this.props.handleConnectionError(error));
+ }
+
+ setupMap = () => {
+
+ let lat = this.props.lat;
+ let lon = this.props.lon;
+ let zoom = this.props.zoom;
+
+ const coordinates = this.extractCoordinatesFromUrl();
+ // override lat/lon/zoom with coordinates from url, if available
+ if (this.areCoordinatesValid(coordinates)) {
+ lat = coordinates.lat;
+ lon = coordinates.lon;
+ zoom = !Number.isNaN(coordinates.zoom) ? coordinates.zoom : zoom;
+ }
+
+ map = new mapboxgl.Map({
+ container: myRef.current!,
+ style: OSM_STYLE as any,
+ center: [lon, lat],
+ zoom: zoom,
+ accessToken: ''
+ });
+
+ map.on('load', (ev) => {
+
+ addBaseLayers(map, this.props.selectedSite, this.props.selectedLink);
+ map.loadImage(
+ lamp,
+ function (error: any, image: any) {
+ if (error) throw error;
+ map.addImage('lamp', image);
+ });
+
+ map.loadImage(
+ datacenter,
+ function (error: any, image: any) {
+ if (error) throw error;
+ map.addImage('data-center', image);
+ });
+
+ map.loadImage(
+ apartment,
+ function (error: any, image: any) {
+ if (error) throw error;
+ map.addImage('house', image);
+ });
+
+ const boundingBox = map.getBounds();
+
+
+ fetch(`${URL_API}/links/geoJson/${boundingBox.getWest()},${boundingBox.getSouth()},${boundingBox.getEast()},${boundingBox.getNorth()}`)
+ .then(result => verifyResponse(result))
+ .then(result => result.json())
+ .then(features => {
+ if (map.getLayer('lines')) {
+ (map.getSource('lines') as mapboxgl.GeoJSONSource).setData(features);
+ }
+ })
+ .catch(error => this.props.handleConnectionError(error));
+
+
+ fetch(`${URL_API}/sites/geoJson/${boundingBox.getWest()},${boundingBox.getSouth()},${boundingBox.getEast()},${boundingBox.getNorth()}`)
+ .then(result => verifyResponse(result))
+ .then(result => result.json())
+ .then(features => {
+ if (map.getLayer('points')) {
+ (map.getSource('points') as mapboxgl.GeoJSONSource).setData(features);
+ }
+ })
+ .catch(error => this.props.handleConnectionError(error));;
+
+ });
+
+ map.on('click', (e: any) => {
+
+ if (map.getLayer('points')) { // data is shown as points
+
+ var clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5],
+ [e.point.x + 5, e.point.y + 5]], {
+ layers: ['lines']
+ }), "id");
+
+ const clickedPoints = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['points'] }), "id");
+ const alarmedSites = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['alarmedPoints'] }), "id");
+
+ if (clickedPoints.length != 0) {
+
+
+ if (alarmedSites.length > 0) {
+ alarmedSites.forEach(alarm => {
+ const index = clickedPoints.findIndex(item => item.properties!.id === alarm.properties!.id);
+ console.log(index);
+
+ if (index !== -1) {
+ clickedPoints[index].properties!.alarmed = true;
+ clickedPoints[index].properties!.type = "alarmed";
+ }
+ });
+ console.log(clickedPoints);
+ }
+
+ this.showSitePopup(clickedPoints, e.point.x, e.point.y);
+ } else if (clickedLines.length != 0) {
+ this.showLinkPopup(clickedLines, e.point.x, e.point.y);
+ }
+
+
+ } else { // data is shown as icons
+
+ const clickedLamps = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['point-lamps'] }), "id");
+ const buildings = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['point-building'] }), "id");
+ const houses = getUniqueFeatures(map.queryRenderedFeatures(e.point, { layers: ['point-data-center'] }), "id");
+
+ const combinedFeatures = [...clickedLamps, ...buildings, ...houses];
+
+ const clickedLines = getUniqueFeatures(map.queryRenderedFeatures([[e.point.x - 5, e.point.y - 5],
+ [e.point.x + 5, e.point.y + 5]], {
+ layers: ['lines']
+ }), "id");
+
+ if (combinedFeatures.length > 0)
+ this.showSitePopup(combinedFeatures, e.point.x, e.point.y);
+ else if (clickedLines.length != 0) {
+ this.showLinkPopup(clickedLines, e.point.x, e.point.y);
+ }
+ }
+
+ });
+
+ map.on('moveend', () => {
+
+ const mapZoom = Number(map.getZoom().toFixed(2));
+ const lat = Number(map.getCenter().lat.toFixed(4));
+ const lon = Number(map.getCenter().lng.toFixed(4));
+
+
+ if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== mapZoom) {
+ this.props.updateMapPosition(lat, lon, mapZoom)
+ }
+
+ const currentUrl = window.location.href;
+ const parts = currentUrl.split(URL_BASEPATH);
+ const detailsPath = parts[1].split("/details/");
+
+ if (detailsPath[1] !== undefined && detailsPath[1].length > 0) {
+ this.props.history.replace(`/${URL_BASEPATH}/${map.getCenter().lat.toFixed(4)},${map.getCenter().lng.toFixed(4)},${mapZoom.toFixed(2)}/details/${detailsPath[1]}`)
+ }
+ else {
+ this.props.history.replace(`/${URL_BASEPATH}/${map.getCenter().lat.toFixed(4)},${map.getCenter().lng.toFixed(4)},${mapZoom.toFixed(2)}`)
+ }
+
+ const boundingBox = map.getBounds();
+
+ showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id);
+
+ fetch(`${URL_API}/info/count/${boundingBox.getWest()},${boundingBox.getSouth()},${boundingBox.getEast()},${boundingBox.getNorth()}`)
+ .then(result => verifyResponse(result))
+ .then(res => res.json())
+ .then(result => {
+ console.log(result);
+ if (result.links !== this.props.linkCount || result.sites !== this.props.siteCount) {
+ this.props.setStatistics(result.links, result.sites);
+ }
+ })
+ .catch(error => this.props.handleConnectionError(error));;
+ })
+
+ map.on('move', () => {
+ const mapZoom = map.getZoom();
+
+ const boundingBox = map.getBounds();
+
+ this.loadNetworkData(boundingBox);
+ if (mapZoom > 9) {
+
+ if (map.getLayer('points')) {
+ map.setLayoutProperty('selectedPoints', 'visibility', 'visible');
+ map.setPaintProperty('points', 'circle-radius', 7);
+ }
+ } else {
+
+ // reduce size of points / lines if zoomed out
+ map.setPaintProperty('points', 'circle-radius', 2);
+ map.setLayoutProperty('selectedPoints', 'visibility', 'none');
+
+ if (mapZoom <= 4) {
+ map.setPaintProperty('lines', 'line-width', 1);
+ } else {
+ map.setPaintProperty('lines', 'line-width', 2);
+ }
+ }
+ });
+ }
+
+ componentDidUpdate(prevProps: mapProps, prevState: {}) {
+
+ if (map !== undefined) {
+ if (prevProps.selectedSite?.properties.id !== this.props.selectedSite?.properties.id) {
+
+ if (this.props.selectedSite != null) {
+ if (map.getSource("selectedLine") !== undefined) {
+ (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] });
+ (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [this.props.selectedSite] });
+ }
+
+
+ if (map.getLayer('point-lamps') !== undefined) {
+
+ map.setFilter('point-lamps', ['==', 'type', 'street lamp']);
+ map.setFilter('point-data-center', ['==', 'type', 'data center']);
+ map.setFilter('point-building', ['==', 'type', 'high rise building'])
+
+ if (this.props.selectedSite?.properties.type !== undefined) {
+ switch (this.props.selectedSite?.properties.type) {
+ case 'street lamp':
+ map.setFilter('point-lamps', ["all", ['==', 'type', 'street lamp'], ['!=', 'id', this.props.selectedSite.properties.id]]);
+ break;
+ case 'data center':
+ map.setFilter('point-data-center', ["all", ['==', 'type', 'data center'], ['!=', 'id', this.props.selectedSite.properties.id]]);
+ break;
+ case 'high rise building':
+ map.setFilter('point-building', ["all", ['==', 'type', 'high rise building'], ['!=', 'id', this.props.selectedSite.properties.id]])
+
+ break;
+ }
+ }
+ }
+
+
+ }
+ else
+ {
+ if (map.getSource("selectedPoints") !== undefined)
+ (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] });
+
+ }
+ }
+
+ if (prevProps.selectedLink !== this.props.selectedLink) {
+ if (this.props.selectedLink != null) {
+
+ if (map.getLayer('point-lamps') !== undefined) {
+ map.setFilter('point-lamps', ['==', 'type', 'street lamp']);
+ map.setFilter('point-data-center', ['==', 'type', 'data center']);
+ map.setFilter('point-building', ['==', 'type', 'high rise building']);
+ }
+
+ if (map.getSource("selectedLine") !== undefined) {
+ (map.getSource("selectedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] });
+ (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [this.props.selectedLink] });
+ }
+ }
+ else
+ {
+ if (map.getSource("selectedLine") !== undefined)
+ (map.getSource("selectedLine") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: [] });
+ }
+ }
+
+ if (prevProps.location.pathname !== this.props.location.pathname) {
+ if (map) {
+ const coordinates = this.extractCoordinatesFromUrl();
+ this.moveMapToCoordinates(coordinates);
+ }
+ }
+
+ if (prevProps.alarmlement !== this.props.alarmlement) {
+ if (this.props.alarmlement !== null && !alarmElements.includes(this.props.alarmlement)) {
+ if (map.getSource("alarmedPoints"))
+ (map.getSource("alarmedPoints") as mapboxgl.GeoJSONSource).setData({ type: "FeatureCollection", features: alarmElements });
+ alarmElements.push(this.props.alarmlement)
+ }
+ }
+
+ if (prevProps.showIcons !== this.props.showIcons) {
+ if (map && map.getZoom() > 11) {
+ console.log(this.props.showIcons);
+ showIconLayers(map, this.props.showIcons, this.props.selectedSite?.properties.id);
+ }
+ }
+
+ if (prevProps.zoomToElement !== this.props.zoomToElement) {
+ if (this.props.zoomToElement !== null) {
+ const currentZoom = map?.getZoom();
+
+ map.flyTo({
+ center: [
+ this.props.zoomToElement.lon,
+ this.props.zoomToElement.lat
+ ], zoom: currentZoom < 10 ? 10 : currentZoom,
+ essential: true
+ });
+ }
+ }
+ }
+ }
+
+ handleResize = () => {
+ if (map) {
+ // wait a moment until resizing actually happened
+ window.setTimeout(() => map.resize(), 500);
+ }
+ }
+
+ extractCoordinatesFromUrl = (): coordinates => {
+ const currentUrl = window.location.href;
+ const mainPathParts = currentUrl.split(URL_BASEPATH);
+ const coordinatePathPart = mainPathParts[1].split("/details/"); // split by details if present
+ const allCoordinates = coordinatePathPart[0].replace("/", "");
+ const coordinates = allCoordinates.split(",");
+ return { lat: Number(coordinates[0]), lon: Number(coordinates[1]), zoom: Number(coordinates[2]) }
+ }
+
+ areCoordinatesValid = (coordinates: coordinates) => {
+
+ if ((!Number.isNaN(coordinates.lat)) && (!Number.isNaN(coordinates.lon))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ moveMapToCoordinates = (coordinates: coordinates) => {
+
+ if (this.areCoordinatesValid(coordinates)) {
+ let zoom = -1;
+
+ if (!Number.isNaN(coordinates.zoom)) {
+ zoom = coordinates.zoom;
+ }
+
+ map.flyTo({
+ center: [
+ coordinates.lon,
+ coordinates.lat
+ ], zoom: zoom !== -1 ? zoom : this.props.zoom,
+ essential: true
+ })
+ }
+ }
+
+
+ //TODO: how to handle if too much data gets loaded? (1 mio points...?)
+ // data might have gotten collected, reload if necessary!
+ //always save count, if count and current view count differ -> reload last boundingbox
+ loadNetworkData = async (bbox: mapboxgl.LngLatBounds) => {
+ if (!isLoadingInProgress) { // only load data if loading not in progress
+ isLoadingInProgress = true;
+
+ if (lastBoundingBox == null) {
+ lastBoundingBox = bbox;
+ await this.draw('lines', `${URL_API}/links/geoJson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`);
+ await this.draw('points', `${URL_API}/sites/geoJson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`);
+ } else {
+
+ // new bbox is bigger than old one
+ if (bbox.contains(lastBoundingBox.getNorthEast()) && bbox.contains(lastBoundingBox.getSouthWest()) && lastBoundingBox !== bbox) { //if new bb is bigger than old one
+
+ lastBoundingBox = bbox;
+
+ const distance = map.getCenter().distanceTo(bbox.getNorthEast()); // radius of visible area (center -> corner) (in meters)
+
+ //calculate new boundingBox
+ const increasedBoundingBox = addDistance(bbox.getSouth(), bbox.getWest(), bbox.getNorth(), bbox.getEast(), (distance / 1000) / 2)
+
+ await this.draw('lines', `${URL_API}/links/geoJson/${increasedBoundingBox.west},${increasedBoundingBox.south},${increasedBoundingBox.east},${increasedBoundingBox.north}`);
+ await this.draw('points', `${URL_API}/sites/geoJson/${increasedBoundingBox.west},${increasedBoundingBox.south},${increasedBoundingBox.east},${increasedBoundingBox.north}`);
+ console.log("bbox is bigger");
+
+ } else if (lastBoundingBox.contains(bbox.getNorthEast()) && lastBoundingBox.contains(bbox.getSouthWest())) { // last one contains new one
+ // bbox is contained in last one, do nothing
+ isLoadingInProgress = false;
+
+ } else { // bbox is not fully contained in old one, extend
+
+ lastBoundingBox.extend(bbox);
+
+ await this.draw('lines', `${URL_API}/links/geoJson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`);
+ await this.draw('points', `${URL_API}/sites/geoJson/${lastBoundingBox.getWest()},${lastBoundingBox.getSouth()},${lastBoundingBox.getEast()},${lastBoundingBox.getNorth()}`);
+ }
+
+ }
+
+
+ if (notLoadedBoundingBoxes.length > 0) { // load last not loaded boundingbox
+ this.loadNetworkData(notLoadedBoundingBoxes.pop()!)
+ notLoadedBoundingBoxes = [];
+ }
+
+ } else {
+ notLoadedBoundingBoxes.push(bbox);
+ }
+ }
+
+ showSitePopup = (sites: mapboxgl.MapboxGeoJSONFeature[], top: number, left: number) => {
+ if (sites.length > 1) {
+ const ids = sites.map(feature => feature.properties!.id);
+
+ this.props.setPopupPosition(top, left);
+ this.props.selectMultipleSites(ids);
+ this.setState({ isPopupOpen: true });
+
+ } else {
+ const id = sites[0].properties!.id;
+
+ fetch(`${URL_API}/site/${id}`)
+ .then(result => verifyResponse(result))
+ .then(res => res.json() as Promise<site>)
+ .then(result => {
+ this.props.selectSite(result);
+ this.props.highlightSite(result);
+ this.props.clearDetailsHistory();
+ })
+ .catch(error => this.props.handleConnectionError(error));;
+ }
+
+ }
+
+
+
+ showLinkPopup = (links: mapboxgl.MapboxGeoJSONFeature[], top: number, left: number) => {
+
+ if (links.length > 1) {
+
+ const ids = links.map(feature => feature.properties!.id as string);
+
+ this.props.setPopupPosition(top, left);
+ this.props.selectMultipleLinks(ids);
+ this.setState({ isPopupOpen: true });
+
+ } else {
+ var id = links[0].properties!.id;
+
+ fetch(`${URL_API}/link/${id}`)
+ .then(result => verifyResponse(result))
+ .then(res => res.json() as Promise<link>)
+ .then(result => {
+ this.props.selectLink(result);
+ this.props.highlightLink(result);
+
+ this.props.clearDetailsHistory();
+ })
+ .catch(error => this.props.handleConnectionError(error));;
+ }
+ }
+
+ draw = async (layer: string, url: string) => {
+
+ fetch(url)
+ .then(result => verifyResponse(result))
+ .then(res => res.json())
+ .then(result => {
+ isLoadingInProgress = false;
+ if (map.getSource(layer)) {
+ (map.getSource(layer) as mapboxgl.GeoJSONSource).setData(result);
+ }
+ })
+ .catch(error => this.props.handleConnectionError(error));;
+ }
+
+ render() {
+
+ const reachabe = this.props.isTopoServerReachable && this.props.isTileServerReachable;
+
+ return <>
+
+ <div id="map" style={{ width: "70%", position: 'relative' }} ref={myRef} >
+ {
+ this.state.isPopupOpen &&
+ <MapPopup onClose={() => { this.setState({ isPopupOpen: false }); }} />
+ }
+ <ConnectionInfo />
+ </div>
+ </>
+ }
+
+}
+
+type mapProps = RouteComponentProps & Connect<typeof mapStateToProps, typeof mapDispatchToProps>;
+
+const mapStateToProps = (state: IApplicationStoreState) => ({
+ selectedLink: state.network.map.selectedLink,
+ selectedSite: state.network.map.selectedSite,
+ zoomToElement: state.network.map.zoomToElement,
+ alarmlement: state.network.map.alarmlement,
+ lat: state.network.map.lat,
+ lon: state.network.map.lon,
+ zoom: state.network.map.zoom,
+ linkCount: state.network.map.statistics.links,
+ siteCount: state.network.map.statistics.sites,
+ isTopoServerReachable: state.network.connectivity.isToplogyServerAvailable,
+ isTileServerReachable: state.network.connectivity.isTileServerAvailable,
+ showIcons: state.network.map.allowIconSwitch
+
+
+
+});
+
+const mapDispatchToProps = (dispatcher: IDispatcher) => ({
+ selectSite: (site: site) => dispatcher.dispatch(new SelectSiteAction(site)),
+ selectLink: (link: link) => dispatcher.dispatch(new SelectLinkAction(link)),
+ clearDetailsHistory: () => dispatcher.dispatch(new ClearHistoryAction()),
+ selectMultipleLinks: (ids: string[]) => dispatcher.dispatch(new SelectMultipleLinksAction(ids)),
+ selectMultipleSites: (ids: string[]) => dispatcher.dispatch(new SelectMultipleSitesAction(ids)),
+ setPopupPosition: (x: number, y: number) => dispatcher.dispatch(new SetPopupPositionAction(x, y)),
+ highlightLink: (link: link) => dispatcher.dispatch(new HighlightLinkAction(link)),
+ highlightSite: (site: site) => dispatcher.dispatch(new HighlightSiteAction(site)),
+ updateMapPosition: (lat: number, lon: number, zoom: number) => dispatcher.dispatch(new SetCoordinatesAction(lat, lon, zoom)),
+ setStatistics: (linkCount: string, siteCount: string) => dispatcher.dispatch(new SetStatistics(siteCount, linkCount)),
+ setTileServerLoaded: (reachable: boolean) => dispatcher.dispatch(new IsTileServerReachableAction(reachable)),
+ handleConnectionError: (error: Error) => dispatcher.dispatch(handleConnectionError(error))
+})
+
+export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Map)); \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx
new file mode 100644
index 000000000..040024760
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/components/mapPopup.tsx
@@ -0,0 +1,94 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { Typography, Select, MenuItem, ClickAwayListener, Popper, Paper, FormGroup, Portal, Popover } from '@material-ui/core';
+import { SelectSiteAction, ClearHistoryAction, ClearDetailsAction } from '../actions/detailsAction';
+import { site } from '../model/site';
+import { link } from '../model/link';
+import { URL_API } from '../config';
+import { HighlightLinkAction, HighlightSiteAction } from '../actions/mapActions';
+import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
+import connect, { IDispatcher, Connect } from '../../../../framework/src/flux/connect';
+import { verifyResponse, handleConnectionError } from '../actions/connectivityAction';
+
+
+
+
+const MapPopup: React.FunctionComponent<props> = (props) => {
+
+ const [value, setValue] = React.useState("");
+
+ const handleChange = (event: any) => {
+ setValue(event.target.value);
+
+ const id = event.target.value;
+
+
+ fetch(`${URL_API}/${props.type}/${id}`)
+ .then(result => verifyResponse(result))
+ .then(res => res.json())
+ .then(result => {
+ props.clearDetailsHistory();
+ props.selectElement(result);
+ props.type === "link" ? props.highlightLink(result) : props.highlightSite(result)
+ props.onClose();
+ })
+ .catch(error => {
+ props.handleConnectionError(error);
+ props.onClose();
+ // props.clearDetails();
+ });
+ };
+
+ return <>
+ <Popover open={true} anchorEl={undefined} onClose={props.onClose} anchorReference="anchorPosition" anchorPosition={{ top: props.position.left, left: props.position.top }}>
+ <Paper style={{ padding: "15px" }}>
+ <Typography variant="h5">{`Multiple ${props.type.toLowerCase()}s were selected`}</Typography>
+ <Typography variant="body1">Please select one.</Typography>
+ <Select style={{ width: 300 }} onChange={handleChange} value={value} native>
+ <option value={""} disabled>{props.type} ids</option>
+ {
+ props.ids.map(id => <option key={id} value={id}>{id}</option>)
+ }
+ </Select>
+ </Paper>
+ </Popover>
+ </>
+}
+
+type props = Connect<typeof mapStateToProps, typeof mapDispatchToProps>& { onClose(): void }
+
+const mapStateToProps = (state: IApplicationStoreState) => ({
+ ids: state.network.popup.selectionPendingForIds,
+ type: state.network.popup.pendingDataType,
+ position: state.network.popup.position
+
+});
+
+const mapDispatchToProps = (dispatcher: IDispatcher) => ({
+ selectElement: (site: site) => dispatcher.dispatch(new SelectSiteAction(site)),
+ clearDetailsHistory:()=> dispatcher.dispatch(new ClearHistoryAction()),
+ highlightLink: (link: link) => dispatcher.dispatch(new HighlightLinkAction(link)),
+ highlightSite: (site: site) => dispatcher.dispatch(new HighlightSiteAction(site)),
+ handleConnectionError: (error:Error) => dispatcher.dispatch(handleConnectionError(error)),
+ clearDetails: () => dispatcher.dispatch(new ClearDetailsAction()),
+
+});
+
+export default (connect(mapStateToProps, mapDispatchToProps))(MapPopup); \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/config.ts b/sdnr/wt/odlux/apps/networkMapApp/src/config.ts
new file mode 100644
index 000000000..bdfcd2407
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/config.ts
@@ -0,0 +1,50 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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_API="/topology"
+export const URL_TILE_API = '/tiles';
+
+
+export const OSM_STYLE = {
+ 'version': 8,
+ 'sources': {
+ 'raster-tiles': {
+ 'type': 'raster',
+ 'tiles': [
+ URL_TILE_API+'/{z}/{x}/{y}.png'
+
+ ],
+ 'tileSize': 256,
+ 'attribution':
+ 'Data by <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>'
+ }
+ },
+ 'layers': [
+ {
+ 'id': 'simple-tiles',
+ 'type': 'raster',
+ 'source': 'raster-tiles',
+ 'minzoom': 0,
+ 'maxzoom': 22
+ }
+ ]
+};
+
+export const URL_BASEPATH = "network";
+
+
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts
new file mode 100644
index 000000000..7214705e1
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/connectivityReducer.ts
@@ -0,0 +1,38 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { IActionHandler } from "../../../../framework/src/flux/action";
+import { IsTopologyServerReachableAction, IsTileServerReachableAction } from "../actions/connectivityAction";
+
+
+export type connectivityState = {isToplogyServerAvailable: boolean, isTileServerAvailable: boolean };
+
+const initialState: connectivityState = {isToplogyServerAvailable: true, isTileServerAvailable: true};
+
+export const ConnectivityReducer: IActionHandler<connectivityState> =(state=initialState, action)=> {
+
+ if(action instanceof IsTopologyServerReachableAction){
+ state = Object.assign({}, state, { isToplogyServerAvailable: action.reachable });
+ }
+ else if (action instanceof IsTileServerReachableAction){
+ state = Object.assign({}, state, { isTileServerAvailable: action.reachable });
+
+ }
+
+ return state;
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/detailsReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/detailsReducer.ts
new file mode 100644
index 000000000..f573009bd
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/detailsReducer.ts
@@ -0,0 +1,70 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { IActionHandler } from '../../../../framework/src/flux/action';
+import { link } from "../model/link";
+import { site, Device } from "../model/site";
+import { HistoryEntry } from "../model/historyEntry";
+import { SelectSiteAction, SelectLinkAction, AddToHistoryAction, ClearHistoryAction, IsBusyCheckingDeviceListAction, FinishedLoadingDeviceListAction, ClearLoadedDevicesAction, ClearDetailsAction } from '../actions/detailsAction';
+
+export type DetailsStoreState={
+ data: site | link | null,
+ history: HistoryEntry[],
+ isBusyCheckingDeviceList: boolean,
+ checkedDevices: Device[] | null
+
+}
+
+const initialState: DetailsStoreState = {
+ data: null,
+ history:[],
+ isBusyCheckingDeviceList: false,
+ checkedDevices: null
+}
+
+export const DetailsReducer:IActionHandler<DetailsStoreState>=(state = initialState, action)=>{
+
+ if(action instanceof SelectSiteAction){
+ state= Object.assign({}, state, {data: action.site});
+ }
+ else if(action instanceof SelectLinkAction){
+ state = Object.assign({}, state, {data: action.link});
+ }else if(action instanceof ClearDetailsAction){
+ state = Object.assign({}, state, {data: null});
+ }
+ else if(action instanceof AddToHistoryAction){
+ state = Object.assign({}, state, {history: [...state.history, action.entry]})
+
+ }else if(action instanceof ClearHistoryAction){
+ state = Object.assign({}, state, {history: []});
+
+ }else if(action instanceof IsBusyCheckingDeviceListAction){
+ state = Object.assign({}, state, {isBusyCheckingDeviceList: action.isBusy});
+ }else if (action instanceof FinishedLoadingDeviceListAction){
+ state = Object.assign({}, state, {checkedDevices: action.devices});
+
+ }else if(action instanceof ClearLoadedDevicesAction){
+ state = Object.assign({}, state, {checkedDevices: null});
+
+ }
+
+
+ return state;
+
+}
+
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts
new file mode 100644
index 000000000..a140e9b77
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/mapReducer.ts
@@ -0,0 +1,81 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { IActionHandler } from '../../../../framework/src/flux/action';
+import { Feature } from "../model/Feature";
+import { HighlightLinkAction, HighlightSiteAction, ZoomToSearchResultAction, AddAlarmAction, SetCoordinatesAction, SetStatistics, SetIconSwitchAction, RemoveHighlightingAction } from '../actions/mapActions';
+
+export type location = {lat: number, lon:number}
+
+export type mapState = {
+ selectedLink: Feature | null,
+ selectedSite: Feature | null,
+ zoomToElement: location | null,
+ alarmlement: Feature|null,
+ lat: number,
+ lon: number,
+ zoom: number,
+ statistics:{links: string, sites: string},
+ allowIconSwitch: boolean
+}
+
+const initialState: mapState ={
+ selectedLink: null,
+ selectedSite: null,
+ zoomToElement: null,
+ alarmlement: null,
+ lat: 52,
+ lon: 13,
+ zoom: 10,
+ statistics:{links:"Not counted yet.", sites: "Not counted yet."},
+ allowIconSwitch: true
+}
+
+export const MapReducer: IActionHandler<mapState> = (state=initialState, action: any) => {
+
+ if(action instanceof HighlightLinkAction){
+
+ state = Object.assign({}, state, {selectedSite: null, selectedLink:{type: "Feature", geometry:{type:"LineString", coordinates:[[action.link.locationA.lon,action.link.locationA.lat ],[action.link.locationB.lon,action.link.locationB.lat ]]}}})
+
+
+ }
+ else if(action instanceof HighlightSiteAction){
+
+ state = Object.assign({}, state, {selectedLink: null, selectedSite:{type: "Feature", properties: {id: action.site.name, type:action.site.type}, geometry:{type:"Point", coordinates:[action.site.geoLocation.lon,action.site.geoLocation.lat ]}}})
+
+ }else if (action instanceof ZoomToSearchResultAction){
+ state = Object.assign({}, state, {zoomToElement:{lat: action.lat, lon: action.lon}});
+ }else if (action instanceof AddAlarmAction){
+ state = Object.assign({}, state, {alarmlement:action.element});
+
+ }else if(action instanceof SetCoordinatesAction){
+ state = Object.assign({}, state, {lat:action.lat, lon: action.lon, zoom:action.zoom});
+
+ }else if(action instanceof SetStatistics){
+ state = Object.assign({}, state, {statistics:{sites: action.siteCount, links: action.linkCount}});
+
+ }else if (action instanceof SetIconSwitchAction){
+ state = Object.assign({}, state, {allowIconSwitch: action.enable});
+
+ }else if(action instanceof RemoveHighlightingAction){
+ state = Object.assign({}, state, {selectedLink: null, selectedSite:null})
+
+ }
+
+ return state;
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/popupReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/popupReducer.ts
new file mode 100644
index 000000000..dcac9c9c2
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/popupReducer.ts
@@ -0,0 +1,50 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { IActionHandler } from '../../../../framework/src/flux/action';
+import { SelectMultipleLinksAction, SelectMultipleSitesAction, SetPopupPositionAction } from "../actions/popupActions";
+
+export type popupStoreState = {
+ selectionPendingForIds: string[],
+ pendingDataType: "link"|"site"| "",
+ position: { top: number, left: number }
+};
+
+const initialState: popupStoreState = {
+ selectionPendingForIds: [],
+ pendingDataType: "",
+ position: { top: 0, left: 0 }
+};
+
+export const PopupsReducer: IActionHandler<popupStoreState> = (state = initialState, action) => {
+
+ if(action instanceof SelectMultipleLinksAction){
+ state = Object.assign({}, state, { selectionPendingForIds: action.ids, pendingDataType: "link", isSelectionNeeded: true });
+
+ }else if(action instanceof SelectMultipleSitesAction){
+ state = Object.assign({}, state, { selectionPendingForIds: action.ids, pendingDataType: "site", isSelectionNeeded: true });
+
+ }else if(action instanceof SetPopupPositionAction){
+ state= Object.assign({}, state, {position:{top:action.top, left: action.left}})
+
+ }
+
+
+ return state;
+
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts
new file mode 100644
index 000000000..4069ed24d
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/handlers/rootReducer.ts
@@ -0,0 +1,47 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { combineActionHandler } from '../../../../framework/src/flux/middleware';
+
+import { DetailsReducer, DetailsStoreState } from "./detailsReducer";
+import { PopupsReducer, popupStoreState } from "./popupReducer";
+import { MapReducer, mapState } from "./mapReducer";
+import { connectivityState, ConnectivityReducer } from './connectivityReducer';
+
+export interface INetworkAppStoreState{
+ details: DetailsStoreState,
+ popup: popupStoreState,
+ map: mapState,
+ connectivity: connectivityState
+}
+
+declare module '../../../../framework/src/store/applicationStore' {
+ interface IApplicationStoreState {
+ network: INetworkAppStoreState
+ }
+ }
+
+const appHandler = {
+ details: DetailsReducer,
+ popup: PopupsReducer,
+ map: MapReducer,
+ connectivity: ConnectivityReducer};
+
+export const networkmapRootHandler = combineActionHandler<INetworkAppStoreState>(appHandler)
+
+export default networkmapRootHandler; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/index.html b/sdnr/wt/odlux/apps/networkMapApp/src/index.html
new file mode 100644
index 000000000..f68e8c13b
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/index.html
@@ -0,0 +1,30 @@
+<!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>Networkmap App</title>
+</head>
+
+<body>
+ <div id="app"></div>
+ <script type="text/javascript" src="./require.js"></script>
+ <script type="text/javascript" src="./config.js"></script>
+ <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.8.1/mapbox-gl.css' rel='stylesheet' />
+
+ <script>
+ // run the application
+ require(["app","connectApp","faultApp", "networkMapApp", "configurationApp"], function (app, connectApp, faultApp, networkMapApp, configurationApp) {
+ connectApp.register();
+ //faultApp.register();
+ configurationApp.register();
+ networkMapApp.register();
+ app("./app.tsx").runApplication();
+ });
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/model/Feature.ts b/sdnr/wt/odlux/apps/networkMapApp/src/model/Feature.ts
new file mode 100644
index 000000000..c4f9ad1cb
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/model/Feature.ts
@@ -0,0 +1,25 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 Feature = {type: "Feature", properties: {id: string, type?: string}, geometry: Geometry}
+
+export type Geometry = Point | LineString;
+
+type Point = {type: "Point", coordinates: number[]}
+
+type LineString ={type: "LineString", coordinates: number[][]} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/model/count.ts b/sdnr/wt/odlux/apps/networkMapApp/src/model/count.ts
new file mode 100644
index 000000000..726e2ff76
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/model/count.ts
@@ -0,0 +1,19 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 elementCount ={sites: string, links: string} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/model/historyEntry.ts b/sdnr/wt/odlux/apps/networkMapApp/src/model/historyEntry.ts
new file mode 100644
index 000000000..707ff3d2a
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/model/historyEntry.ts
@@ -0,0 +1,22 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { site } from "./site";
+import { link } from "./link";
+
+export type HistoryEntry={id: string, data: site|link}; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/model/link.ts b/sdnr/wt/odlux/apps/networkMapApp/src/model/link.ts
new file mode 100644
index 000000000..a6ef65c98
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/model/link.ts
@@ -0,0 +1,30 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 link = {id: string,
+ name: string,
+ length: number,
+ calculatedLength: number,
+ type: string,
+ siteA: string,
+ siteB: string,
+ azimuthA: number,
+ azimuthB: number,
+ locationA:{lon: number, lat: number},
+ locationB:{lon: number, lat: number}
+ }; \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/model/site.ts b/sdnr/wt/odlux/apps/networkMapApp/src/model/site.ts
new file mode 100644
index 000000000..79af65377
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/model/site.ts
@@ -0,0 +1,43 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { link } from "./link";
+
+export type site = {
+ id: string,
+ name: string,
+ address?: string,
+ heighAGLInMeters?: number, //AboveGroundLevel
+ antennaHeightAGLInMeters?: number,
+ type?: string,
+ operator: string,
+ geoLocation:{lon: number, lat: number},
+ devices: Device[],
+ links: link[]
+}
+
+export type Device = {
+ id: string,
+ type: string,
+ name: string,
+ manufacture: string,
+ owner: string,
+ status?: string,
+ port: number[],
+ simulatorId?: string,
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx b/sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx
new file mode 100644
index 000000000..0ff9418ac
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/pluginTransport.tsx
@@ -0,0 +1,101 @@
+/**
+* ============LICENSE_START========================================================================
+* ONAP : ccsdk feature sdnr wt odlux
+* =================================================================================================
+* 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==========================================================================
+*/
+// app configuration and main entry point for the app
+
+import * as React from "react";
+import { faMapMarked } from '@fortawesome/free-solid-svg-icons'; // select app icon
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 applicationManager from '../../../framework/src/services/applicationManager';
+
+
+import { networkmapRootHandler } from './handlers/rootReducer';
+import MainView from "./App";
+import { subscribe, IFormatedMessage } from "../../../framework/src/services/notificationService";
+import applicationApi from "../../../framework/src/services/applicationApi";
+import { UpdateDetailsView } from "./actions/detailsAction";
+import { findSiteToAlarm } from "./actions/mapActions";
+import { URL_BASEPATH } from "./config";
+
+const App : React.SFC = (props) => {
+ return <MainView />
+};
+
+export function register() {
+ applicationManager.registerApplication({
+ name: URL_BASEPATH, // used as name of state as well
+ icon: faMapMarked,
+ rootActionHandler: networkmapRootHandler,
+ rootComponent: App,
+ menuEntry: "Network Map"
+ });
+}
+
+type ObjectNotification = {
+ counter: string;
+ nodeName: string;
+ objectId: string;
+ timeStamp: string;
+}
+
+type FaultAlarmNotification = {
+ id: string;
+ nodeName: string;
+ counter: number;
+ timeStamp: string;
+ objectId: string;
+ problem: string;
+ severity: null | 'Warning' | 'Minor' | 'Major' | 'Critical';
+ type: string;
+ sourceType: string;
+}
+
+// subscribe to the websocket notifications from connect
+subscribe<ObjectNotification & IFormatedMessage>(["ObjectCreationNotification", "ObjectDeletionNotification", "AttributeValueChangedNotification"], (msg => {
+ const store = applicationApi.applicationStore;
+
+ //store && store.dispatch(UpdateDetailsView(msg.nodeName))
+
+}));
+
+
+subscribe<FaultAlarmNotification & IFormatedMessage>("ProblemNotification", (fault => {
+ const store = applicationApi && applicationApi.applicationStore;
+ if (fault && store) {
+ // store.dispatch(findSiteToAlarm(fault.nodeName));
+
+
+ }
+}));
+
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/styles/index.css b/sdnr/wt/odlux/apps/networkMapApp/src/styles/index.css
new file mode 100644
index 000000000..ec2585e8c
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/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/networkMapApp/src/styles/mapbox-gl.css b/sdnr/wt/odlux/apps/networkMapApp/src/styles/mapbox-gl.css
new file mode 100644
index 000000000..03c479af9
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/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/networkMapApp/src/utils/mapLayers.ts b/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts
new file mode 100644
index 000000000..e11d96493
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapLayers.ts
@@ -0,0 +1,398 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { Feature } from 'model/Feature';
+
+
+export const addBaseLayers = (map: mapboxgl.Map, selectedPoint: Feature|null, selectedLine: Feature|null) => {
+
+ const boundingBox = map.getBounds();
+ map.addSource('lines', {
+ type: 'geojson',
+ data: { type: "FeatureCollection", features: [] }
+ });
+
+ const features = selectedLine !== null ? [selectedLine] : [];
+
+ map.addSource('selectedLine', {
+ type: 'geojson',
+ data: { type: "FeatureCollection", features: features }
+ });
+
+ map.addSource('points', {
+ type: 'geojson',
+ data: { type: "FeatureCollection", features: [] }
+ });
+
+ const selectedPointFeature = selectedPoint !== null ? [selectedPoint] : [];
+
+
+ map.addSource('selectedPoints', {
+ type: 'geojson',
+ data: { type: "FeatureCollection", features: selectedPointFeature }
+
+ });
+
+ map.addLayer({
+ 'id': 'lines',
+ 'type': 'line',
+ 'source': 'lines',
+ 'layout': {
+ 'line-join': 'round',
+ 'line-cap': 'round'
+ },
+ 'paint': {
+ 'line-color': '#888',
+ 'line-width': 2
+ }
+ });
+
+ map.addLayer({
+ 'id': 'selectedLine',
+ 'type': 'line',
+ 'source': 'selectedLine',
+ 'layout': {
+ 'line-join': 'round',
+ 'line-cap': 'round'
+ },
+ 'paint': {
+ 'line-color': '#888',
+ 'line-width': 4
+ }
+ });
+
+
+
+ map.addLayer({
+ id: 'points',
+ source: 'points',
+ type: 'circle',
+ paint: {
+ 'circle-color': '#11b4da',
+ 'circle-radius': 7,
+ 'circle-stroke-width': 1,
+ 'circle-stroke-color': '#fff'
+ }
+ });
+
+ map.addLayer({
+ id: 'selectedPoints',
+ source: 'selectedPoints',
+ type: 'circle',
+ paint: {
+ 'circle-color': '#116bda',
+ 'circle-radius': 9,
+ 'circle-stroke-width': 1,
+ 'circle-stroke-color': '#fff'
+ }
+ });
+
+ map.addSource("alarmedPoints", {
+ type: 'geojson',
+ data: {type:"FeatureCollection", features:[]}
+ })
+
+ map.addLayer({
+ id: 'alarmedPoints',
+ source: 'alarmedPoints',
+ type: 'circle',
+ paint: {
+ 'circle-color': '#CC0000',
+ 'circle-radius': 9,
+ 'circle-stroke-width': 1,
+ 'circle-stroke-color': '#fff'
+ }
+ });
+}
+
+export const removeBaseLayers = (map: mapboxgl.Map) => {
+
+ map.removeLayer("points");
+ map.removeLayer("lines");
+ map.removeLayer('selectedPoints');
+ map.removeLayer('selectedLine');
+
+ map.removeSource("points");
+ map.removeSource("lines");
+ map.removeSource('selectedPoints');
+ map.removeSource('selectedLine');
+}
+
+let checkedLayers = false;
+
+const createFilter = (type:'street lamp'|'high rise building'|'data center', selectedSiteId?:string) =>{
+
+ return selectedSiteId === undefined ? ['==', 'type', type] : ["all", ['==', 'type', type], ['!=', 'id', selectedSiteId]]
+}
+
+export const showIconLayers = (map: mapboxgl.Map, show: boolean, selectedSiteId?: string) => {
+
+ const zoom = map.getZoom();
+
+ if(show){
+
+ if (zoom > 11) {
+
+ const bounds = map.getBounds();
+
+ if(map.getLayer('points')!== undefined && map.getLayer('point-lamps')===undefined && !checkedLayers){
+
+ // if sites don't have a type don't change layers to icons
+ const elements = map.queryRenderedFeatures( undefined,{
+ layers: ['points'], filter:['has', 'type']
+ });
+ checkedLayers=true;
+
+ if(elements.length>0 && elements.length<1000){
+
+ if (map.getLayer('point-lamps') === undefined) {
+ map.removeLayer('points');
+
+ map.setLayoutProperty('selectedPoints', 'visibility', 'none');
+
+ map.addLayer({
+ 'id': 'point-lamps',
+ 'type': 'symbol',
+ 'source': 'points',
+ 'layout': {
+ 'icon-allow-overlap': true,
+ 'icon-image': 'lamp',
+ 'icon-size': 0.1
+
+ },
+ 'filter': createFilter("street lamp", selectedSiteId),
+ });
+
+ map.addLayer({
+ 'id': 'point-building',
+ 'type': 'symbol',
+ 'source': 'points',
+ 'filter': createFilter("high rise building", selectedSiteId),
+ 'layout': {
+ 'icon-allow-overlap': true,
+ 'icon-image': 'house',
+ 'icon-size': 0.1
+ }
+ });
+
+ map.addLayer({
+ 'id': 'point-data-center',
+ 'type': 'symbol',
+ 'source': 'points',
+ 'filter': createFilter("data center", selectedSiteId),
+ 'layout': {
+ 'icon-allow-overlap': true,
+ 'icon-image': 'data-center',
+ 'icon-size': 0.1
+ } });
+
+ map.addLayer({
+ id: 'point-remaining',
+ source: 'points',
+ type: 'circle',
+ 'filter': ['==', 'type', ''],
+ paint: {
+ 'circle-color': '#11b4da',
+ 'circle-radius': 7,
+ 'circle-stroke-width': 1,
+ 'circle-stroke-color': '#fff'
+ }
+ });
+
+ map.addLayer({
+ 'id': 'select-point-lamps',
+ 'type': 'symbol',
+ 'source': 'selectedPoints',
+ 'layout': {
+ 'icon-allow-overlap': true,
+ 'icon-image': 'lamp',
+ 'icon-size': 0.15
+
+ },
+ 'filter': ['==', 'type', 'street lamp'],
+ });
+
+ map.addLayer({
+ 'id': 'select-point-buildings',
+ 'type': 'symbol',
+ 'source': 'selectedPoints',
+ 'filter': ['==', 'type', 'high rise building'],
+ 'layout': {
+ 'icon-allow-overlap': true,
+ 'icon-image': 'house',
+ 'icon-size': 0.15
+ }
+ });
+
+ map.addLayer({
+ 'id': 'select-point-data-center',
+ 'type': 'symbol',
+ 'source': 'selectedPoints',
+ 'filter': ['==', 'type', 'data center'],
+ 'layout': {
+ 'icon-allow-overlap': true,
+ 'icon-image': 'data-center',
+ 'icon-size': 0.15
+ }
+ });
+ }
+ }
+ }
+
+ } else {
+ swapLayersBack(map);
+ }
+}else{
+ swapLayersBack(map);
+
+}
+
+}
+
+export const swapLayersBack = (map: mapboxgl.Map) =>{
+ checkedLayers=false;
+ if (map.getLayer('points') === undefined) {
+
+ map.setLayoutProperty('selectedPoints', 'visibility', 'visible');
+
+ map.removeLayer('point-building');
+ map.removeLayer('point-lamps');
+ map.removeLayer('point-data-center');
+ map.removeLayer('point-remaining');
+ map.removeLayer('select-point-data-center');
+ map.removeLayer('select-point-buildings');
+ map.removeLayer('select-point-lamps');
+
+
+
+ map.addLayer({
+ id: 'points',
+ source: 'points',
+ type: 'circle',
+ paint: {
+ 'circle-color': '#11b4da',
+ 'circle-radius': 7,
+ 'circle-stroke-width': 1,
+ 'circle-stroke-color': '#fff'
+ }
+ });
+
+ map.moveLayer('points', map.getLayer('selectedPoints').id)
+
+
+ }
+}
+
+const addClusterLayers = (map: mapboxgl.Map, data: any) => {
+ map.addSource('clusters', {
+ type: 'geojson',
+ data: data
+ });
+
+ map.addSource('selectedLine', {
+ type: 'geojson',
+ data: { type: "FeatureCollection", features: [] }
+ });
+
+ map.addSource('selectedPoints', {
+ type: 'geojson',
+ data: { type: "FeatureCollection", features: [] }
+
+ });
+
+ map.addLayer({
+ id: 'clusters',
+ type: 'circle',
+ source: 'clusters',
+ filter: ['has', 'count'],
+ paint: {
+ 'circle-color': [
+ 'step',
+ ['get', 'count'],
+ '#51bbd6',
+ 100,
+ '#f1f075',
+ 750,
+ '#f28cb1'
+ ],
+ 'circle-radius': [
+ 'step',
+ ['get', 'count'],
+ 20,
+ 100,
+ 30,
+ 750,
+ 40
+ ]
+ }
+ });
+
+
+ map.addLayer({
+ id: 'cluster-count',
+ type: 'symbol',
+ source: 'clusters',
+ filter: ['has', 'count'],
+ layout: {
+ 'text-field': '{count}',
+ 'text-font': ['Roboto Bold'],
+ 'text-size': 12
+ }
+ });
+
+ map.addLayer({
+ 'id': 'selectedLine',
+ 'type': 'line',
+ 'source': 'selectedLine',
+ 'layout': {
+ 'line-join': 'round',
+ 'line-cap': 'round'
+ },
+ 'paint': {
+ 'line-color': '#888',
+ 'line-width': 4
+ }
+ });
+
+ map.addLayer({
+ id: 'unclustered-points',
+ source: 'clusters',
+ filter: ['!', ['has', 'count'],],
+ type: 'circle',
+ paint: {
+ 'circle-color': '#11b4da',
+ 'circle-radius': 7,
+ 'circle-stroke-width': 1,
+ 'circle-stroke-color': '#fff'
+ }
+ });
+
+ map.addLayer({
+ id: 'selectedPoints',
+ source: 'selectedPoints',
+ type: 'circle',
+ paint: {
+ 'circle-color': '#116bda',
+ 'circle-radius': 9,
+ 'circle-stroke-width': 1,
+ 'circle-stroke-color': '#fff'
+ }
+ });
+
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapUtils.ts b/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapUtils.ts
new file mode 100644
index 000000000..34cdc0638
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/utils/mapUtils.ts
@@ -0,0 +1,135 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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==========================================================================
+ */
+
+const EARTHRADIUSM = 6378137;
+
+type updatedCoordinates = { south: number, west: number, north: number, east: number };
+
+
+
+
+export const addDistance = (south: number, west: number, north: number, east: number, distanceKm: number): updatedCoordinates => {
+
+ const distanceInM = distanceKm * 1000;
+
+ const dLat = distanceInM / EARTHRADIUSM;
+ const dLon = distanceInM / (EARTHRADIUSM * Math.cos(Math.PI * (north + south) / 360));
+
+ const latOffset = dLat * 180 / Math.PI;
+ const lonOffset = dLon * 180 / Math.PI;
+
+ const newEast = checkLongitude(east + lonOffset);
+ const newWest = checkLongitude(west - lonOffset);
+ const newNorth = checkLatitude(north + latOffset);
+ const newSouth = checkLatitude(south - latOffset);
+
+ return { east: newEast, north: newNorth, south: newSouth, west: newWest };
+
+}
+
+
+//taken from https://www.movable-type.co.uk/scripts/latlong.html
+export 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)];
+}
+
+
+export const LatLonToDMS = (value:number, isLon:boolean=false) =>{
+ const absoluteValue = Math.abs(value);
+ const d = Math.floor(absoluteValue);
+ const m = Math.floor((absoluteValue -d)* 60);
+ const s = (absoluteValue - d - m / 60 ) * 3600;
+ const dms=`${d}° ${m}' ${s.toFixed(2)}"`
+
+ const sign = Math.sign(value);
+
+ if(isLon){
+ return (sign === -1 || sign === -0 ) ? dms + " W" : dms + " E";
+ }else{
+ return (sign === -1 || sign === -0 ) ? dms + " S" : dms + " N";
+ }
+}
+
+// Because features come from tiled vector data, feature geometries may be split
+// or duplicated across tile boundaries and, as a result, features may appear
+// multiple times in query results.
+
+//taken from https://docs.mapbox.com/mapbox-gl-js/example/filter-features-within-map-view/
+
+export const getUniqueFeatures = (array: mapboxgl.MapboxGeoJSONFeature[], comparatorProperty:string) =>{
+ var existingFeatureKeys: any = {};
+
+ var uniqueFeatures = array.filter(function(el) {
+ if (existingFeatureKeys[el.properties![comparatorProperty]]) {
+ return false;
+ } else {
+ existingFeatureKeys[el.properties![comparatorProperty]] = true;
+ return true;
+ }
+ });
+
+ return uniqueFeatures;
+ }
+
+const radians_to_degrees = (radians:number) =>{
+
+ var pi = Math.PI;
+ return radians * (180/pi);
+}
+
+ const degrees_to_radians = (degrees: number) =>
+ {
+ return degrees * (Math.PI/180);
+ }
+
+
+const checkLatitude = (lat: number) => {
+
+ if (lat > 90)
+ return 90;
+ else if (lat < -90)
+ return -90;
+ else
+ return lat;
+
+}
+
+const checkLongitude = (lon: number) => {
+ if (lon > 180)
+ return 180;
+ else if (lon < -180)
+ return -180;
+ else
+ return lon;
+}
+
+
+
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src/utils/utils.ts b/sdnr/wt/odlux/apps/networkMapApp/src/utils/utils.ts
new file mode 100644
index 000000000..20c4e5aad
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/src/utils/utils.ts
@@ -0,0 +1,25 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2020 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 { link } from "../model/link";
+import { site } from "../model/site";
+
+
+export function isSite(data: link | site): data is site {
+ return (data as site).geoLocation !== undefined;
+} \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src2/main/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/MyOdluxBundle.java b/sdnr/wt/odlux/apps/networkMapApp/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/networkMapApp/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/networkMapApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml b/sdnr/wt/odlux/apps/networkMapApp/src2/main/resources/OSGI-INF/blueprint/blueprint.xml
new file mode 100644
index 000000000..4ede94477
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/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="networkMapApp"/>
+ <property name="index" value="110"/>
+ </bean>
+</blueprint> \ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/networkMapApp/src2/test/java/org/onap/ccsdk/features/sdnr/wt/odlux/bundles/test/TestBundleRes.java b/sdnr/wt/odlux/apps/networkMapApp/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/networkMapApp/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/networkMapApp/src2/test/resources/test.js b/sdnr/wt/odlux/apps/networkMapApp/src2/test/resources/test.js
new file mode 100644
index 000000000..b47fdc39f
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/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/networkMapApp/tsconfig.json b/sdnr/wt/odlux/apps/networkMapApp/tsconfig.json
new file mode 100644
index 000000000..a66b5d828
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/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/networkMapApp/webpack.config.js b/sdnr/wt/odlux/apps/networkMapApp/webpack.config.js
new file mode 100644
index 000000000..5fc67e3ec
--- /dev/null
+++ b/sdnr/wt/odlux/apps/networkMapApp/webpack.config.js
@@ -0,0 +1,177 @@
+/**
+ * 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: {
+ networkMapApp: ["./pluginTransport.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]'
+ }
+ }]
+ }]
+ },
+
+ 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://10.20.6.29:8181",
+ secure: false
+ },
+ "/oauth2/": {
+ target: "http://10.20.6.29:8181",
+ secure: false
+ },
+ "/database/": {
+ target: "http://10.20.6.29:8181",
+ secure: false
+ },
+ "/restconf/": {
+ target: "http://10.20.6.29:8181",
+ secure: false
+ },
+ "/rests/": {
+ target: "http://10.20.6.29:8181",
+ secure: false
+ },
+ "/topology/": {
+ target: "http://localhost:3001",
+ secure: false
+ },
+ "/help/": {
+ target: "http://10.20.6.29:8181",
+ secure: false
+ },
+ "/websocket": {
+ target: "http://10.20.6.29:8181",
+ ws: true,
+ changeOrigin: true,
+ secure: false
+ }
+ }
+
+ }
+ }];
+}
diff --git a/sdnr/wt/odlux/framework/pom.xml b/sdnr/wt/odlux/framework/pom.xml
index a7c81ebb1..7145b4a1c 100644
--- a/sdnr/wt/odlux/framework/pom.xml
+++ b/sdnr/wt/odlux/framework/pom.xml
@@ -46,7 +46,7 @@
<properties>
<buildtime>${maven.build.timestamp}</buildtime>
<distversion>ONAP Frankfurt (Neon, mdsal ${odl.mdsal.version})</distversion>
- <buildno>62.ad364be(20/08/21)</buildno>
+ <buildno>64.b032889(20/08/31)</buildno>
<odlux.version>ONAP SDN-R | ONF Wireless for ${distversion} - Build: ${buildtime} ${buildno} ${project.version}</odlux.version>
</properties>
diff --git a/sdnr/wt/odlux/installer/pom.xml b/sdnr/wt/odlux/installer/pom.xml
index 5ff2791c2..7559b25d7 100644
--- a/sdnr/wt/odlux/installer/pom.xml
+++ b/sdnr/wt/odlux/installer/pom.xml
@@ -156,6 +156,14 @@
<type>jar</type>
<overWrite>false</overWrite>
</artifactItem>
+ <!-- networkMapApp-->
+ <artifactItem>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sdnr-wt-odlux-app-networkMapApp</artifactId>
+ <version>${project.version}</version>
+ <type>jar</type>
+ <overWrite>false</overWrite>
+ </artifactItem>
<!-- linkCalculationApp-->
<artifactItem>
<groupId>${project.groupId}</groupId>
diff --git a/sdnr/wt/odlux/pom.xml b/sdnr/wt/odlux/pom.xml
index a822d3d08..c4e1615d6 100644
--- a/sdnr/wt/odlux/pom.xml
+++ b/sdnr/wt/odlux/pom.xml
@@ -52,6 +52,7 @@
<module>apps/performanceHistoryApp</module>
<module>apps/eventLogApp</module>
<module>apps/configurationApp</module>
+ <module>apps/networkMapApp</module>
<module>apps/linkCalculationApp</module>
<module>apps/app-feature</module>
<module>apps/app-installer</module>