Simon Hunt

GUI -- MapService: rework API and internal code for loading map. WIP.

Change-Id: I74458a3ef615d67a0fe9869926ef230990cd902f
...@@ -42,11 +42,10 @@ ...@@ -42,11 +42,10 @@
42 'use strict'; 42 'use strict';
43 43
44 // injected references 44 // injected references
45 - var $log, $http, $q, fs; 45 + var $log, $http, fs;
46 46
47 // internal state 47 // internal state
48 - var maps = d3.map(), 48 + var mapCache = d3.map(),
49 - msgMs = 'MapService.',
50 bundledUrlPrefix = '../data/map/'; 49 bundledUrlPrefix = '../data/map/';
51 50
52 function getUrl(id) { 51 function getUrl(id) {
...@@ -57,56 +56,42 @@ ...@@ -57,56 +56,42 @@
57 } 56 }
58 57
59 angular.module('onosSvg') 58 angular.module('onosSvg')
60 - .factory('MapService', ['$log', '$http', '$q', 'FnService', 59 + .factory('MapService', ['$log', '$http', 'FnService',
61 - 60 + function (_$log_, _$http_, _fs_) {
62 - function (_$log_, _$http_, _$q_, _fs_) {
63 $log = _$log_; 61 $log = _$log_;
64 $http = _$http_; 62 $http = _$http_;
65 - $q = _$q_;
66 fs = _fs_; 63 fs = _fs_;
67 64
68 - function clearCache() {
69 - maps = d3.map();
70 - }
71 -
72 - // NOTE: It is expected that mapLayer is a D3 selection of the
73 - // <g> element (a child of zoomLayer) into which the map
74 - // path data will be rendered.
75 - function renderMap(mapLayer) {
76 - // TODO ---
77 - $log.log('Hey, let\'s render the map...');
78 - }
79 65
80 function fetchGeoMap(id) { 66 function fetchGeoMap(id) {
81 if (!fs.isS(id)) { 67 if (!fs.isS(id)) {
82 return null; 68 return null;
83 } 69 }
84 - var url = getUrl(id); 70 + var url = getUrl(id),
85 - 71 + promise = mapCache.get(id);
86 - var promise = maps.get(id);
87 72
88 if (!promise) { 73 if (!promise) {
89 - // need to fetch the data and build the object... 74 + // need to fetch the data, build the object,
90 - var deferred = $q.defer(); 75 + // cache it, and return it.
91 - promise = deferred.promise; 76 + promise = $http.get(url);
92 -
93 - $http.get(url)
94 - .success(function (data) {
95 - deferred.resolve(data);
96 - })
97 - .error(function (msg, code) {
98 - deferred.reject(msg);
99 - $log.warn(msg, code);
100 - });
101 77
102 promise.meta = { 78 promise.meta = {
103 id: id, 79 id: id,
104 url: url, 80 url: url,
105 - wasCached: false, 81 + wasCached: false
106 - render: renderMap
107 }; 82 };
108 83
109 - maps.set(id, promise); 84 + promise.then(function (response) {
85 + // success
86 + promise.mapdata = response.data;
87 + }, function (response) {
88 + // error
89 + $log.warn('Failed to retrieve map data: ' + url,
90 + response.status, response.data);
91 + });
92 +
93 + mapCache.set(id, promise);
94 +
110 } else { 95 } else {
111 promise.meta.wasCached = true; 96 promise.meta.wasCached = true;
112 } 97 }
...@@ -114,9 +99,66 @@ ...@@ -114,9 +99,66 @@
114 return promise; 99 return promise;
115 } 100 }
116 101
102 + var geoMapProj;
103 +
104 + function setProjForView(path, topoData) {
105 + var dim = 1000;
106 +
107 + // start with unit scale, no translation..
108 + geoMapProj.scale(1).translate([0, 0]);
109 +
110 + // figure out dimensions of map data..
111 + var b = path.bounds(topoData),
112 + x1 = b[0][0],
113 + y1 = b[0][1],
114 + x2 = b[1][0],
115 + y2 = b[1][1],
116 + dx = x2 - x1,
117 + dy = y2 - y1,
118 + x = (x1 + x2) / 2,
119 + y = (y1 + y2) / 2;
120 +
121 + // size map to 95% of minimum dimension to fill space..
122 + var s = .95 / Math.min(dx / dim, dy / dim);
123 + var t = [dim / 2 - s * x, dim / 2 - s * y];
124 +
125 + // set new scale, translation on the projection..
126 + geoMapProj.scale(s).translate(t);
127 + }
128 +
129 +
130 + function loadMapInto(mapLayer, id) {
131 + var mapObject = fetchGeoMap(id);
132 + if (!mapObject) {
133 + $log.warn('Failed to load map: ' + id);
134 + return null;
135 + }
136 +
137 + var mapdata = mapObject.mapdata,
138 + topoData, path;
139 +
140 + mapObject.then(function () {
141 + // extracts the topojson data into geocoordinate-based geometry
142 + topoData = topojson.feature(mapdata, mapdata.objects.states);
143 +
144 + // see: http://bl.ocks.org/mbostock/4707858
145 + geoMapProj = d3.geo.mercator();
146 + path = d3.geo.path().projection(geoMapProj);
147 +
148 + setProjForView(path, topoData);
149 +
150 + mapLayer.selectAll('path')
151 + .data(topoData.features)
152 + .enter()
153 + .append('path')
154 + .attr('d', path);
155 + });
156 + // TODO: review whether we should just return true (not the map object)
157 + return mapObject;
158 + }
159 +
117 return { 160 return {
118 - clearCache: clearCache, 161 + loadMapInto: loadMapInto
119 - fetchGeoMap: fetchGeoMap
120 }; 162 };
121 }]); 163 }]);
122 164
......
...@@ -20,20 +20,21 @@ ...@@ -20,20 +20,21 @@
20 @author Simon Hunt 20 @author Simon Hunt
21 */ 21 */
22 describe('factory: fw/svg/map.js', function() { 22 describe('factory: fw/svg/map.js', function() {
23 - var $log, fs, ms, d3Elem, promise; 23 + var $log, $httpBackend, fs, ms, d3Elem, promise;
24 24
25 beforeEach(module('onosUtil', 'onosSvg')); 25 beforeEach(module('onosUtil', 'onosSvg'));
26 26
27 - beforeEach(inject(function (_$log_, FnService, MapService) { 27 + beforeEach(inject(function (_$log_, _$httpBackend_, FnService, MapService) {
28 $log = _$log_; 28 $log = _$log_;
29 + $httpBackend = _$httpBackend_;
29 fs = FnService; 30 fs = FnService;
30 ms = MapService; 31 ms = MapService;
31 - ms.clearCache(); 32 + //ms.clearCache();
32 - // TODO: d3Elem = d3.select('body').append('...').attr('id', 'myFoo'); 33 + d3Elem = d3.select('body').append('svg').append('g').attr('id', 'mapLayer');
33 })); 34 }));
34 35
35 afterEach(function () { 36 afterEach(function () {
36 - // TODO d3.select('#myFoo').remove(); 37 + d3.select('svg').remove();
37 }); 38 });
38 39
39 it('should define MapService', function () { 40 it('should define MapService', function () {
...@@ -42,10 +43,54 @@ describe('factory: fw/svg/map.js', function() { ...@@ -42,10 +43,54 @@ describe('factory: fw/svg/map.js', function() {
42 43
43 it('should define api functions', function () { 44 it('should define api functions', function () {
44 expect(fs.areFunctions(ms, [ 45 expect(fs.areFunctions(ms, [
45 - 'clearCache', 'fetchGeoMap' 46 + 'loadMapInto'
46 ])).toBeTruthy(); 47 ])).toBeTruthy();
47 }); 48 });
48 49
50 + var fakeMapId = '../tests/app/fw/svg/fake-map-data',
51 + fakeMapUrl = fakeMapId + '.json';
52 +
53 + var fakeMapData = {
54 + "type": "Topology",
55 + "objects": {
56 + "states": {
57 + "type": "GeometryCollection",
58 + "geometries": [
59 + { "type": "Polygon", "arcs": [[0, 1]]},
60 + { "type": "Polygon", "arcs": [[2, 3]]}
61 + ]
62 + }
63 + },
64 + "arcs": [
65 + [ [6347, 2300], [ -16, -9], [ -22, 1], [ -5, 3], [ 9, 6], [ 27, 7], [ 7, -8]],
66 + [ [6447, 2350], [ -4, -4], [ -19, -41], [ -66, -14], [ 4, 9], [ 14, 2]],
67 + [ [6290, 2347], [ -2, 83], [ -2, 76], [ -2, 75], [ -2, 76], [ -2, 76], [ -2, 75]],
68 + [ [6329, 4211], [ -3, 6], [ -2, 4], [ 2, 1], [ 28, -1], [ 28, 0]]
69 + ],
70 + "transform": {
71 + "scale": [0.005772872856602365, 0.0024829805705001468],
72 + "translate": [-124.70997774915153, 24.542349340056283]
73 + }
74 + };
75 +
76 +
77 + it('should load map into layer', function () {
78 + $httpBackend.expectGET(fakeMapUrl).respond(fakeMapData);
79 +
80 + var obj = ms.loadMapInto(d3Elem, fakeMapId);
81 + //$httpBackend.flush();
82 + // TODO: figure out how to test this function as a black box test.
83 +
84 + expect(obj).toBeTruthy();
85 + debugger;
86 +
87 + // todo: assert that paths are added to map layer element
88 + });
89 +
90 +/*
91 +
92 +
93 +
49 it('should return null when no parameters given', function () { 94 it('should return null when no parameters given', function () {
50 promise = ms.fetchGeoMap(); 95 promise = ms.fetchGeoMap();
51 expect(promise).toBeNull(); 96 expect(promise).toBeNull();
...@@ -95,13 +140,18 @@ describe('factory: fw/svg/map.js', function() { ...@@ -95,13 +140,18 @@ describe('factory: fw/svg/map.js', function() {
95 expect(promise.meta.wasCached).toBeFalsy(); 140 expect(promise.meta.wasCached).toBeFalsy();
96 }); 141 });
97 142
98 - it('should load USA into cache', function () { 143 +
99 - var id = '*continental_us'; 144 + it('should log a warning if data fails to load', function () {
100 - promise = ms.fetchGeoMap(id); 145 + $httpBackend.expectGET(mapurl).respond(404, 'Not found');
101 - expect(promise).toBeDefined(); 146 + spyOn($log, 'warn');
102 - expect(promise.meta.id).toBe(id); 147 +
103 - expect(promise.meta.url).toBe('../data/map/continental_us.json'); 148 + promise = ms.fetchGeoMap(mapid);
104 - // TODO: WIP -- after a pause, the data should be there!!! 149 + $httpBackend.flush();
150 + expect(promise.mapdata).toBeUndefined();
151 + expect($log.warn)
152 + .toHaveBeenCalledWith('Failed to retrieve map data: ' + mapurl,
153 + 404, 'Not found');
105 154
106 }); 155 });
156 +*/
107 }); 157 });
......