GUI2 Added selection of background maps
Change-Id: I88ee69fe2ff24bb1b4b3fe633b04f2f1778f3a82
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);
+ }
}