GUI -- MapService: rework API and internal code for loading map. WIP.
Change-Id: I74458a3ef615d67a0fe9869926ef230990cd902f
Showing
2 changed files
with
143 additions
and
51 deletions
... | @@ -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 | }); | ... | ... |
-
Please register or login to post a comment