blob: 679bfd4258908388b718d33d2f1992c195965561 [file] [log] [blame]
Sean Condonf4f54a12018-10-10 23:25:46 +01001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the 'License');
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an 'AS IS' BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Sean Condon0d064ec2019-02-04 21:53:53 +000016import {
Sean Condon1ae15802019-03-02 09:07:18 +000017 Component, EventEmitter,
Sean Condon0d064ec2019-02-04 21:53:53 +000018 Input,
Sean Condon1ae15802019-03-02 09:07:18 +000019 OnChanges, Output,
Sean Condon0d064ec2019-02-04 21:53:53 +000020 SimpleChanges
21} from '@angular/core';
22import { MapObject } from '../maputils';
Sean Condon1ae15802019-03-02 09:07:18 +000023import {LogService, MapBounds} from 'gui2-fw-lib';
Sean Condon0d064ec2019-02-04 21:53:53 +000024import {HttpClient} from '@angular/common/http';
25import * as d3 from 'd3';
26import * as topojson from 'topojson-client';
27
28const BUNDLED_URL_PREFIX = 'data/map/';
29
30/**
31 * Model of the transform attribute of a topojson file
32 */
33interface TopoDataTransform {
34 scale: number[];
35 translate: number[];
36}
37
38/**
Sean Condon0d064ec2019-02-04 21:53:53 +000039 * Model of the Feature returned prom topojson library
40 */
41interface Feature {
42 geometry: Object;
43 id: string;
44 properties: Object;
45 type: string;
46}
47
48/**
49 * Model of the Features Collection returned by the topojson.features function
50 */
51interface FeatureCollection {
52 type: string;
53 features: Feature[];
54}
55
56/**
57 * Model of the topojson file
58 */
59interface TopoData {
60 type: string; // Usually "Topology"
61 objects: Object; // Can be a list of countries or individual countries
62 arcs: number[][][]; // Coordinates
63 bbox: number[]; // Bounding box
64 transform: TopoDataTransform; // scale and translate
65}
66
Sean Condonf4f54a12018-10-10 23:25:46 +010067@Component({
68 selector: '[onos-mapsvg]',
69 templateUrl: './mapsvg.component.html',
70 styleUrls: ['./mapsvg.component.css']
71})
Sean Condon0d064ec2019-02-04 21:53:53 +000072export class MapSvgComponent implements OnChanges {
73 @Input() map: MapObject = <MapObject>{id: 'none'};
Sean Condon1ae15802019-03-02 09:07:18 +000074 @Output() mapBounds = new EventEmitter<MapBounds>();
Sean Condonf4f54a12018-10-10 23:25:46 +010075
Sean Condon71910542019-02-16 18:16:42 +000076 geodata: FeatureCollection;
Sean Condon1ae15802019-03-02 09:07:18 +000077 pathgen: any; // (Feature) => string; have to leave it general, as there is the bounds method used below
Sean Condon71910542019-02-16 18:16:42 +000078 // testPath: string;
79 // testFeature = <Feature>{
80 // id: 'test',
81 // type: 'Feature',
82 // geometry: {
83 // coordinates: [
84 // [[-15, 60], [45, 60], [45, 45], [-15, 45], [-15, 60]],
85 // [[-10, 55], [45, 55], [45, 50], [-10, 50], [-10, 55]],
86 // ],
87 // type: 'Polygon'
88 // },
89 // properties: { name: 'Test'}
90 // };
Sean Condonf4f54a12018-10-10 23:25:46 +010091
Sean Condon0d064ec2019-02-04 21:53:53 +000092 constructor(
93 private log: LogService,
94 private httpClient: HttpClient,
95 ) {
Sean Condon1ae15802019-03-02 09:07:18 +000096 // Scale everything to 360 degrees wide and 150 high
97 // See background.component.html for more details
Sean Condon71910542019-02-16 18:16:42 +000098 this.pathgen = d3.geoPath().projection(
Sean Condon1ae15802019-03-02 09:07:18 +000099 MapSvgComponent.scale());
Sean Condon71910542019-02-16 18:16:42 +0000100
101 // this.log.debug('Feature Test',this.testFeature);
102 // this.testPath = this.pathgen(this.testFeature);
103 // this.log.debug('Feature Path', this.testPath);
Sean Condon0d064ec2019-02-04 21:53:53 +0000104 this.log.debug('MapSvgComponent constructed');
Sean Condonf4f54a12018-10-10 23:25:46 +0100105 }
106
Sean Condon0d064ec2019-02-04 21:53:53 +0000107 static getUrl(id: string): string {
108 if (id && id[0] === '*') {
109 return BUNDLED_URL_PREFIX + id.slice(1) + '.topojson';
110 }
111 return id + '.topojson';
112 }
113
Sean Condon1ae15802019-03-02 09:07:18 +0000114 static scale () {
Sean Condon71910542019-02-16 18:16:42 +0000115 return d3.geoTransform({
116 point: function(x, y) {
Sean Condon1ae15802019-03-02 09:07:18 +0000117 this.stream.point(x, -y); // 1-1 mapping but invert y-axis
Sean Condon0d064ec2019-02-04 21:53:53 +0000118 }
Sean Condon71910542019-02-16 18:16:42 +0000119 });
Sean Condon0d064ec2019-02-04 21:53:53 +0000120 }
121
122 /**
123 * Wrapper for the path generator function
124 * @param feature The county or state within the map
125 */
126 pathGenerator(feature: Feature): string {
Sean Condon71910542019-02-16 18:16:42 +0000127 return this.pathgen(feature);
128 }
129
130 ngOnChanges(changes: SimpleChanges): void {
131 this.log.debug('Change detected', changes);
132 if (changes['map']) {
133 const map: MapObject = <MapObject>(changes['map'].currentValue);
134 if (map.id) {
135 this.httpClient
136 .get(MapSvgComponent.getUrl(map.filePath))
137 .subscribe((topoData: TopoData) => {
138 // this.mapPathGenerator =
139 this.handleTopoJson(map, topoData);
140 this.log.debug('Path Generated for', map.id,
141 'from', MapSvgComponent.getUrl(map.filePath));
142 });
143 }
144 }
Sean Condon0d064ec2019-02-04 21:53:53 +0000145 }
146
147 /**
148 * Handle the topojson file stream as it arrives back from the server
149 *
150 * The topojson library converts the topojson file in to a FeatureCollection
151 * d3.geo then further converts this in to a Path
152 *
153 * @param map The Map chosen in the GUI
154 * @param topoData The data in the TopoJson file
155 */
Sean Condon71910542019-02-16 18:16:42 +0000156 handleTopoJson(map: MapObject, topoData: TopoData): void {
Sean Condon0d064ec2019-02-04 21:53:53 +0000157
Sean Condon71910542019-02-16 18:16:42 +0000158 let topoObject = topoData.objects[map.id];
159 if (!topoObject) {
160 topoObject = topoData.objects['states'];
161 }
162 this.log.debug('Topo obj', topoObject, 'topodata', topoData);
163 this.geodata = <FeatureCollection>topojson.feature(topoData, topoObject);
Sean Condon1ae15802019-03-02 09:07:18 +0000164 const bounds = this.pathgen.bounds(this.geodata);
165 this.mapBounds.emit(<MapBounds>{
166 lngMin: bounds[0][0],
167 latMin: -bounds[0][1], // Y was inverted in the transform
168 lngMax: bounds[1][0],
169 latMax: -bounds[1][1] // Y was inverted in the transform
170 });
Sean Condon71910542019-02-16 18:16:42 +0000171 this.log.debug('Map retrieved', topoData, this.geodata);
Sean Condon0d064ec2019-02-04 21:53:53 +0000172
Sean Condon0d064ec2019-02-04 21:53:53 +0000173 }
Sean Condonf4f54a12018-10-10 23:25:46 +0100174}