Committed by
Brian O'Connor
GUI -- Topo View - Added ability to define different background maps of world regions.
Change-Id: I937106c1c7c9e045230fce88dc7e5a5849b5cb3f
Showing
6 changed files
with
209 additions
and
102 deletions
| ... | @@ -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 | }]); | ... | ... |
| ... | @@ -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.
-
Please register or login to post a comment