GUI2 Added selection of background maps

Change-Id: I88ee69fe2ff24bb1b4b3fe633b04f2f1778f3a82
diff --git a/web/gui/BUILD b/web/gui/BUILD
index f8b70c8..b142c57 100644
--- a/web/gui/BUILD
+++ b/web/gui/BUILD
@@ -228,6 +228,16 @@
     visibility = ["//visibility:public"],
 )
 
+genrule(
+    name = "onos-gui-data-for-gui2",
+    srcs = glob([
+        "src/main/webapp/data/**/*",
+    ]),
+    outs = ["onos-gui-data-for-gui2.srcjar"],
+    cmd = "jar cf $@ $(SRCS)",
+    visibility = ["//visibility:public"],
+)
+
 """
     Builds the tar ball for the ONOS GUI
 """
diff --git a/web/gui2-fw-lib/package-lock.json b/web/gui2-fw-lib/package-lock.json
index ead67de..0988ae4 100644
--- a/web/gui2-fw-lib/package-lock.json
+++ b/web/gui2-fw-lib/package-lock.json
@@ -3376,7 +3376,7 @@
         "d3-fetch": "1.1.2",
         "d3-force": "1.1.2",
         "d3-format": "1.3.2",
-        "d3-geo": "1.11.1",
+        "d3-geo": "1.11.3",
         "d3-hierarchy": "1.1.8",
         "d3-interpolate": "1.3.2",
         "d3-path": "1.0.7",
@@ -3498,9 +3498,9 @@
       "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
     },
     "d3-geo": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.1.tgz",
-      "integrity": "sha512-GsG7x9G9sykseLviOVSJ3h5yjw0ItLopOtuDQKUt1TRklEegCw5WAmnIpYYiCkSH/QgUMleAeE2xZK38Qb+1+Q==",
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.3.tgz",
+      "integrity": "sha512-n30yN9qSKREvV2fxcrhmHUdXP9TNH7ZZj3C/qnaoU0cVf/Ea85+yT7HY7i8ySPwkwjCNYtmKqQFTvLFngfkItQ==",
       "requires": {
         "d3-array": "1.2.4"
       }
@@ -11855,6 +11855,14 @@
         "repeat-string": "1.6.1"
       }
     },
+    "topojson-client": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.0.0.tgz",
+      "integrity": "sha1-H5kpOnfvQqRI0DKoGqmCtz82DS8=",
+      "requires": {
+        "commander": "2.19.0"
+      }
+    },
     "toposort": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
diff --git a/web/gui2-fw-lib/package.json b/web/gui2-fw-lib/package.json
index d12a596..47fc254 100644
--- a/web/gui2-fw-lib/package.json
+++ b/web/gui2-fw-lib/package.json
@@ -24,6 +24,7 @@
     "core-js": "^2.5.4",
     "d3": "^5.2.0",
     "rxjs": "^6.3.3",
+    "topojson-client": "^3.0.0",
     "zone.js": "^0.8.26"
   },
   "devDependencies": {
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/prefs.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/prefs.service.ts
index b68b4dd..d535441 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/prefs.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/util/prefs.service.ts
@@ -20,6 +20,8 @@
 
 const UPDATE_PREFS: string = 'updatePrefs';
 const UPDATE_PREFS_REQ: string = 'updatePrefReq';
+
+
 /**
  * ONOS GUI -- Util -- User Preference Service
  */
@@ -88,7 +90,7 @@
         return obj;
     }
 
-    getPrefs(name: string, defaults: any, qparams?: string) {
+    getPrefs(name: string, defaults: Object, qparams?: string) {
         const obj = Object.assign({}, defaults || {}, this.cache[name] || {});
 
         // if query params are specified, they override...
diff --git a/web/gui2/BUILD b/web/gui2/BUILD
index 607f955..bd0b330 100644
--- a/web/gui2/BUILD
+++ b/web/gui2/BUILD
@@ -344,6 +344,7 @@
         ":_onos-gui2-base-jar",
         ":_web_inf_classes_files",
         "//web/gui:onos-gui-lion-for-gui2",
+        "//web/gui:onos-gui-data-for-gui2",
         "src/main/webapp/WEB-INF/web.xml",
     ],
     outs = ["onos-gui2.jar"],
@@ -357,6 +358,8 @@
           " unzip -q $$ROOT/$(location //web/gui:onos-gui-lion-for-gui2) web/gui/src/main/resources/**/* &&" +
           " mv web/gui/src/main/resources/org/onosproject/ui/lion* WEB-INF/classes/org/onosproject/ui/ &&" +
           " mv web/gui/src/main/resources/core WEB-INF/classes/ &&" +
+          " unzip -q $$ROOT/$(location //web/gui:onos-gui-data-for-gui2) web/gui/src/main/webapp/data/**/* &&" +
+          " mv web/gui/src/main/webapp/data WEB-INF/classes/ &&" +
           " find . -type f -exec touch -t 201901200000 {} \; &&" +
           " jar cmf META-INF/MANIFEST.MF $$ROOT/$@ WEB-INF/web.xml WEB-INF/classes OSGI-INF/*.xml",
     output_to_bindir = 1,
diff --git a/web/gui2/package-lock.json b/web/gui2/package-lock.json
index c7f2b1c..763b0c4 100644
--- a/web/gui2/package-lock.json
+++ b/web/gui2/package-lock.json
@@ -3216,7 +3216,7 @@
         "d3-fetch": "1.1.2",
         "d3-force": "1.1.2",
         "d3-format": "1.3.2",
-        "d3-geo": "1.11.1",
+        "d3-geo": "1.11.3",
         "d3-hierarchy": "1.1.8",
         "d3-interpolate": "1.3.2",
         "d3-path": "1.0.7",
@@ -3338,9 +3338,9 @@
       "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ=="
     },
     "d3-geo": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.1.tgz",
-      "integrity": "sha512-GsG7x9G9sykseLviOVSJ3h5yjw0ItLopOtuDQKUt1TRklEegCw5WAmnIpYYiCkSH/QgUMleAeE2xZK38Qb+1+Q==",
+      "version": "1.11.3",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.3.tgz",
+      "integrity": "sha512-n30yN9qSKREvV2fxcrhmHUdXP9TNH7ZZj3C/qnaoU0cVf/Ea85+yT7HY7i8ySPwkwjCNYtmKqQFTvLFngfkItQ==",
       "requires": {
         "d3-array": "1.2.4"
       }
@@ -11205,6 +11205,14 @@
         "repeat-string": "1.6.1"
       }
     },
+    "topojson-client": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.0.0.tgz",
+      "integrity": "sha1-H5kpOnfvQqRI0DKoGqmCtz82DS8=",
+      "requires": {
+        "commander": "2.19.0"
+      }
+    },
     "toposort": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
diff --git a/web/gui2/package.json b/web/gui2/package.json
index 1ce2565..3a7f747 100644
--- a/web/gui2/package.json
+++ b/web/gui2/package.json
@@ -27,6 +27,7 @@
     "fm-gui2-lib": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-2.0.0.tgz",
     "gui2-fw-lib": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.0.0.tgz",
     "rxjs": "^6.0.0",
+    "topojson-client": "^3.0.0",
     "zone.js": "^0.8.26"
   },
   "devDependencies": {
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 d745ce8..bb9e638 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
@@ -13,7 +13,7 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg />
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg [map]="map"/>
 <svg:g  xmlns:svg="http://www.w3.org/2000/svg">
     <svg:text>Layout: {{ layoutData.id }} {{ layoutData.bgDesc }}</svg:text>
     <svg:text>Region: {{ layoutData.region }} {{ layoutData.regionName }}</svg:text>
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 f1e1e69..a3eed06 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
@@ -16,21 +16,52 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { BackgroundSvgComponent } from './backgroundsvg.component';
+import {MapSvgComponent} from '../mapsvg/mapsvg.component';
+import {from} from 'rxjs';
+import {HttpClient} from '@angular/common/http';
+import {LogService} from 'gui2-fw-lib';
+import {MapObject} from '../maputils';
+
+class MockHttpClient {
+    get() {
+        return from(['{"id":"app","icon":"nav_apps","cat":"PLATFORM","label":"Applications"}']);
+    }
+
+    subscribe() {}
+}
 
 describe('BackgroundSvgComponent', () => {
+    let logServiceSpy: jasmine.SpyObj<LogService>;
     let component: BackgroundSvgComponent;
     let fixture: ComponentFixture<BackgroundSvgComponent>;
+    const testmap: MapObject = <MapObject>{
+        scale: 1.0,
+        id: 'test',
+        description: 'test map'
+    };
 
     beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+
         TestBed.configureTestingModule({
-            declarations: [ BackgroundSvgComponent ]
+            declarations: [
+                BackgroundSvgComponent,
+                MapSvgComponent
+            ],
+            providers: [
+                { provide: LogService, useValue: logSpy },
+                { provide: HttpClient, useClass: MockHttpClient },
+            ]
         })
         .compileComponents();
+
+        logServiceSpy = TestBed.get(LogService);
     }));
 
     beforeEach(() => {
         fixture = TestBed.createComponent(BackgroundSvgComponent);
         component = fixture.componentInstance;
+        component.map = testmap;
         fixture.detectChanges();
     });
 
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 e07f304..5536784 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,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
+import {MapObject} from '../maputils';
 
 /**
  * model of the topo2CurrentLayout attrs from BgZoom below
@@ -76,6 +77,7 @@
     styleUrls: ['./backgroundsvg.component.css']
 })
 export class BackgroundSvgComponent implements OnInit {
+    @Input() map: MapObject;
 
     layoutData: Layout = <Layout>{};
 
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
index b511886..24dd029 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
@@ -52,6 +52,10 @@
     height: number;
 }
 
+/**
+ * The inspiration for this approach comes from
+ * https://medium.com/netscape/visualizing-data-with-angular-and-d3-209dde784aeb
+ */
 export class ForceDirectedGraph {
     public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
     public simulation: d3.Simulation<any, any>;
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
index f206973..0dac42a 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
@@ -16,4 +16,11 @@
 
 /**
  * ONOS GUI -- Topology Map Layer -- CSS file
- */
\ No newline at end of file
+ */
+/* --- Topo Map --- */
+
+path.topo-map {
+    stroke-width: 2px;
+    stroke: #f4f4f4;
+    fill: #e5e5e6;
+}
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
index 157a87b..adc0346 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
@@ -13,5 +13,11 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:text xmlns:svg="http://www.w3.org/2000/svg">Map of Croatia</svg:text>
-<svg:path xmlns:svg="http://www.w3.org/2000/svg" d="m 187.50819,219.30177 -3.14986,-1.57795 z M 45.764161,56.635237 48.914029,55.022914 Z m 62.997349,-32.313055 6.29973,4.85593 6.29974,11.31816 v 11.300949 h 9.4496 l 12.59947,8.061616 6.29973,12.880483 v 8.039036 l 6.29974,14.448536 -3.14987,9.616908 -6.29973,12.80342 v 14.37787 l 3.14986,7.97587 -9.4496,3.18799 3.14987,9.55592 6.29973,4.77344 v 7.94906 l -6.29973,1.58881 3.14987,15.86987 6.29973,6.33869 v 6.33341 l 6.29974,1.58253 v 20.54314 h 6.29973 18.89921 l 3.14986,4.73289 v 1.57699 6.30469 l -3.14986,4.72511 6.29973,6.29564 v 1.57311 l 3.14987,7.8607 -9.4496,17.26536 h -9.44961 l -6.29973,4.70203 v 14.08899 l -6.29974,3.12741 v 15.61816 l -3.14986,6.23849 -22.04907,3.11737 -3.14987,7.78797 h -6.29974 l 9.44961,17.10623 -3.14987,6.21118 -6.29974,-1.55233 v 10.85988 l -6.29973,7.74786 -18.89921,-6.19768 v -20.17635 l -6.299731,-3.10867 v -6.22107 l -6.299735,-10.8988 -9.449602,-3.11675 v -4.67746 l -9.449602,-1.55978 -6.299735,-6.24224 -9.449602,-3.123 -9.449602,-12.5046 -3.149867,-14.09183 12.599469,12.52734 3.149868,-12.52734 -9.449602,-9.40886 6.299734,1.56894 v -7.84788 l -6.299734,1.57021 -6.299735,-9.4261 3.149867,-4.71738 V 228.7627 l 3.149868,-9.46093 6.299734,-3.15624 v -31.63431 l 6.299735,-7.92914 -3.149867,-6.34927 H 52.063896 V 155.9275 l 9.449602,-9.5519 -9.449602,-7.96914 h 12.599469 v -27.15822 l -12.599469,-6.40444 -6.299735,1.60162 V 82.385126 l 3.149868,-3.213886 6.299734,1.607116 3.149868,-8.039036 -12.59947,-9.658277 6.299735,-9.6708 -3.149867,-1.613022 6.299734,-11.300949 6.299735,-4.848528 v -6.469632 l 6.299735,-3.236933 v -8.098523 l 9.449602,-4.863368 3.149867,3.2426 v 14.574161 l 6.299735,3.234816 12.59947,-3.234816 z"></svg:path>
\ No newline at end of file
+<svg:desc xmlns:svg="http://www.w3.org/2000/svg">Map of {{map.id}} in SVG format</svg:desc>
+<svg:path class="topo-map"
+        *ngFor="let f of mapPathGenerator?.geodata.features"
+        xmlns:svg="http://www.w3.org/2000/svg"
+        [attr.d]="pathGenerator(f)">
+    <svg:title>{{ f.id }}</svg:title>
+</svg:path>
+
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts
index e1000c8..ab73820 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.spec.ts
@@ -16,25 +16,45 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { MapSvgComponent } from './mapsvg.component';
+import {HttpClient} from '@angular/common/http';
+import {from} from 'rxjs';
+import {LogService} from 'gui2-fw-lib';
+
+class MockHttpClient {
+    get() {
+        return from(['{"id":"app","icon":"nav_apps","cat":"PLATFORM","label":"Applications"}']);
+    }
+
+    subscribe() {}
+}
 
 describe('MapSvgComponent', () => {
-  let component: MapSvgComponent;
-  let fixture: ComponentFixture<MapSvgComponent>;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: MapSvgComponent;
+    let fixture: ComponentFixture<MapSvgComponent>;
 
-  beforeEach(async(() => {
-    TestBed.configureTestingModule({
-      declarations: [ MapSvgComponent ]
-    })
-    .compileComponents();
-  }));
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
 
-  beforeEach(() => {
-    fixture = TestBed.createComponent(MapSvgComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
+        TestBed.configureTestingModule({
+            declarations: [ MapSvgComponent ],
+            providers: [
+                { provide: LogService, useValue: logSpy },
+                { provide: HttpClient, useClass: MockHttpClient },
+            ]
+        })
+        .compileComponents();
 
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(MapSvgComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
 });
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 2de6c12..a10cf4c 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
@@ -13,18 +13,206 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
+import {
+    Component,
+    Input,
+    OnChanges,
+    SimpleChanges
+} from '@angular/core';
+import { MapObject } from '../maputils';
+import {LogService} 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 Generator setting for D3 GEO
+ */
+interface GeneratorSettings {
+    objectTag: string;
+    projection: Object;
+    logicalSize: number;
+    mapFillScale: number;
+}
+
+/**
+ * Model of the Path Generator
+ */
+interface PathGenerator {
+    geodata: FeatureCollection;
+    pathgen: (Feature) => string;
+    settings: GeneratorSettings;
+}
+
+/**
+ * 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
+}
+
+/**
+ * Default settings for the path generator for TopoJson
+ */
+const DEFAULT_GEN_SETTINGS: GeneratorSettings = <GeneratorSettings>{
+    objectTag: 'states',
+    projection: d3.geoMercator(),
+    logicalSize: 1000,
+    mapFillScale: .95,
+};
 
 @Component({
     selector: '[onos-mapsvg]',
     templateUrl: './mapsvg.component.html',
     styleUrls: ['./mapsvg.component.css']
 })
-export class MapSvgComponent implements OnInit {
+export class MapSvgComponent implements  OnChanges {
+    @Input() map: MapObject = <MapObject>{id: 'none'};
 
-    constructor() { }
+    cache = new Map<string, TopoData>();
+    topodata: TopoData;
+    mapPathGenerator: PathGenerator;
 
-    ngOnInit() {
+    constructor(
+        private log: LogService,
+        private httpClient: HttpClient,
+    ) {
+        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';
+    }
+
+    ngOnChanges(changes: SimpleChanges): void {
+        this.log.debug('Change detected', changes);
+        if (changes['map']) {
+            const map: MapObject = <MapObject>(changes['map'].currentValue);
+            if (map.id) {
+                if (this.cache.get(map.id)) {
+                    this.topodata = this.cache.get(map.id);
+                } else {
+                    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));
+                        });
+                }
+            }
+        }
+    }
+
+    /**
+     * Wrapper for the path generator function
+     * @param feature The county or state within the map
+     */
+    pathGenerator(feature: Feature): string {
+        return this.mapPathGenerator.pathgen(feature);
+    }
+
+    /**
+     * 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): PathGenerator {
+        this.topodata = topoData;
+        this.cache.set(map.id, topoData);
+        this.log.debug('Map retrieved', topoData);
+
+        const topoObject = topoData.objects[map.id];
+        const geoData: FeatureCollection = <FeatureCollection>topojson.feature(topoData, topoObject);
+        this.log.debug('Map retrieved', topoData, geoData);
+
+        const settings: GeneratorSettings = Object.assign({}, DEFAULT_GEN_SETTINGS);
+        const path = d3.geoPath().projection(settings.projection);
+        this.rescaleProjection(
+            settings.projection,
+            settings.mapFillScale,
+            settings.logicalSize,
+            path,
+            geoData);
+        this.log.debug('Scale adjusted');
+
+        return <PathGenerator>{
+            geodata: geoData,
+            pathgen: path,
+            settings: settings
+        };
+    }
+
+    /**
+     * Adjust projection scale and translation to fill the view
+     * with the map
+     * @param proj
+     * @param mfs
+     * @param dim
+     * @param path
+     * @param geoData
+     * @param adjustScale
+     */
+    rescaleProjection(proj: any, mfs: number, dim: number, path: any,
+                      geoData: FeatureCollection, adjustScale: number = 1.0) {
+        // start with unit scale, no translation..
+        proj.scale(1).translate([0, 0]);
+
+        // figure out dimensions of map data..
+        const b = path.bounds(geoData);
+        const x1 = b[0][0];
+        const y1 = b[0][1];
+        const x2 = b[1][0];
+        const y2 = b[1][1];
+        const dx = x2 - x1;
+        const dy = y2 - y1;
+        const x = (x1 + x2) / 2;
+        const y = (y1 + y2) / 2;
+
+        // size map to 95% of minimum dimension to fill space..
+        const s = (mfs / Math.min(dx / dim, dy / dim)) * adjustScale;
+        const t = [dim / 2 - s * x, dim / 2 - s * y];
+
+        // set new scale, translation on the projection..
+        proj.scale(s).translate(t);
+    }
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/maputils.ts b/web/gui2/src/main/webapp/app/view/topology/layer/maputils.ts
new file mode 100644
index 0000000..45c3140
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/maputils.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2019-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.
+ */
+export interface MapObject {
+    id: string;
+    description: string;
+    filePath: string;
+    scale: number;
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
index f298bf5..23d75c5 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
@@ -15,26 +15,51 @@
  */
 import { ZoomableDirective } from './zoomable.directive';
 import {inject, TestBed} from '@angular/core/testing';
-import {LogService, ConsoleLoggerService} from 'gui2-fw-lib';
+import {LogService, ConsoleLoggerService, FnService} from 'gui2-fw-lib';
 import {ElementRef} from '@angular/core';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
 
 describe('ZoomableDirective', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
     let log: LogService;
     let mockWindow: Window;
 
     beforeEach(() => {
         log = new ConsoleLoggerService();
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
 
         mockWindow = <any>{
             navigator: {
                 userAgent: 'HeadlessChrome',
                 vendor: 'Google Inc.'
+            },
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
             }
         };
 
+        fs = new FnService(ar, log, mockWindow);
+
         TestBed.configureTestingModule({
             providers: [ZoomableDirective,
-                {provide: LogService, useValue: log},
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: log },
+                { provide: 'Window', useValue: mockWindow },
                 { provide: ElementRef, useValue: mockWindow }
             ]
         });
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 bd69db1..0484384 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
@@ -13,46 +13,107 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Directive, ElementRef, Input, OnChanges} from '@angular/core';
-import {LogService} from 'gui2-fw-lib';
+import {
+    Directive,
+    ElementRef,
+    Input,
+    OnChanges,
+    OnInit,
+    SimpleChanges
+} from '@angular/core';
+import {LogService, PrefsService} 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>{
+    tx: 0, ty: 0, sc: 1.0
+};
+
+/**
+ * A directive that takes care of Zooming and Panning the Topology view
+ *
+ * It wraps the D3 Pan and Zoom functionality
+ * See https://github.com/d3/d3-zoom/blob/master/README.md
+ */
 @Directive({
   selector: '[onosZoomableOf]'
 })
-export class ZoomableDirective implements OnChanges {
+export class ZoomableDirective implements OnChanges, OnInit {
     @Input() zoomableOf: ElementRef;
 
+    zoom: any; // The d3 zoom behaviour
+
     constructor(
         private _element: ElementRef,
         private log: LogService,
-    ) {}
+        private ps: PrefsService
+    ) {
+        const container = d3.select(this._element.nativeElement);
+
+        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.zoom = d3.zoom().on('zoom', zoomed);
+    }
+
+    ngOnInit() {
+        const zoomState: TopoZoomPrefs = 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));
+        this.log.debug('Loaded topo_zoom_prefs',
+            zoomState.tx, zoomState.ty, zoomState.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
+        });
+    }
 
     /**
      * If the input object is changed then re-establish the zoom
      */
-    ngOnChanges() {
-        let zoomed, zoom;
-
-        const svg = d3.select(this.zoomableOf);
-        const container = d3.select(this._element.nativeElement);
-
-        zoomed = () => {
-            const transform = d3.event.transform;
-            container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
-        };
-
-        zoom = d3.zoom().on('zoom', zoomed);
-        svg.call(zoom);
-        this.log.debug('Applying zoomable behaviour on', this.zoomableOf, this._element.nativeElement);
+    ngOnChanges(changes: SimpleChanges): void {
+        if (changes['zoomableOf']) {
+            const svg = d3.select(changes['zoomableOf'].currentValue);
+            svg.call(this.zoom);
+            this.log.debug('Applying zoomable behaviour on', this.zoomableOf, this._element.nativeElement);
+        }
     }
 
     /**
      * Reset the zoom when the R key is pressed when in Topology view
+     *
+     * Animated to run over 750ms
      */
     resetZoom(): void {
-        const container = d3.select(this._element.nativeElement);
-        container.attr('transform', 'translate(0,0) scale(1.0)');
+        const svg = d3.select(this.zoomableOf);
+        svg.transition().duration(750).call(this.zoom.transform, d3.zoomIdentity);
+        this.updateZoomState(0, 0, 1.0);
         this.log.debug('Pan to 0,0 and zoom to 1.0');
     }
 
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.css b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.css
new file mode 100644
index 0000000..59c0d78
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.css
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019-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.
+ */
+
+/**
+ * ONOS GUI -- Topology Map Selector -- CSS file
+ */
+.dialog h2 {
+    margin: 0;
+    word-wrap: break-word;
+    display: inline-block;
+    width: 210px;
+    vertical-align: middle;
+}
+
+.dialog .dialog-button {
+    display: inline-block;
+    cursor: pointer;
+    height: 20px;
+    padding: 6px 8px 2px 8px;
+    margin: 4px;
+    float: right;
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.html b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.html
new file mode 100644
index 0000000..b3a4224
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.html
@@ -0,0 +1,33 @@
+<!--
+~ Copyright 2019-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.
+-->
+<div id="topo-p-dialog"
+     class="floatpanel dialog topo-p"
+     style="opacity: 1; left: 20px; width: 300px;">
+    <div class="header">
+        <h2>{{ lionFn('title_select_map') }}</h2>
+    </div>
+    <div class="map-list">
+        <form [formGroup]="form">
+            <select formControlName="mapid">
+                <option *ngFor="let o of mapSelectorResponse.order" [ngValue]="o">{{ mapSelectorResponse.maps[o].description }}</option>
+            </select>
+        </form>
+    </div>
+    <div class="footer">
+        <div class="dialog-button" (click)="choice(form.value)">{{ lionFn('ok') }}</div>
+        <div class="dialog-button" (click)="choice(undefined)">{{ lionFn('close') }}</div>
+    </div>
+</div>
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.spec.ts
new file mode 100644
index 0000000..4d95361
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.spec.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019-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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MapSelectorComponent } from './mapselector.component';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+import {FnService, LogService} from 'gui2-fw-lib';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+describe('MapSelectorComponent', () => {
+    let fs: FnService;
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: MapSelectorComponent;
+    let fixture: ComponentFixture<MapSelectorComponent>;
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+        windowMock = <any>{
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
+            }
+        };
+        fs = new FnService(ar, logSpy, windowMock);
+
+        TestBed.configureTestingModule({
+            imports: [
+                FormsModule,
+                ReactiveFormsModule
+            ],
+            declarations: [ MapSelectorComponent ],
+            providers: [
+                { provide: FnService, useValue: fs },
+                { provide: LogService, useValue: logSpy },
+                { provide: 'Window', useValue: windowMock },
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(MapSelectorComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
+
+// Expecting WebSocket request and response similar to:
+//
+// {"event":"mapSelectorRequest","payload":{}}
+//
+// {
+//     "event": "mapSelectorResponse",
+//     "payload": {
+//     "order": ["australia", "americas", "n_america", "s_america", "usa", "bayareaGEO",
+//     "europe", "italy", "uk", "japan", "s_korea", "taiwan", "africa", "oceania", "asia"],
+//         "maps": {
+//         "australia": {
+//             "id": "australia",
+//             "description": "Australia",
+//             "filePath": "*australia",
+//             "scale": 1.0
+//         },
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.ts b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.ts
new file mode 100644
index 0000000..c97e0ca
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.component.ts
@@ -0,0 +1,87 @@
+
+import {
+    Component, EventEmitter, OnChanges,
+    OnDestroy,
+    OnInit, Output, SimpleChanges,
+} from '@angular/core';
+import {
+    DetailsPanelBaseImpl,
+    FnService,
+    LionService, LoadingService,
+    LogService,
+    WebSocketService
+} from 'gui2-fw-lib';
+import {FormControl, FormGroup} from '@angular/forms';
+import { MapObject } from '../../layer/maputils';
+
+interface MapSelection {
+    order: string[];
+    maps: Object[];
+}
+
+@Component({
+    selector: 'onos-mapselector',
+    templateUrl: './mapselector.component.html',
+    styleUrls: ['./mapselector.component.css', './mapselector.theme.css', '../../topology.common.css']
+})
+export class MapSelectorComponent extends DetailsPanelBaseImpl implements OnInit, OnDestroy {
+    @Output() chosenMap = new EventEmitter<MapObject>();
+    lionFn; // Function
+    mapSelectorResponse: MapSelection = <MapSelection>{
+        order: [],
+        maps: []
+    };
+    form = new FormGroup({
+        mapid: new FormControl(this.mapSelectorResponse.order[0]),
+    });
+
+    constructor(
+        protected fs: FnService,
+        protected log: LogService,
+        protected ls: LoadingService,
+        protected wss: WebSocketService,
+        private lion: LionService
+    ) {
+        super(fs, ls, log, wss, 'topo');
+
+        if (this.lion.ubercache.length === 0) {
+            this.lionFn = this.dummyLion;
+            this.lion.loadCbs.set('topoms', () => this.doLion());
+        } else {
+            this.doLion();
+        }
+
+        this.log.debug('Topo MapSelectorComponent constructed');
+    }
+
+    ngOnInit() {
+        this.wss.bindHandlers(new Map<string, (data) => void>([
+            ['mapSelectorResponse', (data) => {
+                this.mapSelectorResponse = data;
+                this.form.setValue({'mapid': this.mapSelectorResponse.order[0]});
+            }
+            ]
+        ]));
+        this.wss.sendEvent('mapSelectorRequest', {});
+        this.log.debug('Topo MapSelectorComponent initialized');
+    }
+
+    /**
+     * When the component is being unloaded then unbind the WSS handler.
+     */
+    ngOnDestroy(): void {
+        this.wss.unbindHandlers(['mapSelectorResponse']);
+        this.log.debug('Topo MapSelectorComponent destroyed');
+    }
+
+    /**
+     * Read the LION bundle for Details panel and set up the lionFn
+     */
+    doLion() {
+        this.lionFn = this.lion.bundle('core.view.Topo');
+    }
+
+    choice(mapid: Object): void {
+        this.chosenMap.emit(<MapObject>this.mapSelectorResponse.maps[mapid['mapid']]);
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.theme.css b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.theme.css
new file mode 100644
index 0000000..0ef1538
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/mapselector/mapselector.theme.css
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2019-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.
+ */
+
+/**
+ * ONOS GUI -- Topology Map Selector theme -- CSS file
+ */
+
+/*.light */
+.dialog .dialog-button {
+    background-color: #518ecc;
+    color: white;
+}
+
+
+/* ========== DARK Theme ========== */
+
+.dark .dialog .dialog-button {
+    background-color: #345e85;
+    color: #cccccd;
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html
index 18f9687..fa7fd01 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html
@@ -14,7 +14,7 @@
 ~ limitations under the License.
 -->
 <div id="toolbar-topo2-toolbar" class="floatpanel toolbar" [@toolbarState]="on"
-     style="opacity: 1; left: 0px; width: 261px; top: auto; bottom: 10px;">
+     style="opacity: 1; left: 0px; width: 286px; top: auto; bottom: 10px;">
     <div class="tbar-arrow" (click)="on =! on">
         <onos-icon [iconSize]="10" [iconId]="on?'triangleLeft':'triangleRight'"></onos-icon>
     </div>
@@ -41,6 +41,9 @@
         <div class="toggleButton" id="toolbar-topo2-toolbar-topo2-bkgrnd-tog" (click)="buttonClicked('bkgrnd-tog')">
             <onos-icon [iconSize]="25" iconId="m_map" [toolTip]="lionFn('tbtt_tog_map')" classes="toggleButton selected" [classes]="['toggleButton', backgroundVisible?'selected':'']"></onos-icon>
         </div>
+        <div class="toggleButton" id="toolbar-topo2-toolbar-topo2-bkgrnd-sel" (click)="buttonClicked('bkgrnd-sel')">
+            <onos-icon [iconSize]="25" iconId="m_selectMap" [toolTip]="lionFn('tbtt_sel_map')" classes="button"></onos-icon>
+        </div>
     </div>
     <br>
     <div class="tbar-row">
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts
index f781364..ed9e2ee 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts
@@ -30,6 +30,7 @@
 export const OFFLINE_TOGGLE = 'offline-tog';
 export const PORTS_TOGGLE = 'ports-tog';
 export const BKGRND_TOGGLE = 'bkgrnd-tog';
+export const BKGRND_SELECT = 'bkgrnd-sel';
 export const CYCLELABELS_BTN = 'cycleLabels-btn';
 export const CYCLEHOSTLABEL_BTN = 'cycleHostLabel-btn';
 export const RESETZOOM_BTN = 'resetZoom-btn';
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology.module.ts b/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
index e7fe739..b6c79a1 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
@@ -34,6 +34,8 @@
     DeviceNodeSvgComponent,
     HostNodeSvgComponent, LinkSvgComponent,
 } from './layer/forcesvg/visuals';
+import { MapSelectorComponent } from './panel/mapselector/mapselector.component';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 
 /**
  * ONOS GUI -- Topology View Module
@@ -46,6 +48,8 @@
 @NgModule({
     imports: [
         CommonModule,
+        FormsModule,
+        ReactiveFormsModule,
         TopologyRoutingModule,
         Gui2FwLibModule
     ],
@@ -64,7 +68,8 @@
         LinkSvgComponent,
         DeviceNodeSvgComponent,
         HostNodeSvgComponent,
-        SubRegionNodeSvgComponent
+        SubRegionNodeSvgComponent,
+        MapSelectorComponent
     ],
     providers: [
         TopologyService
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 4b66bd6..95d5d9e 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
@@ -38,6 +38,7 @@
               [summaryVisible]="prefsState.summary">
 </onos-toolbar>
 <onos-details #details [on]="prefsState.detail"></onos-details>
+<onos-mapselector *ngIf="mapSelShown" (chosenMap)="changeMap($event)"></onos-mapselector>
 
 <div id="ov-topo2">
     <!-- Template explanation -
@@ -62,7 +63,7 @@
                onos-nodeviceconnected />
         <svg:g id="topo-zoomlayer" onosZoomableOf [zoomableOf]="svgZoom">
             <svg:desc>A logical layer that allows the main SVG canvas to be zoomed and panned</svg:desc>
-            <svg:g *ngIf="prefsState.bg" onos-backgroundsvg>
+            <svg:g *ngIf="prefsState.bg" onos-backgroundsvg [map]="mapIdState">
                 <svg:desc>The Background SVG component - contains maps</svg:desc>
             </svg:g>
             <svg:g #force onos-forcesvg
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
index 446d41c..0c0a6dd 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
@@ -18,7 +18,7 @@
 import { of } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-
+import * as d3 from 'd3';
 import { TopologyComponent } from './topology.component';
 import {
     Instance,
@@ -45,6 +45,10 @@
     LinkSvgComponent, SubRegionNodeSvgComponent
 } from '../layer/forcesvg/visuals';
 import {DraggableDirective} from '../layer/forcesvg/draggable/draggable.directive';
+import {MapSelectorComponent} from '../panel/mapselector/mapselector.component';
+import {BackgroundSvgComponent} from '../layer/backgroundsvg/backgroundsvg.component';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
+import {MapSvgComponent} from '../layer/mapsvg/mapsvg.component';
 
 
 class MockActivatedRoute extends ActivatedRoute {
@@ -156,7 +160,12 @@
         fs = new FnService(ar, logSpy, windowMock);
 
         TestBed.configureTestingModule({
-            imports: [ BrowserAnimationsModule, RouterTestingModule ],
+            imports: [
+                BrowserAnimationsModule,
+                RouterTestingModule,
+                FormsModule,
+                ReactiveFormsModule
+            ],
             declarations: [
                 TopologyComponent,
                 InstanceComponent,
@@ -173,7 +182,10 @@
                 HostNodeSvgComponent,
                 DraggableDirective,
                 ZoomableDirective,
-                SubRegionNodeSvgComponent
+                SubRegionNodeSvgComponent,
+                MapSelectorComponent,
+                BackgroundSvgComponent,
+                MapSvgComponent
             ],
             providers: [
                 { provide: FnService, useValue: fs },
@@ -201,6 +213,7 @@
     beforeEach(() => {
         fixture = TestBed.createComponent(TopologyComponent);
         component = fixture.componentInstance;
+
         fixture.detectChanges();
     });
 
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 ff8a050..750162f 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,7 +28,6 @@
     PrefsService,
     SvgUtilService,
     WebSocketService,
-    ZoomService
 } from 'gui2-fw-lib';
 import {InstanceComponent} from '../panel/instance/instance.component';
 import {DetailsComponent} from '../panel/details/details.component';
@@ -45,12 +44,15 @@
     HOSTS_TOGGLE, OFFLINE_TOGGLE, PORTS_TOGGLE,
     BKGRND_TOGGLE, CYCLELABELS_BTN, CYCLEHOSTLABEL_BTN,
     RESETZOOM_BTN, EQMASTER_BTN,
-    CANCEL_TRAFFIC, ALL_TRAFFIC, QUICKHELP_BTN
+    CANCEL_TRAFFIC, ALL_TRAFFIC, QUICKHELP_BTN, BKGRND_SELECT
 } from '../panel/toolbar/toolbar.component';
 import {TrafficService} from '../traffic.service';
 import {ZoomableDirective} from '../layer/zoomable.directive';
+import {MapObject} from '../layer/maputils';
 
 const TOPO2_PREFS = 'topo2_prefs';
+const TOPO_MAPID_PREFS = 'topo_mapid';
+
 const PREF_BG = 'bg';
 const PREF_DETAIL = 'detail';
 const PREF_DLBLS = 'dlbls';
@@ -63,7 +65,7 @@
 const PREF_TOOLBAR = 'toolbar';
 
 /**
- * model of the topo2_prefs object - this is a subset of the overall Prefs returned
+ * Model of the topo2_prefs object - this is a subset of the overall Prefs returned
  * by the server
  */
 export interface Topo2Prefs {
@@ -134,6 +136,12 @@
         summary: 1,
         toolbar: 0,
     };
+
+    mapIdState: MapObject = <MapObject>{
+        id: undefined,
+        scale: 1.0
+    };
+    mapSelShown: boolean = false;
     lionFn; // Function
 
     constructor(
@@ -167,6 +175,7 @@
         this.is.loadIconDef('m_summary');
         this.is.loadIconDef('m_details');
         this.is.loadIconDef('m_map');
+        this.is.loadIconDef('m_selectMap');
         this.is.loadIconDef('m_cycleLabels');
         this.is.loadIconDef('m_resetZoom');
         this.is.loadIconDef('m_eqMaster');
@@ -217,6 +226,7 @@
 
         this.ps.addListener((data) => this.prefsUpdateHandler(data));
         this.prefsState = this.ps.getPrefs(TOPO2_PREFS, this.prefsState);
+        this.mapIdState = this.ps.getPrefs(TOPO_MAPID_PREFS, this.mapIdState);
         this.log.debug('Topology component initialized');
     }
 
@@ -228,8 +238,13 @@
      */
     prefsUpdateHandler(data: any): void {
         // Extract the TOPO2 prefs from it
-        this.prefsState = data[TOPO2_PREFS];
-        this.log.debug('Updated topo2 prefs', this.prefsState);
+        if (data[TOPO2_PREFS]) {
+            this.prefsState = data[TOPO2_PREFS];
+        }
+        if (data[TOPO_MAPID_PREFS]) {
+            this.mapIdState = data[TOPO_MAPID_PREFS];
+        }
+        this.log.debug('Updated topo2 prefs', this.prefsState, this.mapIdState);
     }
 
     /**
@@ -270,6 +285,9 @@
             case BKGRND_TOGGLE:
                 this.toggleBackground();
                 break;
+            case BKGRND_SELECT:
+                this.mapSelShown = !this.mapSelShown;
+                break;
             case CYCLELABELS_BTN:
                 this.cycleDeviceLabels();
                 break;
@@ -309,6 +327,7 @@
             B: [(token) => {this.toggleBackground(token); }, 'Toggle background'],
             D: [(token) => {this.toggleDetails(token); }, 'Toggle details panel'],
             I: [(token) => {this.toggleInstancePanel(token); }, 'Toggle ONOS Instance Panel'],
+            G: [() => {this.mapSelShown = !this.mapSelShown; }, 'Show map selection dialog'],
             O: [() => {this.toggleSummary(); }, 'Toggle the Summary Panel'],
             R: [() => {this.resetZoom(); }, 'Reset pan / zoom'],
             P: [(token) => {this.togglePorts(token); }, 'Toggle Port Highlighting'],
@@ -572,6 +591,13 @@
         this.trs.destroy();
     }
 
+    changeMap(map: MapObject) {
+        this.mapSelShown = false; // Hide the MapSelector component
+        this.mapIdState = map;
+        this.ps.setPrefs(TOPO_MAPID_PREFS, this.mapIdState);
+        this.log.debug('Map has been changed to ', map);
+    }
+
     /**
      * Read the LION bundle for Toolbar and set up the lionFn
      */
diff --git a/web/gui2/src/main/webapp/data/img/apple-touch-icon.png b/web/gui2/src/main/webapp/data/img/apple-touch-icon.png
deleted file mode 100644
index 3bf5eb6..0000000
--- a/web/gui2/src/main/webapp/data/img/apple-touch-icon.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/dropdown-icon.png b/web/gui2/src/main/webapp/data/img/dropdown-icon.png
deleted file mode 100644
index eb57f17..0000000
--- a/web/gui2/src/main/webapp/data/img/dropdown-icon.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-01.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-01.png
deleted file mode 100644
index 5f209a3..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-01.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-02.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-02.png
deleted file mode 100644
index 18f371b..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-02.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-03.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-03.png
deleted file mode 100644
index 00eb3a8..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-03.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-04.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-04.png
deleted file mode 100644
index 5977f6e..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-04.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-05.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-05.png
deleted file mode 100644
index 1da5d28..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-05.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-06.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-06.png
deleted file mode 100644
index 871b9a6..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-06.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-07.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-07.png
deleted file mode 100644
index 7538b84..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-07.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-08.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-08.png
deleted file mode 100644
index a7f3233..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-08.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-09.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-09.png
deleted file mode 100644
index 8c311df..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-09.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-10.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-10.png
deleted file mode 100644
index cf59c93..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-10.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-11.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-11.png
deleted file mode 100644
index e18c93a..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-11.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-12.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-12.png
deleted file mode 100644
index c63c240..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-12.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-13.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-13.png
deleted file mode 100644
index a6b3c7a..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-13.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-14.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-14.png
deleted file mode 100644
index 046a132..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-14.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-15.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-15.png
deleted file mode 100644
index c22108f..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-15.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/dark/load-16.png b/web/gui2/src/main/webapp/data/img/loading/dark/load-16.png
deleted file mode 100644
index b685ad5..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/dark/load-16.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-01.png b/web/gui2/src/main/webapp/data/img/loading/light/load-01.png
deleted file mode 100644
index 5cfedd6..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-01.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-02.png b/web/gui2/src/main/webapp/data/img/loading/light/load-02.png
deleted file mode 100644
index ec726fa..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-02.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-03.png b/web/gui2/src/main/webapp/data/img/loading/light/load-03.png
deleted file mode 100644
index d815032..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-03.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-04.png b/web/gui2/src/main/webapp/data/img/loading/light/load-04.png
deleted file mode 100644
index 6b97e0e..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-04.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-05.png b/web/gui2/src/main/webapp/data/img/loading/light/load-05.png
deleted file mode 100644
index 7b20749..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-05.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-06.png b/web/gui2/src/main/webapp/data/img/loading/light/load-06.png
deleted file mode 100644
index 4926bcc..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-06.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-07.png b/web/gui2/src/main/webapp/data/img/loading/light/load-07.png
deleted file mode 100644
index 27c95fd..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-07.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-08.png b/web/gui2/src/main/webapp/data/img/loading/light/load-08.png
deleted file mode 100644
index 0709c12..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-08.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-09.png b/web/gui2/src/main/webapp/data/img/loading/light/load-09.png
deleted file mode 100644
index 8318b43..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-09.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-10.png b/web/gui2/src/main/webapp/data/img/loading/light/load-10.png
deleted file mode 100644
index 57dd853..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-10.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-11.png b/web/gui2/src/main/webapp/data/img/loading/light/load-11.png
deleted file mode 100644
index 549c3c2..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-11.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-12.png b/web/gui2/src/main/webapp/data/img/loading/light/load-12.png
deleted file mode 100644
index 17bb059..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-12.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-13.png b/web/gui2/src/main/webapp/data/img/loading/light/load-13.png
deleted file mode 100644
index b52e5d7..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-13.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-14.png b/web/gui2/src/main/webapp/data/img/loading/light/load-14.png
deleted file mode 100644
index 78f6183f7..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-14.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-15.png b/web/gui2/src/main/webapp/data/img/loading/light/load-15.png
deleted file mode 100644
index e29dbc5..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-15.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/loading/light/load-16.png b/web/gui2/src/main/webapp/data/img/loading/light/load-16.png
deleted file mode 100644
index 74c7933..0000000
--- a/web/gui2/src/main/webapp/data/img/loading/light/load-16.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/masthead-logo-mojo.png b/web/gui2/src/main/webapp/data/img/masthead-logo-mojo.png
deleted file mode 100644
index 969c5fb..0000000
--- a/web/gui2/src/main/webapp/data/img/masthead-logo-mojo.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/nav-menu-mojo.png b/web/gui2/src/main/webapp/data/img/nav-menu-mojo.png
deleted file mode 100644
index d8cd6d5..0000000
--- a/web/gui2/src/main/webapp/data/img/nav-menu-mojo.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/nav-menu.png b/web/gui2/src/main/webapp/data/img/nav-menu.png
deleted file mode 100644
index 07bbcf1..0000000
--- a/web/gui2/src/main/webapp/data/img/nav-menu.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/onos-logo-fliprotate.png b/web/gui2/src/main/webapp/data/img/onos-logo-fliprotate.png
deleted file mode 100644
index 7368017..0000000
--- a/web/gui2/src/main/webapp/data/img/onos-logo-fliprotate.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/onos-logo.lg.png b/web/gui2/src/main/webapp/data/img/onos-logo.lg.png
deleted file mode 100644
index afbd438..0000000
--- a/web/gui2/src/main/webapp/data/img/onos-logo.lg.png
+++ /dev/null
Binary files differ
diff --git a/web/gui2/src/main/webapp/data/img/onos-logo.png b/web/gui2/src/main/webapp/data/img/onos-logo.png
deleted file mode 100644
index 8688cd6..0000000
--- a/web/gui2/src/main/webapp/data/img/onos-logo.png
+++ /dev/null
Binary files differ