GUI2 Extract Topology view in to its own library

Change-Id: I45597d0902c99b5b3d606966866cc518011c54a0
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/mapsvg/mapsvg.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/mapsvg/mapsvg.component.ts
new file mode 100644
index 0000000..ec9bdae
--- /dev/null
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/mapsvg/mapsvg.component.ts
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+import {
+    Component, EventEmitter,
+    Input,
+    OnChanges, Output,
+    SimpleChanges
+} from '@angular/core';
+import { MapObject } from '../maputils';
+import {LogService, MapBounds} from 'gui2-fw-lib';
+import {HttpClient} from '@angular/common/http';
+import * as d3 from 'd3';
+import * as topojson from 'topojson-client';
+
+const BUNDLED_URL_PREFIX = 'data/map/';
+
+/**
+ * Model of the transform attribute of a topojson file
+ */
+interface TopoDataTransform {
+    scale: number[];
+    translate: number[];
+}
+
+/**
+ * Model of the Feature returned prom topojson library
+ */
+interface Feature {
+    geometry: Object;
+    id: string;
+    properties: Object;
+    type: string;
+}
+
+/**
+ * Model of the Features Collection returned by the topojson.features function
+ */
+interface FeatureCollection {
+    type: string;
+    features: Feature[];
+}
+
+/**
+ * Model of the topojson file
+ */
+interface TopoData {
+    type: string; // Usually "Topology"
+    objects: Object; // Can be a list of countries or individual countries
+    arcs: number[][][]; // Coordinates
+    bbox: number[]; // Bounding box
+    transform: TopoDataTransform; // scale and translate
+}
+
+@Component({
+    selector: '[onos-mapsvg]',
+    templateUrl: './mapsvg.component.html',
+    styleUrls: ['./mapsvg.component.css']
+})
+export class MapSvgComponent implements  OnChanges {
+    @Input() map: MapObject = <MapObject>{id: 'none'};
+    @Output() mapBounds = new EventEmitter<MapBounds>();
+
+    geodata: FeatureCollection;
+    pathgen: any; // (Feature) => string; have to leave it general, as there is the bounds method used below
+    // testPath: string;
+    // testFeature = <Feature>{
+    //     id: 'test',
+    //     type: 'Feature',
+    //     geometry: {
+    //         coordinates: [
+    //             [[-15, 60], [45, 60], [45, 45], [-15, 45], [-15, 60]],
+    //             [[-10, 55], [45, 55], [45, 50], [-10, 50], [-10, 55]],
+    //         ],
+    //         type: 'Polygon'
+    //     },
+    //     properties: { name: 'Test'}
+    // };
+
+    constructor(
+        private log: LogService,
+        private httpClient: HttpClient,
+    ) {
+        // Scale everything to 360 degrees wide and 150 high
+        // See background.component.html for more details
+        this.pathgen = d3.geoPath().projection(d3.geoIdentity().reflectY(true)
+            // MapSvgComponent.scale()
+        );
+
+        // this.log.debug('Feature Test',this.testFeature);
+        // this.testPath = this.pathgen(this.testFeature);
+        // this.log.debug('Feature Path', this.testPath);
+        this.log.debug('MapSvgComponent constructed');
+    }
+
+    static getUrl(id: string): string {
+        if (id && id[0] === '*') {
+            return BUNDLED_URL_PREFIX + id.slice(1) + '.topojson';
+        }
+        return id + '.topojson';
+    }
+
+    /**
+     * Wrapper for the path generator function
+     * @param feature The county or state within the map
+     */
+    pathGenerator(feature: Feature): string {
+        return this.pathgen(feature);
+    }
+
+    ngOnChanges(changes: SimpleChanges): void {
+        this.log.debug('Change detected', changes);
+        if (changes['map']) {
+            const map: MapObject = <MapObject>(changes['map'].currentValue);
+            if (map.id) {
+                this.httpClient
+                    .get(MapSvgComponent.getUrl(map.filePath))
+                    .subscribe((topoData: TopoData) => {
+                        // this.mapPathGenerator =
+                        this.handleTopoJson(map, topoData);
+                        this.log.debug('Path Generated for', map.id,
+                            'from', MapSvgComponent.getUrl(map.filePath));
+                    });
+            }
+        }
+    }
+
+    /**
+     * Handle the topojson file stream as it arrives back from the server
+     *
+     * The topojson library converts the topojson file in to a FeatureCollection
+     * d3.geo then further converts this in to a Path
+     *
+     * @param map The Map chosen in the GUI
+     * @param topoData The data in the TopoJson file
+     */
+    handleTopoJson(map: MapObject, topoData: TopoData): void {
+
+        let topoObject = topoData.objects[map.id];
+        if (!topoObject) {
+            topoObject = topoData.objects['states'];
+        }
+        this.log.debug('Topo obj', topoObject, 'topodata', topoData);
+        this.geodata = <FeatureCollection>topojson.feature(topoData, topoObject);
+        const bounds = this.pathgen.bounds(this.geodata);
+        this.mapBounds.emit(<MapBounds>{
+            lngMin: bounds[0][0],
+            latMin: -bounds[0][1], // Y was inverted in the transform
+            lngMax: bounds[1][0],
+            latMax: -bounds[1][1] // Y was inverted in the transform
+        });
+        this.log.debug('Map retrieved', topoData, this.geodata);
+
+    }
+}