GUI -- MapService: rework API and internal code for loading map. WIP.
Change-Id: I74458a3ef615d67a0fe9869926ef230990cd902f
diff --git a/web/gui/src/main/webapp/app/fw/svg/map.js b/web/gui/src/main/webapp/app/fw/svg/map.js
index a14a038..7a6b981 100644
--- a/web/gui/src/main/webapp/app/fw/svg/map.js
+++ b/web/gui/src/main/webapp/app/fw/svg/map.js
@@ -42,11 +42,10 @@
'use strict';
// injected references
- var $log, $http, $q, fs;
+ var $log, $http, fs;
// internal state
- var maps = d3.map(),
- msgMs = 'MapService.',
+ var mapCache = d3.map(),
bundledUrlPrefix = '../data/map/';
function getUrl(id) {
@@ -57,56 +56,42 @@
}
angular.module('onosSvg')
- .factory('MapService', ['$log', '$http', '$q', 'FnService',
-
- function (_$log_, _$http_, _$q_, _fs_) {
+ .factory('MapService', ['$log', '$http', 'FnService',
+ function (_$log_, _$http_, _fs_) {
$log = _$log_;
$http = _$http_;
- $q = _$q_;
fs = _fs_;
- function clearCache() {
- maps = d3.map();
- }
-
- // NOTE: It is expected that mapLayer is a D3 selection of the
- // <g> element (a child of zoomLayer) into which the map
- // path data will be rendered.
- function renderMap(mapLayer) {
- // TODO ---
- $log.log('Hey, let\'s render the map...');
- }
function fetchGeoMap(id) {
if (!fs.isS(id)) {
return null;
}
- var url = getUrl(id);
-
- var promise = maps.get(id);
+ var url = getUrl(id),
+ promise = mapCache.get(id);
if (!promise) {
- // need to fetch the data and build the object...
- var deferred = $q.defer();
- promise = deferred.promise;
-
- $http.get(url)
- .success(function (data) {
- deferred.resolve(data);
- })
- .error(function (msg, code) {
- deferred.reject(msg);
- $log.warn(msg, code);
- });
+ // need to fetch the data, build the object,
+ // cache it, and return it.
+ promise = $http.get(url);
promise.meta = {
id: id,
url: url,
- wasCached: false,
- render: renderMap
+ wasCached: false
};
- maps.set(id, promise);
+ promise.then(function (response) {
+ // success
+ promise.mapdata = response.data;
+ }, function (response) {
+ // error
+ $log.warn('Failed to retrieve map data: ' + url,
+ response.status, response.data);
+ });
+
+ mapCache.set(id, promise);
+
} else {
promise.meta.wasCached = true;
}
@@ -114,9 +99,66 @@
return promise;
}
+ var geoMapProj;
+
+ function setProjForView(path, topoData) {
+ var dim = 1000;
+
+ // start with unit scale, no translation..
+ geoMapProj.scale(1).translate([0, 0]);
+
+ // figure out dimensions of map data..
+ var b = path.bounds(topoData),
+ x1 = b[0][0],
+ y1 = b[0][1],
+ x2 = b[1][0],
+ y2 = b[1][1],
+ dx = x2 - x1,
+ dy = y2 - y1,
+ x = (x1 + x2) / 2,
+ y = (y1 + y2) / 2;
+
+ // size map to 95% of minimum dimension to fill space..
+ var s = .95 / Math.min(dx / dim, dy / dim);
+ var t = [dim / 2 - s * x, dim / 2 - s * y];
+
+ // set new scale, translation on the projection..
+ geoMapProj.scale(s).translate(t);
+ }
+
+
+ function loadMapInto(mapLayer, id) {
+ var mapObject = fetchGeoMap(id);
+ if (!mapObject) {
+ $log.warn('Failed to load map: ' + id);
+ return null;
+ }
+
+ var mapdata = mapObject.mapdata,
+ topoData, path;
+
+ mapObject.then(function () {
+ // extracts the topojson data into geocoordinate-based geometry
+ topoData = topojson.feature(mapdata, mapdata.objects.states);
+
+ // see: http://bl.ocks.org/mbostock/4707858
+ geoMapProj = d3.geo.mercator();
+ path = d3.geo.path().projection(geoMapProj);
+
+ setProjForView(path, topoData);
+
+ mapLayer.selectAll('path')
+ .data(topoData.features)
+ .enter()
+ .append('path')
+ .attr('d', path);
+ });
+ // TODO: review whether we should just return true (not the map object)
+ return mapObject;
+ }
+
return {
- clearCache: clearCache,
- fetchGeoMap: fetchGeoMap
+ loadMapInto: loadMapInto
};
}]);
diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js
index 5dfb1d6..23746f4 100644
--- a/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js
@@ -20,20 +20,21 @@
@author Simon Hunt
*/
describe('factory: fw/svg/map.js', function() {
- var $log, fs, ms, d3Elem, promise;
+ var $log, $httpBackend, fs, ms, d3Elem, promise;
beforeEach(module('onosUtil', 'onosSvg'));
- beforeEach(inject(function (_$log_, FnService, MapService) {
+ beforeEach(inject(function (_$log_, _$httpBackend_, FnService, MapService) {
$log = _$log_;
+ $httpBackend = _$httpBackend_;
fs = FnService;
ms = MapService;
- ms.clearCache();
- // TODO: d3Elem = d3.select('body').append('...').attr('id', 'myFoo');
+ //ms.clearCache();
+ d3Elem = d3.select('body').append('svg').append('g').attr('id', 'mapLayer');
}));
afterEach(function () {
- // TODO d3.select('#myFoo').remove();
+ d3.select('svg').remove();
});
it('should define MapService', function () {
@@ -42,10 +43,54 @@
it('should define api functions', function () {
expect(fs.areFunctions(ms, [
- 'clearCache', 'fetchGeoMap'
+ 'loadMapInto'
])).toBeTruthy();
});
+ var fakeMapId = '../tests/app/fw/svg/fake-map-data',
+ fakeMapUrl = fakeMapId + '.json';
+
+ var fakeMapData = {
+ "type": "Topology",
+ "objects": {
+ "states": {
+ "type": "GeometryCollection",
+ "geometries": [
+ { "type": "Polygon", "arcs": [[0, 1]]},
+ { "type": "Polygon", "arcs": [[2, 3]]}
+ ]
+ }
+ },
+ "arcs": [
+ [ [6347, 2300], [ -16, -9], [ -22, 1], [ -5, 3], [ 9, 6], [ 27, 7], [ 7, -8]],
+ [ [6447, 2350], [ -4, -4], [ -19, -41], [ -66, -14], [ 4, 9], [ 14, 2]],
+ [ [6290, 2347], [ -2, 83], [ -2, 76], [ -2, 75], [ -2, 76], [ -2, 76], [ -2, 75]],
+ [ [6329, 4211], [ -3, 6], [ -2, 4], [ 2, 1], [ 28, -1], [ 28, 0]]
+ ],
+ "transform": {
+ "scale": [0.005772872856602365, 0.0024829805705001468],
+ "translate": [-124.70997774915153, 24.542349340056283]
+ }
+ };
+
+
+ it('should load map into layer', function () {
+ $httpBackend.expectGET(fakeMapUrl).respond(fakeMapData);
+
+ var obj = ms.loadMapInto(d3Elem, fakeMapId);
+ //$httpBackend.flush();
+ // TODO: figure out how to test this function as a black box test.
+
+ expect(obj).toBeTruthy();
+ debugger;
+
+ // todo: assert that paths are added to map layer element
+ });
+
+/*
+
+
+
it('should return null when no parameters given', function () {
promise = ms.fetchGeoMap();
expect(promise).toBeNull();
@@ -95,13 +140,18 @@
expect(promise.meta.wasCached).toBeFalsy();
});
- it('should load USA into cache', function () {
- var id = '*continental_us';
- promise = ms.fetchGeoMap(id);
- expect(promise).toBeDefined();
- expect(promise.meta.id).toBe(id);
- expect(promise.meta.url).toBe('../data/map/continental_us.json');
- // TODO: WIP -- after a pause, the data should be there!!!
+
+ it('should log a warning if data fails to load', function () {
+ $httpBackend.expectGET(mapurl).respond(404, 'Not found');
+ spyOn($log, 'warn');
+
+ promise = ms.fetchGeoMap(mapid);
+ $httpBackend.flush();
+ expect(promise.mapdata).toBeUndefined();
+ expect($log.warn)
+ .toHaveBeenCalledWith('Failed to retrieve map data: ' + mapurl,
+ 404, 'Not found');
});
+*/
});