Simon Hunt

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

Change-Id: I937106c1c7c9e045230fce88dc7e5a5849b5cb3f
......@@ -63,9 +63,109 @@
function getUrl(id) {
if (id[0] === '*') {
return bundledUrlPrefix + id.slice(1) + '.json';
return bundledUrlPrefix + id.slice(1) + '.topojson';
}
return id + '.json';
return id + '.topojson';
}
// start afresh...
function clearCache() {
cache = d3.map();
}
// returns a promise decorated with:
// .meta -- id, url, and whether the data was cached
// .topodata -- TopoJSON data (on response from server)
function fetchTopoData(id) {
if (!fs.isS(id)) {
return null;
}
var url = getUrl(id),
promise = cache.get(id);
if (!promise) {
// need to fetch the data, build the object,
// cache it, and return it.
promise = $http.get(url);
promise.meta = {
id: id,
url: url,
wasCached: false
};
promise.then(function (response) {
// success
promise.topodata = response.data;
}, function (response) {
// error
$log.warn('Failed to retrieve map TopoJSON data: ' + url,
response.status, response.data);
});
cache.set(id, promise);
} else {
promise.meta.wasCached = true;
}
return promise;
}
var defaultGenSettings = {
objectTag: 'states',
projection: d3.geo.mercator(),
logicalSize: 1000,
mapFillScale: .95
};
// converts given TopoJSON-format data into corresponding GeoJSON
// data, and creates a path generator for that data.
function createPathGenerator(topoData, opts) {
var settings = angular.extend({}, defaultGenSettings, opts),
topoObject = topoData.objects[settings.objectTag],
geoData = topojson.feature(topoData, topoObject),
proj = settings.projection,
dim = settings.logicalSize,
mfs = settings.mapFillScale,
path = d3.geo.path().projection(proj);
rescaleProjection(proj, mfs, dim, path, geoData);
// return the results
return {
geodata: geoData,
pathgen: path,
settings: settings
};
}
function rescaleProjection(proj, mfs, dim, path, geoData) {
// adjust projection scale and translation to fill the view
// with the map
// start with unit scale, no translation..
proj.scale(1).translate([0, 0]);
// figure out dimensions of map data..
var b = path.bounds(geoData),
x1 = b[0][0],
y1 = b[0][1],
x2 = b[1][0],
y2 = b[1][1],
dx = x2 - x1,
dy = y2 - y1,
x = (x1 + x2) / 2,
y = (y1 + y2) / 2;
// size map to 95% of minimum dimension to fill space..
var s = mfs / Math.min(dx / dim, dy / dim),
t = [dim / 2 - s * x, dim / 2 - s * y];
// set new scale, translation on the projection..
proj.scale(s).translate(t);
}
angular.module('onosSvg')
......@@ -75,105 +175,12 @@
$http = _$http_;
fs = _fs_;
// start afresh...
function clearCache() {
cache = d3.map();
}
// returns a promise decorated with:
// .meta -- id, url, and whether the data was cached
// .topodata -- TopoJSON data (on response from server)
function fetchTopoData(id) {
if (!fs.isS(id)) {
return null;
}
var url = getUrl(id),
promise = cache.get(id);
if (!promise) {
// need to fetch the data, build the object,
// cache it, and return it.
promise = $http.get(url);
promise.meta = {
id: id,
url: url,
wasCached: false
};
promise.then(function (response) {
// success
promise.topodata = response.data;
}, function (response) {
// error
$log.warn('Failed to retrieve map TopoJSON data: ' + url,
response.status, response.data);
});
cache.set(id, promise);
} else {
promise.meta.wasCached = true;
}
return promise;
}
var defaultGenSettings = {
objectTag: 'states',
projection: d3.geo.mercator(),
logicalSize: 1000,
mapFillScale: .95
};
// converts given TopoJSON-format data into corresponding GeoJSON
// data, and creates a path generator for that data.
function createPathGenerator(topoData, opts) {
var settings = angular.extend({}, defaultGenSettings, opts),
topoObject = topoData.objects[settings.objectTag],
geoData = topojson.feature(topoData, topoObject),
proj = settings.projection,
dim = settings.logicalSize,
mfs = settings.mapFillScale,
path = d3.geo.path().projection(proj);
// adjust projection scale and translation to fill the view
// with the map
// start with unit scale, no translation..
proj.scale(1).translate([0, 0]);
// figure out dimensions of map data..
var b = path.bounds(geoData),
x1 = b[0][0],
y1 = b[0][1],
x2 = b[1][0],
y2 = b[1][1],
dx = x2 - x1,
dy = y2 - y1,
x = (x1 + x2) / 2,
y = (y1 + y2) / 2;
// size map to 95% of minimum dimension to fill space..
var s = mfs / Math.min(dx / dim, dy / dim),
t = [dim / 2 - s * x, dim / 2 - s * y];
// set new scale, translation on the projection..
proj.scale(s).translate(t);
// return the results
return {
geodata: geoData,
pathgen: path,
settings: settings
};
}
return {
clearCache: clearCache,
fetchTopoData: fetchTopoData,
createPathGenerator: createPathGenerator
createPathGenerator: createPathGenerator,
rescaleProjection: rescaleProjection
};
}]);
}());
\ No newline at end of file
......
......@@ -37,6 +37,11 @@
// injected references
var $log, $q, fs, gds;
// NOTE: This method assumes the datafile has exactly the map data
// that you want to load; for example id="*continental_us"
// mapping to ~/data/map/continental_us.topojson contains
// exactly the paths for the continental US.
function loadMapInto(mapLayer, id, opts) {
var promise = gds.fetchTopoData(id),
deferredProjection = $q.defer();
......@@ -60,6 +65,52 @@
return deferredProjection.promise;
}
// ---
// NOTE: This method uses the countries.topojson data file, and then
// filters the results based on the supplied options.
// Usage:
// promise = loadMapRegionInto(svgGroup, {
// countryFilter: function (country) {
// return country.properties.continent === 'South America';
// }
// });
function loadMapRegionInto(mapLayer, filterOpts) {
var promise = gds.fetchTopoData("*countries"),
deferredProjection = $q.defer();
if (!promise) {
$log.warn('Failed to load countries TopoJSON data');
return false;
}
promise.then(function () {
var width = 1000,
height = 1000,
proj = d3.geo.mercator().translate([width/2, height/2]),
pathGen = d3.geo.path().projection(proj),
data = promise.topodata,
features = topojson.feature(data, data.objects.countries).features,
country = features.filter(filterOpts.countryFilter),
countryFeature = {
type: 'FeatureCollection',
features: country
},
path = d3.geo.path().projection(proj);
gds.rescaleProjection(proj, 0.95, 1000, path, countryFeature);
deferredProjection.resolve(proj);
mapLayer.selectAll('path.country')
.data([countryFeature])
.enter()
.append('path').classed('country', true)
.attr('d', pathGen);
});
return deferredProjection.promise;
}
angular.module('onosSvg')
.factory('MapService', ['$log', '$q', 'FnService', 'GeoDataService',
......@@ -70,6 +121,7 @@
gds = _gds_;
return {
loadMapRegionInto: loadMapRegionInto,
loadMapInto: loadMapInto
};
}]);
......
......@@ -65,7 +65,7 @@
}
.light #ov-topo svg #topo-map {
stroke: #eee;
stroke: #ddd;
}
.dark #ov-topo svg #topo-map {
stroke: #444;
......
......@@ -276,10 +276,57 @@
sus.visible(noDevsLayer, b);
}
function setUpMap() {
var countryFilters = {
world: function (c) {
return c.properties.continent !== 'Antarctica';
},
// NOTE: for "usa" we are using our hand-crafted topojson file
s_america: function (c) {
return c.properties.continent === 'South America';
},
japan: function (c) {
return c.properties.geounit === 'Japan';
},
europe: function (c) {
return c.properties.continent === 'Europe';
},
italy: function (c) {
return c.properties.geounit === 'Italy';
},
uk: function (c) {
// technically, Ireland is not part of the United Kingdom,
// but the map looks weird without it showing.
return c.properties.adm0_a3 === 'GBR' ||
c.properties.adm0_a3 === 'IRL';
}
};
function setUpMap($loc) {
var s1 = $loc.search().mapid,
s2 = ps.getPrefs('topo_mapid'),
mapId = s1 || (s2 && s2.id) || 'world',
promise,
cfilter,
opts;
mapG = zoomLayer.append('g').attr('id', 'topo-map');
// returns a promise for the projection...
return ms.loadMapInto(mapG, '*continental_us');
if (mapId === 'usa') {
promise = ms.loadMapInto(mapG, '*continental_us');
} else {
ps.setPrefs('topo_mapid', {id:mapId});
cfilter = countryFilters[mapId] || countryFilters.world;
opts = { countryFilter: cfilter };
promise = ms.loadMapRegionInto(mapG, opts);
}
return promise;
}
function opacifyMap(b) {
......@@ -405,7 +452,7 @@
setUpDefs();
setUpZoom();
setUpNoDevs();
setUpMap().then(
setUpMap($loc).then(
function (proj) {
var z = ps.getPrefs('topo_zoom') || {tx:0, ty:0, sc:1};
zoomer.panZoom([z.tx, z.ty], z.sc);
......@@ -416,6 +463,7 @@
flash.enable(false);
toggleMap(prefsState.bg);
flash.enable(true);
// TODO: move tes.start() to here ????
}
);
setUpSprites($loc, tspr);
......
This diff could not be displayed because it is too large.