Simon Hunt

GUI -- Split MapService into GeoDataService and MapService. WIP.

Change-Id: Ibfe5b35ecdfaaf39b9d48abd29d0a44327dec130
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 });
......