GUI -- Further work on MapService and GeoDataService. Still WIP.

Change-Id: I92e826cc15cc1a07238cc4b4eac20583260a3c84
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 244507a..4d3c4a2 100644
--- a/web/gui/src/main/webapp/app/fw/svg/geodata.js
+++ b/web/gui/src/main/webapp/app/fw/svg/geodata.js
@@ -21,18 +21,36 @@
  */
 
 /*
- The GeoData Service caches GeoJSON map data, and provides supporting
- projections for mapping into SVG layers.
+ The GeoData Service facilitates the fetching and caching of TopoJSON data
+ from the server, as well as providing a way of creating a path generator
+ for that data, to be used to render the map in an SVG layer.
 
- A GeoMap object can be fetched by ID. IDs that start with an asterisk
+ A TopoData object can be fetched by ID. IDs that start with an asterisk
  identify maps bundled with the GUI. IDs that do not start with an
- asterisk are assumed to be URLs to externally provided data (exact
- format to be decided).
+ asterisk are assumed to be URLs to externally provided data.
 
- e.g.  var geomap = GeoDataService.fetchGeoMap('*continental-us');
+     var topodata = GeoDataService.fetchTopoData('*continental-us');
 
- Note that, since the GeoMap instance is cached / shared, it should
- contain no state.
+ The path generator can then be created for that data-set:
+
+     var gen = GeoDataService.createPathGenerator(topodata, opts);
+
+ opts is an optional argument that allows the override of default settings:
+     {
+         objectTag: 'states',
+         projection: d3.geo.mercator(),
+         logicalSize: 1000,
+         mapFillScale: .95
+     };
+
+ The returned object (gen) comprises transformed data (TopoJSON -> GeoJSON),
+ the D3 path generator function, and the settings used ...
+
+    {
+        geodata:  { ... },
+        pathgen:  function (...) { ... },
+        settings: { ... }
+    }
  */
 
 (function () {
@@ -66,9 +84,9 @@
 
             // returns a promise decorated with:
             //   .meta -- id, url, and whether the data was cached
-            //   .mapdata -- geojson data (on response from server)
+            //   .topodata -- TopoJSON data (on response from server)
 
-            function fetchGeoMap(id) {
+            function fetchTopoData(id) {
                 if (!fs.isS(id)) {
                     return null;
                 }
@@ -88,10 +106,10 @@
 
                     promise.then(function (response) {
                         // success
-                        promise.mapdata = response.data;
+                        promise.topodata = response.data;
                     }, function (response) {
                         // error
-                        $log.warn('Failed to retrieve map data: ' + url,
+                        $log.warn('Failed to retrieve map TopoJSON data: ' + url,
                             response.status, response.data);
                     });
 
@@ -104,15 +122,32 @@
                 return promise;
             }
 
-            // TODO: clean up implementation of projection...
-            function setProjForView(path, topoData) {
-                var dim = 1000;
+            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 = $.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..
-                geoMapProj.scale(1).translate([0, 0]);
+                proj.scale(1).translate([0, 0]);
 
                 // figure out dimensions of map data..
-                var b = path.bounds(topoData),
+                var b = path.bounds(geoData),
                     x1 = b[0][0],
                     y1 = b[0][1],
                     x2 = b[1][0],
@@ -123,17 +158,24 @@
                     y = (y1 + y2) / 2;
 
                 // size map to 95% of minimum dimension to fill space..
-                var s = .95 / Math.min(dx / dim, dy / dim);
-                var t = [dim / 2 - s * x, dim / 2 - s * y];
+                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..
-                geoMapProj.scale(s).translate(t);
-            }
+                proj.scale(s).translate(t);
 
+                // return the results
+                return {
+                    geodata: geoData,
+                    pathgen: path,
+                    settings: settings
+                };
+            }
 
             return {
                 clearCache: clearCache,
-                fetchGeoMap: fetchGeoMap
+                fetchTopoData: fetchTopoData,
+                createPathGenerator: createPathGenerator
             };
         }]);
 }());
\ No newline at end of file