GUI2 added method to zoom to map size

Change-Id: I3aa578b78ebe2ab26f72a7535b8b5e9e0a822cb6
diff --git a/web/gui2/package-lock.json b/web/gui2/package-lock.json
index 763b0c4..846cc0f 100644
--- a/web/gui2/package-lock.json
+++ b/web/gui2/package-lock.json
@@ -5560,7 +5560,7 @@
     },
     "gui2-fw-lib": {
       "version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.0.0.tgz",
-      "integrity": "sha512-+ueL0HHFkVkLJYVDNVFu2sZ19uSYT/dSOTyN0faPh6Xwdlsfz58JLSdL5L8wXMjI2QYPg8Ff5hjBhjtO7cJ7kA==",
+      "integrity": "sha512-3UMUj7LSENJ75R5yVAZM/KmvZAA/fCG+qJl7/1VnvI/0wJGjVPp4eTEHDH2hMOkJ/yw8b0VgQARdZR49zEc2iQ==",
       "requires": {
         "tslib": "1.9.3"
       }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
index b6290b6..9f8faf8 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
@@ -25,5 +25,5 @@
      countries in the northern and southern extremities, and so
      the map is squashed horizontally slightly here to compensate
      (with no squashing the width would be 2400)-->
-<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg [map]="map"
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg [map]="map" (mapBounds)="updatedBounds($event)"
        transform="translate(500,500), scale(5.5555,6.666666)"/>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
index 0732018..7fb0b3f 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
@@ -19,9 +19,8 @@
 import {MapSvgComponent} from '../mapsvg/mapsvg.component';
 import {from} from 'rxjs';
 import {HttpClient} from '@angular/common/http';
-import {LogService} from 'gui2-fw-lib';
+import {LocMeta, LogService, ZoomUtils} from 'gui2-fw-lib';
 import {MapObject} from '../maputils';
-import {LocMeta} from '../forcesvg/models';
 import {ForceSvgComponent} from '../forcesvg/forcesvg.component';
 import {
     DeviceNodeSvgComponent,
@@ -83,31 +82,8 @@
     });
 
     it('should convert latlong to xy', () => {
-        const result = BackgroundSvgComponent.convertGeoToCanvas(<LocMeta>{lat: 52, lng: -8});
+        const result = ZoomUtils.convertGeoToCanvas(<LocMeta>{lat: 52, lng: -8});
         expect(Math.round(result.x * 100)).toEqual(45556);
         expect(Math.round(result.y * 100)).toEqual(15333);
     });
-
-    /**
-     * For some reason including the following causes "ForceSvgComponent should create error
-     * TODO: Investigate
-     */
-
-    // it('should convert xy random extents to latlong', () => {
-    //     const result = BackgroundSvgComponent.convertXYtoGeo(455.556, 153.33);
-    //     expect(Math.round(result.equivLoc.lng)).toEqual(-8);
-    //     expect(Math.round(result.equivLoc.lat)).toEqual(52);
-    // });
-
-    // it('should convert xy min extents to latlong', () => {
-    //     const result = BackgroundSvgComponent.convertXYtoGeo(-500, 0);
-    //     expect(Math.round(result.equivLoc.lng)).toEqual(-180);
-    //     expect(Math.round(result.equivLoc.lat)).toEqual(75);
-    // });
-
-    // it('should convert xy full extents to latlong', () => {
-    //     const result = BackgroundSvgComponent.convertXYtoGeo(1500, 1000);
-    //     expect(Math.round(result.equivLoc.lng)).toEqual(180);
-    //     expect(Math.round(result.equivLoc.lat)).toEqual(-75);
-    // });
 });
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
index cc41fb6..daa4fcb 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, Input, OnInit} from '@angular/core';
+import {Component, EventEmitter, Input, Output} from '@angular/core';
 import {MapObject} from '../maputils';
-import {LocMeta, MetaUi} from '../forcesvg/models';
+import {MapBounds, TopoZoomPrefs, LogService, ZoomUtils} from 'gui2-fw-lib';
 
 /**
  * model of the topo2CurrentLayout attrs from BgZoom below
@@ -69,53 +69,39 @@
     regionName: string;
 }
 
-const LONGITUDE_EXTENT = 180;
-const LATITUDE_EXTENT = 75;
-
 /**
  * ONOS GUI -- Topology Background Layer View.
+ *
+ * TODO: consider that this layer has only one component the MapSvg and hence
+ * might be able to be eliminated
  */
 @Component({
     selector: '[onos-backgroundsvg]',
     templateUrl: './backgroundsvg.component.html',
     styleUrls: ['./backgroundsvg.component.css']
 })
-export class BackgroundSvgComponent implements OnInit {
+export class BackgroundSvgComponent {
     @Input() map: MapObject;
+    @Output() zoomlevel = new EventEmitter<TopoZoomPrefs>();
 
     layoutData: Layout = <Layout>{};
 
-    static convertGeoToCanvas(location: LocMeta): MetaUi {
-        const calcX = (LONGITUDE_EXTENT + location.lng) / ( LONGITUDE_EXTENT * 2 ) * 2000 - 500;
-        const calcY = (LATITUDE_EXTENT - location.lat) / ( LATITUDE_EXTENT * 2 ) * 1000;
-        return <MetaUi>{
-            x: calcX,
-            y: calcY,
-            equivLoc: {
-                lat: location.lat,
-                lng: location.lng
-            }
-        };
+    constructor(
+        private log: LogService
+    ) {
+        this.log.debug('BackgroundSvg constructed');
     }
 
-    static convertXYtoGeo(x: number, y: number): MetaUi {
-        const calcLong: number = (x + 500) * 2 * LONGITUDE_EXTENT / 2000 - LONGITUDE_EXTENT;
-        const calcLat: number = -(y * 2 * LATITUDE_EXTENT / 1000 - LATITUDE_EXTENT);
-        return <MetaUi>{
-            x: x,
-            y: y,
-            equivLoc: <LocMeta>{
-                lat: calcLat,
-                lng: calcLong
-            }
-        };
+    /**
+     * Called when ever the mapBounds event is raised by the MapSvgComponent
+     *
+     * @param bounds - the bounds of the newly loaded map in terms of Lat and Long
+     */
+    updatedBounds(bounds: MapBounds): void {
+        const zoomPrefs: TopoZoomPrefs =
+            ZoomUtils.convertBoundsToZoomLevel(bounds, this.log);
+
+        this.zoomlevel.emit(zoomPrefs);
     }
 
-    constructor() { }
-
-    ngOnInit() {
-    }
-
-
-
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts
index ef99728..474d7a8 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts
@@ -20,9 +20,9 @@
     Input,
     OnChanges, Output
 } from '@angular/core';
-import {ForceDirectedGraph, LocMeta, MetaUi, Node} from '../models';
+import {ForceDirectedGraph, Node} from '../models';
 import * as d3 from 'd3';
-import {LogService} from 'gui2-fw-lib';
+import {LogService, MetaUi, ZoomUtils} from 'gui2-fw-lib';
 import {BackgroundSvgComponent} from '../../backgroundsvg/backgroundsvg.component';
 
 @Directive({
@@ -73,7 +73,7 @@
                 if (!d3.event.active) {
                     graph.simulation.alphaTarget(0);
                 }
-                newLocation.emit(BackgroundSvgComponent.convertXYtoGeo(node.fx, node.fy));
+                newLocation.emit(ZoomUtils.convertXYtoGeo(node.fx, node.fy));
 
                 // node.fx = null;
                 // node.fy = null;
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
index 6bbf85b..b673b24 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
@@ -23,11 +23,18 @@
          Input parameter to the link item from the next line
         line 2) Use the built in NgFor directive to iterate through the
          set of links filtered by the filteredLinks() function.
+        line 3) feed the highlightPorts of this (forcesvg) component in to
+         the highlightsEnabled of the link component
+        line 5) when the onos-linksvg component emits the selectedEvent,
+         call the updateSelected() method of this (forcesvg) component
+        line 6) feed the scale of this (forcesvg) component in to the scale
+         of the link
     -->
     <svg:g onos-linksvg [link]="link"
            *ngFor="let link of filteredLinks()"
            [highlightsEnabled]="highlightPorts"
-           (selectedEvent)="updateSelected($event)">
+           (selectedEvent)="updateSelected($event)"
+           [scale]="scale">
     </svg:g>
 </svg:g>
 <svg:g xmlns:svg="http://www.w3.org/2000/svg" class="topo2-nodes">
@@ -43,15 +50,20 @@
          Input parameter to 'graph'
         line 4) event handler of the draggable directive - causes the new location
          to be written back to the server
-        line 5) when the onos-devicenodesvg component emits the selectedEvent
+        line 5) when the onos-devicenodesvg component emits the selectedEvent,
          call the updateSelected() method of this (forcesvg) component
+        line 6) feed the devicelabeltoggle of this (forcesvg) component in to
+         the labelToggle of the device
+        line 7) feed the scale of this (forcesvg) component in to the scale
+         of the device
     -->
     <svg:g onos-devicenodesvg [device]="device"
            *ngFor="let device of regionData.devices[visibleLayerIdx()]"
            onosDraggableNode [draggableNode]="device" [draggableInGraph]="graph"
                (newLocation)="nodeMoved('device', device.id, $event)"
            (selectedEvent)="updateSelected($event)"
-            [labelToggle]="deviceLabelToggle">
+            [labelToggle]="deviceLabelToggle"
+            [scale]="scale">
         <svg:desc>Device nodes</svg:desc>
     </svg:g>
     <!-- Template explanation - only display the hosts if 'showHosts' is set true -->
@@ -69,13 +81,18 @@
              to be written back to the server
             line 5) when the onos-hostnodesvg component emits the selectedEvent
              call the updateSelected() method of this (forcesvg) component
+            line 6) feed the hostLabelToggle of this (forcesvg) component in to
+             the labelToggle of the host
+            line 7) feed the scale of this (forcesvg) component in to the scale
+             of the host
         -->
         <svg:g onos-hostnodesvg [host]="host"
                *ngFor="let host of regionData.hosts[visibleLayerIdx()]"
                onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph"
                    (newLocation)="nodeMoved('host', device.id, $event)"
                (selectedEvent)="updateSelected($event)"
-               [labelToggle]="hostLabelToggle">
+               [labelToggle]="hostLabelToggle"
+               [scale]="scale">
             <svg:desc>Host nodes</svg:desc>
         </svg:g>
     </svg:g>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
index 7b66f36..ebc70aa 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
@@ -28,7 +28,13 @@
     SimpleChanges,
     ViewChildren
 } from '@angular/core';
-import {LogService, WebSocketService} from 'gui2-fw-lib';
+import {
+    LocMeta,
+    LogService,
+    MetaUi,
+    WebSocketService,
+    ZoomUtils
+} from 'gui2-fw-lib';
 import {
     Device,
     ForceDirectedGraph,
@@ -38,8 +44,7 @@
     LayerType,
     Link,
     LinkHighlight,
-    Location, LocMeta,
-    MetaUi,
+    Location,
     ModelEventMemo,
     ModelEventType,
     Region,
@@ -83,6 +88,7 @@
     @Input() onosInstMastership: string = '';
     @Input() visibleLayer: LayerType = LayerType.LAYER_DEFAULT;
     @Input() selectedLink: RegionLink = null;
+    @Input() scale: number = 1;
     @Input() regionData: Region = <Region>{devices: [ [], [], [] ], hosts: [ [], [], [] ], links: []};
     @Output() linkSelected = new EventEmitter<RegionLink>();
     @Output() selectedNodeEvent = new EventEmitter<UiElement>();
@@ -192,7 +198,7 @@
                 const loc: Location = <Location>n['location'];
                 if (loc && loc.locType === LocationType.GEO) {
                     const position: MetaUi =
-                        BackgroundSvgComponent.convertGeoToCanvas(
+                        ZoomUtils.convertGeoToCanvas(
                             <LocMeta>{lng: loc.longOrX, lat: loc.latOrY});
                     n.fx = position.x;
                     n.fy = position.y;
@@ -438,5 +444,11 @@
         });
         this.log.debug(klass, id, 'has been moved to', newLocation);
     }
+
+    resetNodeLocations() {
+        this.devices.forEach((d) => {
+            d.resetNodeLocation();
+        });
+    }
 }
 
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
index 5f8d8c6..ce5441f 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
@@ -15,12 +15,9 @@
  */
 import * as d3 from 'd3';
 import {LocationType} from '../../backgroundsvg/backgroundsvg.component';
-import {
-    LayerType,
-    Location,
-    NodeType,
-    RegionProps
-} from './regions';
+import {LayerType, Location, NodeType, RegionProps} from './regions';
+import {LocMeta, LogService, MetaUi} from 'gui2-fw-lib';
+import {ZoomUtils} from 'gui2-fw-lib';
 
 export interface UiElement {
     index?: number;
@@ -116,23 +113,6 @@
     uiType: string;
 }
 
-/**
- * model of the topo2CurrentRegion Loc part of the MetaUi below
- */
-export interface LocMeta {
-    lng: number;
-    lat: number;
-}
-
-/**
- * model of the topo2CurrentRegion MetaUi from Device below
- */
-export interface MetaUi {
-    equivLoc: LocMeta;
-    x: number;
-    y: number;
-}
-
 export interface HostProps {
     gridX: number;
     gridY: number;
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
index 57a2bd3..204b85c 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
@@ -37,7 +37,7 @@
 }
 g.node.device.online use {
     /* NOTE: this gets overridden programatically */
-    fill: #454545;
+    fill: #ffffff;
 }
 
 g.node.selected .node-container {
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
index 747b1bb..399955c 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
@@ -15,6 +15,7 @@
 -->
 <svg:defs xmlns:svg="http://www.w3.org/2000/svg">
     <!-- Template explanation: Define an SVG Filter that in
+        line 0) creates a box big enough to accommodate the drop shadow
         line 1) render the target object in to a bit map and apply a blur to it
             based on its alpha channel
         line 2) take that blurred layer and shift it down and to the right by 4
@@ -29,6 +30,7 @@
             <svg:feMergeNode in="SourceGraphic" />
         </svg:feMerge>
     </svg:filter>
+    <!-- Template explanation: Define a colour gradient that can be used in icons -->
     <svg:linearGradient id="diagonal_blue" x1="0%" y1="0%" x2="100%" y2="100%">
         <svg:stop offset= "0%" style="stop-color: #7fabdb;" />
         <svg:stop offset= "100%" style="stop-color: #5b99d2;" />
@@ -36,6 +38,7 @@
 </svg:defs>
 <!-- Template explanation: Creates an SVG Group and in
     line 1) transform it to the position calculated by the d3 force graph engine
+            and scale it inversely to the zoom level
     line 2) Give it various CSS styles depending on attributes
     line 3) When it is clicked, call the method that toggles the selection and
         emits an event.
@@ -80,7 +83,6 @@
             text-anchor="start" y="0.3em" x="22"
             [attr.textLength]= "labelTextLen()"
             lengthAdjust= "spacing"
-            [ngStyle]="{'transform': 'scale(' + scale + ')'}"
             [@deviceLabelToggleTxt]="labelToggle">
         {{ labelToggle == 0 ? '': labelToggle == 1 ? device.id:device.props.name }}
     </svg:text>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
index 1cfe7cf..87b7e0a 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
@@ -23,9 +23,10 @@
     SimpleChanges,
 } from '@angular/core';
 import {Device, LabelToggle, UiElement} from '../../models';
-import {IconService, LogService} from 'gui2-fw-lib';
+import {IconService, LocMeta, LogService, MetaUi, ZoomUtils} from 'gui2-fw-lib';
 import {NodeVisual} from '../nodevisual';
 import {animate, state, style, transition, trigger} from '@angular/animations';
+import {LocationType} from '../../../backgroundsvg/backgroundsvg.component';
 
 /**
  * The Device node in the force graph
@@ -113,9 +114,10 @@
      */
     labelTextLen() {
         if (this.labelToggle === 1) {
-            return this.device.id.length * 8 * this.scale;
-        } else if (this.labelToggle === 2 && this.device && this.device.props.name && this.device.props.name.trim().length > 0) {
-            return this.device.props.name.length * 8 * this.scale;
+            return this.device.id.length * 8;
+        } else if (this.labelToggle === 2 && this.device &&
+            this.device.props.name && this.device.props.name.trim().length > 0) {
+            return this.device.props.name.length * 8;
         } else {
             return 0;
         }
@@ -129,4 +131,25 @@
             return 'm_' + this.device.type;
         }
     }
+
+    resetNodeLocation(): void {
+        this.log.debug('Resetting device', this.device.id, this.device.type);
+        let origLoc: MetaUi;
+
+        if (!this.device.location || this.device.location.locType === LocationType.NONE) {
+            // No location - nothing to do
+            return;
+        } else if (this.device.location.locType === LocationType.GEO) {
+            origLoc = ZoomUtils.convertGeoToCanvas(<LocMeta>{
+                lng: this.device.location.longOrX,
+                lat: this.device.location.latOrY
+            });
+        } else if (this.device.location.locType === LocationType.GRID) {
+            origLoc = ZoomUtils.convertXYtoGeo(
+                this.device.location.longOrX, this.device.location.latOrY);
+        }
+        this.device.metaUi = origLoc;
+        this.device['fx'] = origLoc.x;
+        this.device['fy'] = origLoc.y;
+    }
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
index 5bab75d..ccce65a 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
@@ -55,7 +55,8 @@
         filter="url(#drop-shadow-host)"
         style="fill: url(#three_stops_radial)">
     </svg:circle>
-    <svg:use xlink:href="#m_endstation" width="22.5" height="22.5" x="-11.25" y="-11.25" style="transform: scale(1);"></svg:use>
+    <svg:use xlink:href="#m_endstation" width="22.5" height="22.5" x="-11.25" y="-11.25">
+    </svg:use>
     <!-- Template explanation: Creates an SVG Text
         line 1) if the labelToggle is not 0
         line 2) shift it below the circle, and have it centred with the circle
@@ -65,5 +66,5 @@
     <svg:text
         *ngIf="labelToggle != 0"
         dy="30" text-anchor="middle"
-        style="transform: scale(1);">{{hostName()}}</svg:text>
+        >{{hostName()}}</svg:text>
 </svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.html
index b3a5557..c856763 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.html
@@ -27,6 +27,7 @@
     line 1) transform end A to the position calculated by the d3 force graph engine
     line 2) transform end B to the position calculated by the d3 force graph engine
     line 3) Give it various CSS styles depending on attributes
+    ling 4) Change the line width depending on the scale
     line 4) When it is clicked, call the method that toggles the selection and
         emits an event.
     line 5) When the mouse is moved over call on enhance() function. This will
@@ -37,12 +38,16 @@
         [attr.x1]="link.source?.x" [attr.y1]="link.source?.y"
         [attr.x2]="link.target?.x" [attr.y2]="link.target?.y"
         [ngClass]="['link', selected?'selected':'', enhanced?'enhanced':'', highlighted]"
+        [ngStyle]="{'stroke-width': (enhanced ? 4 : 2) * scale + 'px'}"
         (click)="toggleSelected(link)"
         (mouseover)="enhance()"
         [attr.filter]="highlighted?'url(#glow)':'none'">
 </svg:line>
-<svg:g xmlns:svg="http://www.w3.org/2000/svg" [ngClass]="['linkLabel']">
-    <!-- Template explanation: Creates SVG Text and in
+<svg:g xmlns:svg="http://www.w3.org/2000/svg"
+       [ngClass]="['linkLabel']"
+       [attr.transform]="'scale(' + scale + ')'">
+    <!-- Template explanation: Creates SVG Text in the middle of the link to
+          show traffic and in:
         line 1) Performs the animation 'linkLabelVisible' whenever the isHighlighted
             boolean value changes
         line 2 & 3) Sets the text at half way between the 2 end points of the line
@@ -61,13 +66,14 @@
 -->
 <svg:g xmlns:svg="http://www.w3.org/2000/svg"
        *ngIf="enhanced && link.portA"
-       class="portLabel">
+       class="portLabel"
+       [attr.transform]="'translate(' + labelPosSrc.x + ',' + labelPosSrc.y + '),scale(' + scale + ')'">
     <!-- Template explanation: Creates an SVG Rectangle and in
         line 1) transform end A to the position calculated by the d3 force graph engine
         line 2) assigns classes to it
     -->
     <svg:rect
-            [attr.x]="labelPosSrc.x - 2 - textLength(link.portA)/2" [attr.y]="labelPosSrc.y - 8"
+            [attr.x]="2 - textLength(link.portA)/2" y="-8"
             [attr.width]="4 + textLength(link.portA)" height="16" >
     </svg:rect>
     <!-- Template explanation: Creates SVG Text and in
@@ -75,23 +81,20 @@
         line 2) centre aligns it
         line 3) ensures that the text fills the rectangle by adjusting spacing
     -->
-    <svg:text
-            [attr.x]="labelPosSrc.x" [attr.y]="labelPosSrc.y + 6"
-            text-anchor="middle"
+    <svg:text y="2" text-anchor="middle"
             [attr.textLength]= "textLength(link.portA)" lengthAdjust="spacing"
     >{{ link.portA }}</svg:text>
 </svg:g>
 <!-- A repeat of the above, but for the other end of the line -->
 <svg:g xmlns:svg="http://www.w3.org/2000/svg"
        *ngIf="enhanced && link.portB"
-       class="portLabel">
+       class="portLabel"
+       [attr.transform]="'translate(' + labelPosTgt.x + ',' + labelPosTgt.y + '),scale(' + scale + ')'">
     <svg:rect
-            [attr.x]="labelPosTgt.x - 2 - textLength(link.portB)/2" [attr.y]="labelPosTgt.y - 8"
+            [attr.x]="2 - textLength(link.portB)/2" y="-8"
             [attr.width]="4 + textLength(link.portB)" height="16">
     </svg:rect>
-    <svg:text
-            [attr.x]="labelPosTgt.x" [attr.y]="labelPosTgt.y + 6"
-            text-anchor="middle"
+    <svg:text x="2" y="2" text-anchor="middle"
             [attr.textLength]= "textLength(link.portB)" lengthAdjust="spacing"
     >{{ link.portB }}</svg:text>
 </svg:g>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.ts
index af4d0a9..eab533f 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.ts
@@ -28,11 +28,6 @@
     y: number;
 }
 
-enum LinkEnd {
-    A,
-    B
-}
-
 @Component({
     selector: '[onos-linksvg]',
     templateUrl: './linksvg.component.html',
@@ -55,6 +50,7 @@
     @Input() highlighted: string = '';
     @Input() highlightsEnabled: boolean = true;
     @Input() label: string;
+    @Input() scale = 1.0;
     isHighlighted: boolean = false;
     @Output() selectedEvent = new EventEmitter<UiElement>();
     @Output() enhancedEvent = new EventEmitter<Link>();
@@ -100,7 +96,7 @@
     }
 
     /**
-     * We want to place the label for the port about 40 px from the node
+     * We want to place the label for the port about 40 px from the node.
      * If the distance between the nodes is less than 100, then just place the
      * label 1/3 of the way from the node
      */
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
index 23dbeb1..679bfd4 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 import {
-    Component,
+    Component, EventEmitter,
     Input,
-    OnChanges,
+    OnChanges, Output,
     SimpleChanges
 } from '@angular/core';
 import { MapObject } from '../maputils';
-import {LogService} from 'gui2-fw-lib';
+import {LogService, MapBounds} from 'gui2-fw-lib';
 import {HttpClient} from '@angular/common/http';
 import * as d3 from 'd3';
 import * as topojson from 'topojson-client';
@@ -36,16 +36,6 @@
 }
 
 /**
- * Model of the Generator setting for D3 GEO
- */
-interface GeneratorSettings {
-    objectTag: string;
-    projection: Object;
-    logicalSize: number;
-    mapFillScale: number;
-}
-
-/**
  * Model of the Feature returned prom topojson library
  */
 interface Feature {
@@ -81,9 +71,10 @@
 })
 export class MapSvgComponent implements  OnChanges {
     @Input() map: MapObject = <MapObject>{id: 'none'};
+    @Output() mapBounds = new EventEmitter<MapBounds>();
 
     geodata: FeatureCollection;
-    pathgen: (Feature) => string;
+    pathgen: any; // (Feature) => string; have to leave it general, as there is the bounds method used below
     // testPath: string;
     // testFeature = <Feature>{
     //     id: 'test',
@@ -102,13 +93,14 @@
         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(
-            MapSvgComponent.scale(1, 360, 150));
+            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');
     }
 
@@ -119,11 +111,10 @@
         return id + '.topojson';
     }
 
-    static scale (scaleFactor: number, width: number, height: number) {
+    static scale () {
         return d3.geoTransform({
             point: function(x, y) {
-                this.stream.point( (x - width / 2) * scaleFactor + width / 2,
-                    (-y - height / 2) * scaleFactor + height / 2);
+                this.stream.point(x, -y); // 1-1 mapping but invert y-axis
             }
         });
     }
@@ -170,6 +161,13 @@
         }
         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);
 
     }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
index 0484384..2020a42 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
@@ -21,19 +21,9 @@
     OnInit,
     SimpleChanges
 } from '@angular/core';
-import {LogService, PrefsService} from 'gui2-fw-lib';
+import {LogService, PrefsService, TopoZoomPrefs} from 'gui2-fw-lib';
 import * as d3 from 'd3';
 
-
-/**
- * Model of the Zoom preferences
- */
-export interface TopoZoomPrefs {
-    tx: number;
-    ty: number;
-    sc: number;
-}
-
 const TOPO_ZOOM_PREFS = 'topo_zoom';
 
 const ZOOM_PREFS_DEFAULT: TopoZoomPrefs = <TopoZoomPrefs>{
@@ -53,6 +43,7 @@
     @Input() zoomableOf: ElementRef;
 
     zoom: any; // The d3 zoom behaviour
+    zoomCached: TopoZoomPrefs = <TopoZoomPrefs>{tx: 0, ty: 0, sc: 1.0};
 
     constructor(
         private _element: ElementRef,
@@ -64,34 +55,31 @@
         const zoomed = () => {
             const transform = d3.event.transform;
             container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
-            this.updateZoomState(transform.x, transform.y, transform.k);
+            this.updateZoomState(<TopoZoomPrefs>{tx: transform.x, ty: transform.y, sc: transform.k});
         };
 
         this.zoom = d3.zoom().on('zoom', zoomed);
     }
 
     ngOnInit() {
-        const zoomState: TopoZoomPrefs = this.ps.getPrefs(TOPO_ZOOM_PREFS, ZOOM_PREFS_DEFAULT);
+        this.zoomCached = this.ps.getPrefs(TOPO_ZOOM_PREFS, ZOOM_PREFS_DEFAULT);
         const svg = d3.select(this.zoomableOf);
 
         svg.call(this.zoom);
 
         svg.transition().call(this.zoom.transform,
-            d3.zoomIdentity.translate(zoomState.tx, zoomState.ty).scale(zoomState.sc));
+            d3.zoomIdentity.translate(this.zoomCached.tx, this.zoomCached.ty).scale(this.zoomCached.sc));
         this.log.debug('Loaded topo_zoom_prefs',
-            zoomState.tx, zoomState.ty, zoomState.sc);
+            this.zoomCached.tx, this.zoomCached.ty, this.zoomCached.sc);
 
     }
 
     /**
      * Updates the cache of zoom preferences locally and onwards to the PrefsService
      */
-    updateZoomState(x: number, y: number, scale: number): void {
-        this.ps.setPrefs(TOPO_ZOOM_PREFS, <TopoZoomPrefs>{
-            tx: x,
-            ty: y,
-            sc: scale
-        });
+    updateZoomState(zoomPrefs: TopoZoomPrefs): void {
+        this.zoomCached = zoomPrefs;
+        this.ps.setPrefs(TOPO_ZOOM_PREFS, zoomPrefs);
     }
 
     /**
@@ -113,8 +101,21 @@
     resetZoom(): void {
         const svg = d3.select(this.zoomableOf);
         svg.transition().duration(750).call(this.zoom.transform, d3.zoomIdentity);
-        this.updateZoomState(0, 0, 1.0);
+        this.updateZoomState(ZOOM_PREFS_DEFAULT);
         this.log.debug('Pan to 0,0 and zoom to 1.0');
     }
 
+    /**
+     * Change the zoom level when a map is chosen in Topology view
+     *
+     * Animated to run over 750ms
+     */
+    changeZoomLevel(zoomState: TopoZoomPrefs): void {
+        const svg = d3.select(this.zoomableOf);
+        svg.transition().duration(750).call(this.zoom.transform,
+            d3.zoomIdentity.translate(zoomState.tx, zoomState.ty).scale(zoomState.sc));
+        this.updateZoomState(zoomState);
+        this.log.debug('Pan to', zoomState.tx, zoomState.ty, 'and zoom to', zoomState.sc);
+    }
+
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
index b6f511a..8644ac1 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
@@ -72,7 +72,8 @@
                    [invertVertical]="true" [fit]="'fit1000high'" [aspectRatio]="0.83333"
                    [gridcolor]="'#bfe7fb'">
             </svg:g>
-            <svg:g *ngIf="prefsState.bg" onos-backgroundsvg [map]="mapIdState">
+            <svg:g *ngIf="prefsState.bg"
+                   onos-backgroundsvg [map]="mapIdState" (zoomlevel)="mapExtentsZoom($event)">
                 <svg:desc>The Background SVG component - contains maps</svg:desc>
             </svg:g>
             <svg:g #force onos-forcesvg
@@ -80,6 +81,7 @@
                    [hostLabelToggle]="prefsState.hlbls"
                    [showHosts]="prefsState.hosts"
                    [highlightPorts]="prefsState.porthl"
+                   [scale]="1 / (2 * zoomDirective.zoomCached.sc)"
                    (selectedNodeEvent)="nodeSelected($event)">
                 <svg:desc>The Force SVG component - contains all the devices, hosts and links</svg:desc>
             </svg:g>
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
index 2de3c8b..441bbc6 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
@@ -28,6 +28,7 @@
     PrefsService,
     SvgUtilService,
     WebSocketService,
+    TopoZoomPrefs
 } from 'gui2-fw-lib';
 import {InstanceComponent} from '../panel/instance/instance.component';
 import {DetailsComponent} from '../panel/details/details.component';
@@ -552,6 +553,7 @@
 
     protected resetNodeLocation() {
         // TODO: Implement reset locations
+        this.force.resetNodeLocations();
         this.flashMsg = this.lionFn('fl_reset_node_locations');
         this.log.debug('resetting node location');
     }
@@ -624,6 +626,12 @@
         this.log.debug('Map has been changed to ', map);
     }
 
+    mapExtentsZoom(zoomMapExtents: TopoZoomPrefs) {
+        // this.zoomDirective.updateZoomState(zoomPrefs.tx, zoomPrefs.ty, zoomPrefs.sc);
+        this.zoomDirective.changeZoomLevel(zoomMapExtents);
+        this.log.debug('Map zoom prefs updated', zoomMapExtents);
+    }
+
     /**
      * Read the LION bundle for Toolbar and set up the lionFn
      */