blob: 28ece5e246bc90a30d9afc056dfd952f822a5441 [file] [log] [blame]
/*
* Copyright 2018-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 {
Component, EventEmitter,
Input,
OnChanges, Output,
SimpleChanges
} from '@angular/core';
import { MapObject } from '../maputils';
import {LogService, MapBounds} from 'gui2-fw-lib/public_api';
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 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
*/
export 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
}
@Component({
selector: '[onos-mapsvg]',
templateUrl: './mapsvg.component.html',
styleUrls: ['./mapsvg.component.css']
})
export class MapSvgComponent implements OnChanges {
@Input() map: MapObject = <MapObject>{id: 'none'};
@Output() mapBounds = new EventEmitter<MapBounds>();
geodata: FeatureCollection;
pathgen: any; // (Feature) => string; have to leave it general, as there is the bounds method used below
// testPath: string;
// testFeature = <Feature>{
// id: 'test',
// type: 'Feature',
// geometry: {
// coordinates: [
// [[-15, 60], [45, 60], [45, 45], [-15, 45], [-15, 60]],
// [[-10, 55], [45, 55], [45, 50], [-10, 50], [-10, 55]],
// ],
// type: 'Polygon'
// },
// properties: { name: 'Test'}
// };
constructor(
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(d3.geoIdentity().reflectY(true)
// 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');
}
static getUrl(id: string): string {
if (id && id[0] === '*') {
return BUNDLED_URL_PREFIX + id.slice(1) + '.topojson';
}
return id + '.topojson';
}
/**
* Wrapper for the path generator function
* @param feature The county or state within the map
*/
pathGenerator(feature: Feature): string {
return this.pathgen(feature);
}
ngOnChanges(changes: SimpleChanges): void {
this.log.debug('Change detected', changes);
if (changes['map']) {
const map: MapObject = <MapObject>(changes['map'].currentValue);
if (map.id) {
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));
});
}
}
}
/**
* 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): void {
let topoObject = topoData.objects[map.id];
if (!topoObject) {
topoObject = topoData.objects['states'];
}
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);
}
}