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

Change-Id: I937106c1c7c9e045230fce88dc7e5a5849b5cb3f
diff --git a/web/gui/src/main/webapp/app/fw/svg/geodata.js b/web/gui/src/main/webapp/app/fw/svg/geodata.js
index bc6b296..6c54bb4 100644
--- a/web/gui/src/main/webapp/app/fw/svg/geodata.js
+++ b/web/gui/src/main/webapp/app/fw/svg/geodata.js
@@ -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
diff --git a/web/gui/src/main/webapp/app/fw/svg/map.js b/web/gui/src/main/webapp/app/fw/svg/map.js
index 1faf6e2..f8d40f9 100644
--- a/web/gui/src/main/webapp/app/fw/svg/map.js
+++ b/web/gui/src/main/webapp/app/fw/svg/map.js
@@ -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
             };
         }]);