Simon Hunt
Committed by Brian O'Connor

GUI -- Topo View - Added ability to define different background maps of world regions.

Change-Id: I937106c1c7c9e045230fce88dc7e5a5849b5cb3f
...@@ -63,9 +63,109 @@ ...@@ -63,9 +63,109 @@
63 63
64 function getUrl(id) { 64 function getUrl(id) {
65 if (id[0] === '*') { 65 if (id[0] === '*') {
66 - return bundledUrlPrefix + id.slice(1) + '.json'; 66 + return bundledUrlPrefix + id.slice(1) + '.topojson';
67 } 67 }
68 - return id + '.json'; 68 + return id + '.topojson';
69 + }
70 +
71 +
72 + // start afresh...
73 + function clearCache() {
74 + cache = d3.map();
75 + }
76 +
77 + // returns a promise decorated with:
78 + // .meta -- id, url, and whether the data was cached
79 + // .topodata -- TopoJSON data (on response from server)
80 +
81 + function fetchTopoData(id) {
82 + if (!fs.isS(id)) {
83 + return null;
84 + }
85 + var url = getUrl(id),
86 + promise = cache.get(id);
87 +
88 + if (!promise) {
89 + // need to fetch the data, build the object,
90 + // cache it, and return it.
91 + promise = $http.get(url);
92 +
93 + promise.meta = {
94 + id: id,
95 + url: url,
96 + wasCached: false
97 + };
98 +
99 + promise.then(function (response) {
100 + // success
101 + promise.topodata = response.data;
102 + }, function (response) {
103 + // error
104 + $log.warn('Failed to retrieve map TopoJSON data: ' + url,
105 + response.status, response.data);
106 + });
107 +
108 + cache.set(id, promise);
109 +
110 + } else {
111 + promise.meta.wasCached = true;
112 + }
113 +
114 + return promise;
115 + }
116 +
117 + var defaultGenSettings = {
118 + objectTag: 'states',
119 + projection: d3.geo.mercator(),
120 + logicalSize: 1000,
121 + mapFillScale: .95
122 + };
123 +
124 + // converts given TopoJSON-format data into corresponding GeoJSON
125 + // data, and creates a path generator for that data.
126 + function createPathGenerator(topoData, opts) {
127 + var settings = angular.extend({}, defaultGenSettings, opts),
128 + topoObject = topoData.objects[settings.objectTag],
129 + geoData = topojson.feature(topoData, topoObject),
130 + proj = settings.projection,
131 + dim = settings.logicalSize,
132 + mfs = settings.mapFillScale,
133 + path = d3.geo.path().projection(proj);
134 +
135 + rescaleProjection(proj, mfs, dim, path, geoData);
136 +
137 + // return the results
138 + return {
139 + geodata: geoData,
140 + pathgen: path,
141 + settings: settings
142 + };
143 + }
144 +
145 + function rescaleProjection(proj, mfs, dim, path, geoData) {
146 + // adjust projection scale and translation to fill the view
147 + // with the map
148 +
149 + // start with unit scale, no translation..
150 + proj.scale(1).translate([0, 0]);
151 +
152 + // figure out dimensions of map data..
153 + var b = path.bounds(geoData),
154 + x1 = b[0][0],
155 + y1 = b[0][1],
156 + x2 = b[1][0],
157 + y2 = b[1][1],
158 + dx = x2 - x1,
159 + dy = y2 - y1,
160 + x = (x1 + x2) / 2,
161 + y = (y1 + y2) / 2;
162 +
163 + // size map to 95% of minimum dimension to fill space..
164 + var s = mfs / Math.min(dx / dim, dy / dim),
165 + t = [dim / 2 - s * x, dim / 2 - s * y];
166 +
167 + // set new scale, translation on the projection..
168 + proj.scale(s).translate(t);
69 } 169 }
70 170
71 angular.module('onosSvg') 171 angular.module('onosSvg')
...@@ -75,105 +175,12 @@ ...@@ -75,105 +175,12 @@
75 $http = _$http_; 175 $http = _$http_;
76 fs = _fs_; 176 fs = _fs_;
77 177
78 - // start afresh...
79 - function clearCache() {
80 - cache = d3.map();
81 - }
82 -
83 - // returns a promise decorated with:
84 - // .meta -- id, url, and whether the data was cached
85 - // .topodata -- TopoJSON data (on response from server)
86 -
87 - function fetchTopoData(id) {
88 - if (!fs.isS(id)) {
89 - return null;
90 - }
91 - var url = getUrl(id),
92 - promise = cache.get(id);
93 -
94 - if (!promise) {
95 - // need to fetch the data, build the object,
96 - // cache it, and return it.
97 - promise = $http.get(url);
98 -
99 - promise.meta = {
100 - id: id,
101 - url: url,
102 - wasCached: false
103 - };
104 -
105 - promise.then(function (response) {
106 - // success
107 - promise.topodata = response.data;
108 - }, function (response) {
109 - // error
110 - $log.warn('Failed to retrieve map TopoJSON data: ' + url,
111 - response.status, response.data);
112 - });
113 -
114 - cache.set(id, promise);
115 -
116 - } else {
117 - promise.meta.wasCached = true;
118 - }
119 -
120 - return promise;
121 - }
122 -
123 - var defaultGenSettings = {
124 - objectTag: 'states',
125 - projection: d3.geo.mercator(),
126 - logicalSize: 1000,
127 - mapFillScale: .95
128 - };
129 -
130 - // converts given TopoJSON-format data into corresponding GeoJSON
131 - // data, and creates a path generator for that data.
132 - function createPathGenerator(topoData, opts) {
133 - var settings = angular.extend({}, defaultGenSettings, opts),
134 - topoObject = topoData.objects[settings.objectTag],
135 - geoData = topojson.feature(topoData, topoObject),
136 - proj = settings.projection,
137 - dim = settings.logicalSize,
138 - mfs = settings.mapFillScale,
139 - path = d3.geo.path().projection(proj);
140 -
141 - // adjust projection scale and translation to fill the view
142 - // with the map
143 -
144 - // start with unit scale, no translation..
145 - proj.scale(1).translate([0, 0]);
146 -
147 - // figure out dimensions of map data..
148 - var b = path.bounds(geoData),
149 - x1 = b[0][0],
150 - y1 = b[0][1],
151 - x2 = b[1][0],
152 - y2 = b[1][1],
153 - dx = x2 - x1,
154 - dy = y2 - y1,
155 - x = (x1 + x2) / 2,
156 - y = (y1 + y2) / 2;
157 -
158 - // size map to 95% of minimum dimension to fill space..
159 - var s = mfs / Math.min(dx / dim, dy / dim),
160 - t = [dim / 2 - s * x, dim / 2 - s * y];
161 -
162 - // set new scale, translation on the projection..
163 - proj.scale(s).translate(t);
164 -
165 - // return the results
166 - return {
167 - geodata: geoData,
168 - pathgen: path,
169 - settings: settings
170 - };
171 - }
172 178
173 return { 179 return {
174 clearCache: clearCache, 180 clearCache: clearCache,
175 fetchTopoData: fetchTopoData, 181 fetchTopoData: fetchTopoData,
176 - createPathGenerator: createPathGenerator 182 + createPathGenerator: createPathGenerator,
183 + rescaleProjection: rescaleProjection
177 }; 184 };
178 }]); 185 }]);
179 }()); 186 }());
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -37,6 +37,11 @@ ...@@ -37,6 +37,11 @@
37 // injected references 37 // injected references
38 var $log, $q, fs, gds; 38 var $log, $q, fs, gds;
39 39
40 + // NOTE: This method assumes the datafile has exactly the map data
41 + // that you want to load; for example id="*continental_us"
42 + // mapping to ~/data/map/continental_us.topojson contains
43 + // exactly the paths for the continental US.
44 +
40 function loadMapInto(mapLayer, id, opts) { 45 function loadMapInto(mapLayer, id, opts) {
41 var promise = gds.fetchTopoData(id), 46 var promise = gds.fetchTopoData(id),
42 deferredProjection = $q.defer(); 47 deferredProjection = $q.defer();
...@@ -60,6 +65,52 @@ ...@@ -60,6 +65,52 @@
60 return deferredProjection.promise; 65 return deferredProjection.promise;
61 } 66 }
62 67
68 + // ---
69 +
70 + // NOTE: This method uses the countries.topojson data file, and then
71 + // filters the results based on the supplied options.
72 + // Usage:
73 + // promise = loadMapRegionInto(svgGroup, {
74 + // countryFilter: function (country) {
75 + // return country.properties.continent === 'South America';
76 + // }
77 + // });
78 +
79 + function loadMapRegionInto(mapLayer, filterOpts) {
80 + var promise = gds.fetchTopoData("*countries"),
81 + deferredProjection = $q.defer();
82 +
83 + if (!promise) {
84 + $log.warn('Failed to load countries TopoJSON data');
85 + return false;
86 + }
87 +
88 + promise.then(function () {
89 + var width = 1000,
90 + height = 1000,
91 + proj = d3.geo.mercator().translate([width/2, height/2]),
92 + pathGen = d3.geo.path().projection(proj),
93 + data = promise.topodata,
94 + features = topojson.feature(data, data.objects.countries).features,
95 + country = features.filter(filterOpts.countryFilter),
96 + countryFeature = {
97 + type: 'FeatureCollection',
98 + features: country
99 + },
100 + path = d3.geo.path().projection(proj);
101 +
102 + gds.rescaleProjection(proj, 0.95, 1000, path, countryFeature);
103 +
104 + deferredProjection.resolve(proj);
105 +
106 + mapLayer.selectAll('path.country')
107 + .data([countryFeature])
108 + .enter()
109 + .append('path').classed('country', true)
110 + .attr('d', pathGen);
111 + });
112 + return deferredProjection.promise;
113 + }
63 114
64 angular.module('onosSvg') 115 angular.module('onosSvg')
65 .factory('MapService', ['$log', '$q', 'FnService', 'GeoDataService', 116 .factory('MapService', ['$log', '$q', 'FnService', 'GeoDataService',
...@@ -70,6 +121,7 @@ ...@@ -70,6 +121,7 @@
70 gds = _gds_; 121 gds = _gds_;
71 122
72 return { 123 return {
124 + loadMapRegionInto: loadMapRegionInto,
73 loadMapInto: loadMapInto 125 loadMapInto: loadMapInto
74 }; 126 };
75 }]); 127 }]);
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
65 } 65 }
66 66
67 .light #ov-topo svg #topo-map { 67 .light #ov-topo svg #topo-map {
68 - stroke: #eee; 68 + stroke: #ddd;
69 } 69 }
70 .dark #ov-topo svg #topo-map { 70 .dark #ov-topo svg #topo-map {
71 stroke: #444; 71 stroke: #444;
......
...@@ -276,10 +276,57 @@ ...@@ -276,10 +276,57 @@
276 sus.visible(noDevsLayer, b); 276 sus.visible(noDevsLayer, b);
277 } 277 }
278 278
279 - function setUpMap() { 279 +
280 + var countryFilters = {
281 + world: function (c) {
282 + return c.properties.continent !== 'Antarctica';
283 + },
284 +
285 + // NOTE: for "usa" we are using our hand-crafted topojson file
286 +
287 + s_america: function (c) {
288 + return c.properties.continent === 'South America';
289 + },
290 +
291 + japan: function (c) {
292 + return c.properties.geounit === 'Japan';
293 + },
294 +
295 + europe: function (c) {
296 + return c.properties.continent === 'Europe';
297 + },
298 +
299 + italy: function (c) {
300 + return c.properties.geounit === 'Italy';
301 + },
302 +
303 + uk: function (c) {
304 + // technically, Ireland is not part of the United Kingdom,
305 + // but the map looks weird without it showing.
306 + return c.properties.adm0_a3 === 'GBR' ||
307 + c.properties.adm0_a3 === 'IRL';
308 + }
309 + };
310 +
311 +
312 + function setUpMap($loc) {
313 + var s1 = $loc.search().mapid,
314 + s2 = ps.getPrefs('topo_mapid'),
315 + mapId = s1 || (s2 && s2.id) || 'world',
316 + promise,
317 + cfilter,
318 + opts;
319 +
280 mapG = zoomLayer.append('g').attr('id', 'topo-map'); 320 mapG = zoomLayer.append('g').attr('id', 'topo-map');
281 - // returns a promise for the projection... 321 + if (mapId === 'usa') {
282 - return ms.loadMapInto(mapG, '*continental_us'); 322 + promise = ms.loadMapInto(mapG, '*continental_us');
323 + } else {
324 + ps.setPrefs('topo_mapid', {id:mapId});
325 + cfilter = countryFilters[mapId] || countryFilters.world;
326 + opts = { countryFilter: cfilter };
327 + promise = ms.loadMapRegionInto(mapG, opts);
328 + }
329 + return promise;
283 } 330 }
284 331
285 function opacifyMap(b) { 332 function opacifyMap(b) {
...@@ -405,7 +452,7 @@ ...@@ -405,7 +452,7 @@
405 setUpDefs(); 452 setUpDefs();
406 setUpZoom(); 453 setUpZoom();
407 setUpNoDevs(); 454 setUpNoDevs();
408 - setUpMap().then( 455 + setUpMap($loc).then(
409 function (proj) { 456 function (proj) {
410 var z = ps.getPrefs('topo_zoom') || {tx:0, ty:0, sc:1}; 457 var z = ps.getPrefs('topo_zoom') || {tx:0, ty:0, sc:1};
411 zoomer.panZoom([z.tx, z.ty], z.sc); 458 zoomer.panZoom([z.tx, z.ty], z.sc);
...@@ -416,6 +463,7 @@ ...@@ -416,6 +463,7 @@
416 flash.enable(false); 463 flash.enable(false);
417 toggleMap(prefsState.bg); 464 toggleMap(prefsState.bg);
418 flash.enable(true); 465 flash.enable(true);
466 + // TODO: move tes.start() to here ????
419 } 467 }
420 ); 468 );
421 setUpSprites($loc, tspr); 469 setUpSprites($loc, tspr);
......
This diff could not be displayed because it is too large.