blob: ec9bdaec6fb774883873ca0476eaa976b070996a [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 Condonff85fbe2019-03-16 14:28:46 +000098 this.pathgen = d3.geoPath().projection(d3.geoIdentity().reflectY(true)
99 // MapSvgComponent.scale()
100 );
Sean Condon71910542019-02-16 18:16:42 +0000101
102 // this.log.debug('Feature Test',this.testFeature);
103 // this.testPath = this.pathgen(this.testFeature);
104 // this.log.debug('Feature Path', this.testPath);
Sean Condon0d064ec2019-02-04 21:53:53 +0000105 this.log.debug('MapSvgComponent constructed');
Sean Condonf4f54a12018-10-10 23:25:46 +0100106 }
107
Sean Condon0d064ec2019-02-04 21:53:53 +0000108 static getUrl(id: string): string {
109 if (id && id[0] === '*') {
110 return BUNDLED_URL_PREFIX + id.slice(1) + '.topojson';
111 }
112 return id + '.topojson';
113 }
114
Sean Condon0d064ec2019-02-04 21:53:53 +0000115 /**
116 * Wrapper for the path generator function
117 * @param feature The county or state within the map
118 */
119 pathGenerator(feature: Feature): string {
Sean Condon71910542019-02-16 18:16:42 +0000120 return this.pathgen(feature);
121 }
122
123 ngOnChanges(changes: SimpleChanges): void {
124 this.log.debug('Change detected', changes);
125 if (changes['map']) {
126 const map: MapObject = <MapObject>(changes['map'].currentValue);
127 if (map.id) {
128 this.httpClient
129 .get(MapSvgComponent.getUrl(map.filePath))
130 .subscribe((topoData: TopoData) => {
131 // this.mapPathGenerator =
132 this.handleTopoJson(map, topoData);
133 this.log.debug('Path Generated for', map.id,
134 'from', MapSvgComponent.getUrl(map.filePath));
135 });
136 }
137 }
Sean Condon0d064ec2019-02-04 21:53:53 +0000138 }
139
140 /**
141 * Handle the topojson file stream as it arrives back from the server
142 *
143 * The topojson library converts the topojson file in to a FeatureCollection
144 * d3.geo then further converts this in to a Path
145 *
146 * @param map The Map chosen in the GUI
147 * @param topoData The data in the TopoJson file
148 */
Sean Condon71910542019-02-16 18:16:42 +0000149 handleTopoJson(map: MapObject, topoData: TopoData): void {
Sean Condon0d064ec2019-02-04 21:53:53 +0000150
Sean Condon71910542019-02-16 18:16:42 +0000151 let topoObject = topoData.objects[map.id];
152 if (!topoObject) {
153 topoObject = topoData.objects['states'];
154 }
155 this.log.debug('Topo obj', topoObject, 'topodata', topoData);
156 this.geodata = <FeatureCollection>topojson.feature(topoData, topoObject);
Sean Condon1ae15802019-03-02 09:07:18 +0000157 const bounds = this.pathgen.bounds(this.geodata);
158 this.mapBounds.emit(<MapBounds>{
159 lngMin: bounds[0][0],
160 latMin: -bounds[0][1], // Y was inverted in the transform
161 lngMax: bounds[1][0],
162 latMax: -bounds[1][1] // Y was inverted in the transform
163 });
Sean Condon71910542019-02-16 18:16:42 +0000164 this.log.debug('Map retrieved', topoData, this.geodata);
Sean Condon0d064ec2019-02-04 21:53:53 +0000165
Sean Condon0d064ec2019-02-04 21:53:53 +0000166 }
Sean Condonf4f54a12018-10-10 23:25:46 +0100167}