GUI -- Split MapService into GeoDataService and MapService. WIP.
Change-Id: Ibfe5b35ecdfaaf39b9d48abd29d0a44327dec130
Showing
5 changed files
with
255 additions
and
82 deletions
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
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 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- SVG -- GeoData Service | ||
19 | + | ||
20 | + @author Simon Hunt | ||
21 | + */ | ||
22 | + | ||
23 | +/* | ||
24 | + The GeoData Service caches GeoJSON map data, and provides supporting | ||
25 | + projections for mapping into SVG layers. | ||
26 | + | ||
27 | + A GeoMap object can be fetched by ID. IDs that start with an asterisk | ||
28 | + identify maps bundled with the GUI. IDs that do not start with an | ||
29 | + asterisk are assumed to be URLs to externally provided data (exact | ||
30 | + format to be decided). | ||
31 | + | ||
32 | + e.g. var geomap = GeoDataService.fetchGeoMap('*continental-us'); | ||
33 | + | ||
34 | + Note that, since the GeoMap instance is cached / shared, it should | ||
35 | + contain no state. | ||
36 | + */ | ||
37 | + | ||
38 | +(function () { | ||
39 | + 'use strict'; | ||
40 | + | ||
41 | + // injected references | ||
42 | + var $log, $http, fs; | ||
43 | + | ||
44 | + // internal state | ||
45 | + var cache = d3.map(), | ||
46 | + bundledUrlPrefix = '../data/map/'; | ||
47 | + | ||
48 | + function getUrl(id) { | ||
49 | + if (id[0] === '*') { | ||
50 | + return bundledUrlPrefix + id.slice(1) + '.json'; | ||
51 | + } | ||
52 | + return id + '.json'; | ||
53 | + } | ||
54 | + | ||
55 | + angular.module('onosSvg') | ||
56 | + .factory('GeoDataService', ['$log', '$http', 'FnService', | ||
57 | + function (_$log_, _$http_, _fs_) { | ||
58 | + $log = _$log_; | ||
59 | + $http = _$http_; | ||
60 | + fs = _fs_; | ||
61 | + | ||
62 | + // start afresh... | ||
63 | + function clearCache() { | ||
64 | + cache = d3.map(); | ||
65 | + } | ||
66 | + | ||
67 | + // returns a promise decorated with: | ||
68 | + // .meta -- id, url, and whether the data was cached | ||
69 | + // .mapdata -- geojson data (on response from server) | ||
70 | + | ||
71 | + function fetchGeoMap(id) { | ||
72 | + if (!fs.isS(id)) { | ||
73 | + return null; | ||
74 | + } | ||
75 | + var url = getUrl(id), | ||
76 | + promise = cache.get(id); | ||
77 | + | ||
78 | + if (!promise) { | ||
79 | + // need to fetch the data, build the object, | ||
80 | + // cache it, and return it. | ||
81 | + promise = $http.get(url); | ||
82 | + | ||
83 | + promise.meta = { | ||
84 | + id: id, | ||
85 | + url: url, | ||
86 | + wasCached: false | ||
87 | + }; | ||
88 | + | ||
89 | + promise.then(function (response) { | ||
90 | + // success | ||
91 | + promise.mapdata = response.data; | ||
92 | + }, function (response) { | ||
93 | + // error | ||
94 | + $log.warn('Failed to retrieve map data: ' + url, | ||
95 | + response.status, response.data); | ||
96 | + }); | ||
97 | + | ||
98 | + cache.set(id, promise); | ||
99 | + | ||
100 | + } else { | ||
101 | + promise.meta.wasCached = true; | ||
102 | + } | ||
103 | + | ||
104 | + return promise; | ||
105 | + } | ||
106 | + | ||
107 | + // TODO: clean up implementation of projection... | ||
108 | + function setProjForView(path, topoData) { | ||
109 | + var dim = 1000; | ||
110 | + | ||
111 | + // start with unit scale, no translation.. | ||
112 | + geoMapProj.scale(1).translate([0, 0]); | ||
113 | + | ||
114 | + // figure out dimensions of map data.. | ||
115 | + var b = path.bounds(topoData), | ||
116 | + x1 = b[0][0], | ||
117 | + y1 = b[0][1], | ||
118 | + x2 = b[1][0], | ||
119 | + y2 = b[1][1], | ||
120 | + dx = x2 - x1, | ||
121 | + dy = y2 - y1, | ||
122 | + x = (x1 + x2) / 2, | ||
123 | + y = (y1 + y2) / 2; | ||
124 | + | ||
125 | + // size map to 95% of minimum dimension to fill space.. | ||
126 | + var s = .95 / Math.min(dx / dim, dy / dim); | ||
127 | + var t = [dim / 2 - s * x, dim / 2 - s * y]; | ||
128 | + | ||
129 | + // set new scale, translation on the projection.. | ||
130 | + geoMapProj.scale(s).translate(t); | ||
131 | + } | ||
132 | + | ||
133 | + | ||
134 | + return { | ||
135 | + clearCache: clearCache, | ||
136 | + fetchGeoMap: fetchGeoMap | ||
137 | + }; | ||
138 | + }]); | ||
139 | +}()); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
... | @@ -21,22 +21,14 @@ | ... | @@ -21,22 +21,14 @@ |
21 | */ | 21 | */ |
22 | 22 | ||
23 | /* | 23 | /* |
24 | - The Map Service caches GeoJSON maps, which can be loaded into the map | 24 | + The Map Service provides a simple API for loading geographical maps into |
25 | - layer of the Topology View. | 25 | + an SVG layer. For example, as a background to the Topology View. |
26 | 26 | ||
27 | - A GeoMap object can be fetched by ID. IDs that start with an asterisk | 27 | + e.g. var ok = MapService.loadMapInto(svgLayer, '*continental-us'); |
28 | - identify maps bundled with the GUI. IDs that do not start with an | ||
29 | - asterisk are assumed to be URLs to externally provided data (exact | ||
30 | - format to be decided). | ||
31 | 28 | ||
32 | - e.g. var geomap = MapService.fetchGeoMap('*continental-us'); | 29 | + The Map Service makes use of the GeoDataService to load the required data |
33 | - | 30 | + from the server. |
34 | - The GeoMap object encapsulates topology data (features), and the | 31 | +*/ |
35 | - D3 projection object. | ||
36 | - | ||
37 | - Note that, since the GeoMap instance is cached / shared, it should | ||
38 | - contain no state. | ||
39 | - */ | ||
40 | 32 | ||
41 | (function () { | 33 | (function () { |
42 | 'use strict'; | 34 | 'use strict'; | ... | ... |
... | @@ -44,6 +44,7 @@ | ... | @@ -44,6 +44,7 @@ |
44 | <script src="fw/svg/svg.js"></script> | 44 | <script src="fw/svg/svg.js"></script> |
45 | <script src="fw/svg/glyph.js"></script> | 45 | <script src="fw/svg/glyph.js"></script> |
46 | <script src="fw/svg/icon.js"></script> | 46 | <script src="fw/svg/icon.js"></script> |
47 | + <script src="fw/svg/geodata.js"></script> | ||
47 | <script src="fw/svg/map.js"></script> | 48 | <script src="fw/svg/map.js"></script> |
48 | <script src="fw/svg/zoom.js"></script> | 49 | <script src="fw/svg/zoom.js"></script> |
49 | 50 | ... | ... |
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
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 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- SVG -- GeoData Service - Unit Tests | ||
19 | + | ||
20 | + @author Simon Hunt | ||
21 | + */ | ||
22 | +describe('factory: fw/svg/geodata.js', function() { | ||
23 | + var $log, $httpBackend, fs, gds, promise; | ||
24 | + | ||
25 | + beforeEach(module('onosUtil', 'onosSvg')); | ||
26 | + | ||
27 | + beforeEach(inject(function (_$log_, _$httpBackend_, FnService, GeoDataService) { | ||
28 | + $log = _$log_; | ||
29 | + $httpBackend = _$httpBackend_; | ||
30 | + fs = FnService; | ||
31 | + gds = GeoDataService; | ||
32 | + gds.clearCache(); | ||
33 | + })); | ||
34 | + | ||
35 | + | ||
36 | + it('should define GeoDataService', function () { | ||
37 | + expect(gds).toBeDefined(); | ||
38 | + }); | ||
39 | + | ||
40 | + it('should define api functions', function () { | ||
41 | + expect(fs.areFunctions(gds, [ | ||
42 | + 'clearCache', 'fetchGeoMap' | ||
43 | + ])).toBeTruthy(); | ||
44 | + }); | ||
45 | + | ||
46 | + it('should return null when no parameters given', function () { | ||
47 | + promise = gds.fetchGeoMap(); | ||
48 | + expect(promise).toBeNull(); | ||
49 | + }); | ||
50 | + | ||
51 | + it('should augment the id of a bundled map', function () { | ||
52 | + var id = '*foo'; | ||
53 | + promise = gds.fetchGeoMap(id); | ||
54 | + expect(promise.meta).toBeDefined(); | ||
55 | + expect(promise.meta.id).toBe(id); | ||
56 | + expect(promise.meta.url).toBe('../data/map/foo.json'); | ||
57 | + }); | ||
58 | + | ||
59 | + it('should treat an external id as the url itself', function () { | ||
60 | + var id = 'some/path/to/foo'; | ||
61 | + promise = gds.fetchGeoMap(id); | ||
62 | + expect(promise.meta).toBeDefined(); | ||
63 | + expect(promise.meta.id).toBe(id); | ||
64 | + expect(promise.meta.url).toBe(id + '.json'); | ||
65 | + }); | ||
66 | + | ||
67 | + it('should cache the returned objects', function () { | ||
68 | + var id = 'foo'; | ||
69 | + promise = gds.fetchGeoMap(id); | ||
70 | + expect(promise).toBeDefined(); | ||
71 | + expect(promise.meta.wasCached).toBeFalsy(); | ||
72 | + expect(promise.tagged).toBeUndefined(); | ||
73 | + | ||
74 | + promise.tagged = 'I woz here'; | ||
75 | + | ||
76 | + promise = gds.fetchGeoMap(id); | ||
77 | + expect(promise).toBeDefined(); | ||
78 | + expect(promise.meta.wasCached).toBeTruthy(); | ||
79 | + expect(promise.tagged).toEqual('I woz here'); | ||
80 | + }); | ||
81 | + | ||
82 | + it('should clear the cache when asked', function () { | ||
83 | + var id = 'foo'; | ||
84 | + promise = gds.fetchGeoMap(id); | ||
85 | + expect(promise.meta.wasCached).toBeFalsy(); | ||
86 | + | ||
87 | + promise = gds.fetchGeoMap(id); | ||
88 | + expect(promise.meta.wasCached).toBeTruthy(); | ||
89 | + | ||
90 | + gds.clearCache(); | ||
91 | + promise = gds.fetchGeoMap(id); | ||
92 | + expect(promise.meta.wasCached).toBeFalsy(); | ||
93 | + }); | ||
94 | + | ||
95 | + | ||
96 | + it('should log a warning if data fails to load', function () { | ||
97 | + var id = 'foo'; | ||
98 | + $httpBackend.expectGET('foo.json').respond(404, 'Not found'); | ||
99 | + spyOn($log, 'warn'); | ||
100 | + | ||
101 | + promise = gds.fetchGeoMap(id); | ||
102 | + $httpBackend.flush(); | ||
103 | + expect(promise.mapdata).toBeUndefined(); | ||
104 | + expect($log.warn) | ||
105 | + .toHaveBeenCalledWith('Failed to retrieve map data: foo.json', | ||
106 | + 404, 'Not found'); | ||
107 | + }); | ||
108 | + | ||
109 | +}); |
... | @@ -82,76 +82,8 @@ describe('factory: fw/svg/map.js', function() { | ... | @@ -82,76 +82,8 @@ describe('factory: fw/svg/map.js', function() { |
82 | // TODO: figure out how to test this function as a black box test. | 82 | // TODO: figure out how to test this function as a black box test. |
83 | 83 | ||
84 | expect(obj).toBeTruthy(); | 84 | expect(obj).toBeTruthy(); |
85 | - debugger; | ||
86 | 85 | ||
87 | // todo: assert that paths are added to map layer element | 86 | // todo: assert that paths are added to map layer element |
88 | }); | 87 | }); |
89 | 88 | ||
90 | -/* | ||
91 | - | ||
92 | - | ||
93 | - | ||
94 | - it('should return null when no parameters given', function () { | ||
95 | - promise = ms.fetchGeoMap(); | ||
96 | - expect(promise).toBeNull(); | ||
97 | - }); | ||
98 | - | ||
99 | - it('should augment the id of a bundled map', function () { | ||
100 | - var id = '*foo'; | ||
101 | - promise = ms.fetchGeoMap(id); | ||
102 | - expect(promise.meta).toBeDefined(); | ||
103 | - expect(promise.meta.id).toBe(id); | ||
104 | - expect(promise.meta.url).toBe('../data/map/foo.json'); | ||
105 | - }); | ||
106 | - | ||
107 | - it('should treat an external id as the url itself', function () { | ||
108 | - var id = 'some/path/to/foo'; | ||
109 | - promise = ms.fetchGeoMap(id); | ||
110 | - expect(promise.meta).toBeDefined(); | ||
111 | - expect(promise.meta.id).toBe(id); | ||
112 | - expect(promise.meta.url).toBe(id + '.json'); | ||
113 | - }); | ||
114 | - | ||
115 | - it('should cache the returned objects', function () { | ||
116 | - var id = 'foo'; | ||
117 | - promise = ms.fetchGeoMap(id); | ||
118 | - expect(promise).toBeDefined(); | ||
119 | - expect(promise.meta.wasCached).toBeFalsy(); | ||
120 | - expect(promise.tagged).toBeUndefined(); | ||
121 | - | ||
122 | - promise.tagged = 'I woz here'; | ||
123 | - | ||
124 | - promise = ms.fetchGeoMap(id); | ||
125 | - expect(promise).toBeDefined(); | ||
126 | - expect(promise.meta.wasCached).toBeTruthy(); | ||
127 | - expect(promise.tagged).toEqual('I woz here'); | ||
128 | - }); | ||
129 | - | ||
130 | - it('should clear the cache when asked', function () { | ||
131 | - var id = 'foo'; | ||
132 | - promise = ms.fetchGeoMap(id); | ||
133 | - expect(promise.meta.wasCached).toBeFalsy(); | ||
134 | - | ||
135 | - promise = ms.fetchGeoMap(id); | ||
136 | - expect(promise.meta.wasCached).toBeTruthy(); | ||
137 | - | ||
138 | - ms.clearCache(); | ||
139 | - promise = ms.fetchGeoMap(id); | ||
140 | - expect(promise.meta.wasCached).toBeFalsy(); | ||
141 | - }); | ||
142 | - | ||
143 | - | ||
144 | - it('should log a warning if data fails to load', function () { | ||
145 | - $httpBackend.expectGET(mapurl).respond(404, 'Not found'); | ||
146 | - spyOn($log, 'warn'); | ||
147 | - | ||
148 | - promise = ms.fetchGeoMap(mapid); | ||
149 | - $httpBackend.flush(); | ||
150 | - expect(promise.mapdata).toBeUndefined(); | ||
151 | - expect($log.warn) | ||
152 | - .toHaveBeenCalledWith('Failed to retrieve map data: ' + mapurl, | ||
153 | - 404, 'Not found'); | ||
154 | - | ||
155 | - }); | ||
156 | -*/ | ||
157 | }); | 89 | }); | ... | ... |
-
Please register or login to post a comment