Topo2 Fixed device positions

Change-Id: I10e61981000b427ff1ebf6ae0c35bfb2cdbb9c4b
diff --git a/tools/test/topos/regions-topo-2 b/tools/test/topos/regions-topo-2
index 297bc65..4180c37 100755
--- a/tools/test/topos/regions-topo-2
+++ b/tools/test/topos/regions-topo-2
@@ -64,4 +64,13 @@
 layout-add l3 r3 l2
 
 layouts
-EOF
\ No newline at end of file
+EOF
+
+
+onos ${host} <<-EOF
+
+log:set DEBUG org.onosproject.ui.impl.topo.Topo2ViewMessageHandler
+log:set DEBUG org.onosproject.ui.impl.topo.Topo2Jsonifier
+log:set DEBUG org.onosproject.ui.impl.UiWebSocket
+log:list
+EOF
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
index 8ff3bb6..11aef1d 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
@@ -36,6 +36,7 @@
 import org.onosproject.net.link.LinkService;
 import org.onosproject.net.statistic.StatisticService;
 import org.onosproject.net.topology.TopologyService;
+import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.model.topo.UiClusterMember;
 import org.onosproject.ui.model.topo.UiDevice;
 import org.onosproject.ui.model.topo.UiHost;
@@ -482,4 +483,18 @@
 
         return splitList;
     }
+
+    /**
+     * Stores the memento for an element.
+     * This method assumes the payload has an id String, memento ObjectNode
+     *
+     * @param payload event payload
+     */
+    void updateMeta(ObjectNode payload) {
+
+        String id = JsonUtils.string(payload, "id");
+        metaUi.put(id, JsonUtils.node(payload, "memento"));
+
+        log.debug("Storing metadata for {}", id);
+    }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
index 3513b79..4aa6fc9 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
@@ -60,6 +60,7 @@
     private static final String START = "topo2Start";
     private static final String NAV_REGION = "topo2navRegion";
     private static final String STOP = "topo2Stop";
+    private static final String UPDATE_META2 = "updateMeta2";
 
     // === Outbound event identifiers
     private static final String ALL_INSTANCES = "topo2AllInstances";
@@ -87,7 +88,8 @@
         return ImmutableSet.of(
                 new Topo2Start(),
                 new Topo2NavRegion(),
-                new Topo2Stop()
+                new Topo2Stop(),
+                new Topo2UpdateMeta()
         );
     }
 
@@ -196,4 +198,15 @@
         }
     }
 
+    private final class Topo2UpdateMeta extends RequestHandler {
+        private Topo2UpdateMeta() {
+            super(UPDATE_META2);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            t2json.updateMeta(payload);
+        }
+    }
+
 }
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.js b/web/gui/src/main/webapp/app/view/topo2/topo2.js
index 839a736..ef76ac1 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.js
@@ -25,7 +25,7 @@
 
     // references to injected services
     var $scope, $log, fs, mast, ks, zs,
-        gs, sus, ps, t2es, t2fs, t2is, t2bcs, t2kcs, t2ms;
+        gs, sus, ps, t2es, t2fs, t2is, t2bcs, t2kcs, t2ms, t2mcs;
 
     // DOM elements
     var ovtopo2, svg, defs, zoomLayer, forceG;
@@ -88,12 +88,13 @@
         'WebSocketService', 'PrefsService', 'ThemeService',
         'Topo2EventService', 'Topo2ForceService', 'Topo2InstanceService',
         'Topo2BreadcrumbService', 'Topo2KeyCommandService', 'Topo2MapService',
+        'Topo2MapConfigService',
 
         function (_$scope_, _$log_, _$loc_,
             _fs_, _mast_, _ks_, _zs_,
             _gs_, _ms_, _sus_, _flash_,
             _wss_, _ps_, _th_,
-            _t2es_, _t2fs_, _t2is_, _t2bcs_, _t2kcs_, _t2ms_) {
+            _t2es_, _t2fs_, _t2is_, _t2bcs_, _t2kcs_, _t2ms_, _t2mcs_) {
 
             var params = _$loc_.search(),
                 dim,
@@ -127,6 +128,7 @@
             t2bcs = _t2bcs_;
             t2kcs = _t2kcs_;
             t2ms = _t2ms_;
+            t2mcs = _t2mcs_;
 
             // capture selected intent parameters (if they are set in the
             //  query string) so that the traffic overlay can highlight
@@ -177,25 +179,25 @@
                 function (proj) {
                     var z = ps.getPrefs('topo_zoom', { tx: 0, ty: 0, sc: 1 });
                     zoomer.panZoom([z.tx, z.ty], z.sc);
+
+                    t2mcs.projection(proj);
                     $log.debug('** Zoom restored:', z);
                     $log.debug('** We installed the projection:', proj);
+
+                    // Now the map has load and we have a projection we can
+                    // get the info from the server
+                    t2es.start();
+
                 }
             );
 
             // initialize the force layout, ready to render the topology
             forceG = zoomLayer.append('g').attr('id', 'topo-force');
+
             t2fs.init(svg, forceG, uplink, dim);
             t2bcs.init();
             t2kcs.init(t2fs);
 
-
-            // =-=-=-=-=-=-=-=-
-            // TODO: in future, we will load background map data
-            //  asynchronously (hence the promise) and then chain off
-            //  there to send the topo2start event to the server.
-            // For now, we'll send the event inline...
-            t2es.start();
-
             t2is.initInst({ showMastership: t2fs.showMastership });
 
             // === ORIGINAL CODE ===
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
index c23bc7e..a02a844 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -22,7 +22,7 @@
 (function () {
     'use strict';
 
-    var $log, sus, t2rs, t2d3, t2vs, t2ss;
+    var $log, wss, sus, t2rs, t2d3, t2vs, t2ss;
 
     var uplink, linkG, linkLabelG, nodeG;
     var link, node;
@@ -142,7 +142,7 @@
         // once we've finished moving, pin the node in position
         d.fixed = true;
         d3.select(this).classed('fixed', true);
-        // TODO: sendUpdateMeta(d);
+        sendUpdateMeta(d);
         t2ss.clickConsumed(true);
     }
 
@@ -153,6 +153,31 @@
         return !nodeLock && !zoomingOrPanning(ev);
     }
 
+    function sendUpdateMeta(d, clearPos) {
+        var metaUi = {},
+            ll;
+
+        // if we are not clearing the position data (unpinning),
+        // attach the x, y, (and equivalent longitude, latitude)...
+        if (!clearPos) {
+            ll = d.lngLatFromCoord([d.x, d.y]);
+            metaUi = {
+                x: d.x,
+                y: d.y,
+                equivLoc: {
+                    lng: ll[0],
+                    lat: ll[1]
+                }
+            };
+        }
+        d.metaUi = metaUi;
+        wss.sendEvent('updateMeta2', {
+            id: d.get('id'),
+            class: d.get('class'),
+            memento: metaUi
+        });
+    }
+
     // predicate that indicates when clicking is active
     function clickEnabled() {
         return true;
@@ -396,12 +421,13 @@
     angular.module('ovTopo2')
     .factory('Topo2LayoutService',
         [
-            '$log', 'SvgUtilService', 'Topo2RegionService',
+            '$log', 'WebSocketService', 'SvgUtilService', 'Topo2RegionService',
             'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService',
 
-            function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_, _t2ss_) {
+            function (_$log_, _wss_, _sus_, _t2rs_, _t2d3_, _t2vs_, _t2ss_) {
 
                 $log = _$log_;
+                wss = _wss_;
                 t2rs = _t2rs_;
                 t2d3 = _t2d3_;
                 t2vs = _t2vs_;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Map.js b/web/gui/src/main/webapp/app/view/topo2/topo2Map.js
index 7176a76..4ff2162 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Map.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Map.js
@@ -55,7 +55,7 @@
 
         if (mapFilePath === '*countries') {
             cfilter = countryFilters[mapId] || countryFilters.uk;
-            loadMap = ms.loadMapRegionInto
+            loadMap = ms.loadMapRegionInto;
         }
 
         promise = loadMap(mapG, mapFilePath, mapId, {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2MapConfig.js b/web/gui/src/main/webapp/app/view/topo2/topo2MapConfig.js
new file mode 100644
index 0000000..ffb7f86
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2MapConfig.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Topology Layout Module.
+ Module that contains the d3.force.layout logic
+ */
+
+(function () {
+    'use strict';
+
+    // Injected refs
+    var $log;
+
+    // Internal State
+    var proj;
+
+    function projection(x) {
+        if (x) {
+            proj = x;
+            $log.debug("Set the projection");
+        }
+        return proj;
+    }
+
+    angular.module('ovTopo2')
+    .factory('Topo2MapConfigService',
+        ['$log',
+            function (_$log_) {
+
+                $log = _$log_;
+
+                return {
+                    projection: projection
+                };
+            }
+        ]
+    );
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
index 3f781c4..d4917a3 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
@@ -22,7 +22,7 @@
 (function () {
     'use strict';
 
-    var randomService, ps, sus, is, ts;
+    var randomService, ps, sus, is, ts, t2mcs;
     var fn;
 
     // Internal state;
@@ -55,8 +55,7 @@
     }
 
     function positionNode(node, forUpdate) {
-
-        var meta = node.metaUi,
+        var meta = node.get('metaUi'),
             x = meta && meta.x,
             y = meta && meta.y,
             dim = [800, 600],
@@ -64,7 +63,6 @@
 
         // If the device contains explicit LONG/LAT data, use that to position
         if (setLongLat(node)) {
-            // Indicate we want to update cached meta data...
             return true;
         }
 
@@ -103,13 +101,16 @@
         }
 
         function getDevice(cp) {
-            // console.log(cp);
-            // var d = lu[cp.device];
-            // return d || rand();
             return rand();
         }
 
         xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
+
+        if (node.class === 'sub-region') {
+            xy = rand();
+            node.x = node.px = xy.x;
+            node.y = node.py = xy.y;
+        }
         angular.extend(node, xy);
     }
 
@@ -118,7 +119,7 @@
             coord;
 
         if (loc && loc.type === 'lnglat') {
-            coord = [0, 0];
+            coord = coordFromLngLat(loc);
             node.fixed = true;
             node.px = node.x = coord[0];
             node.py = node.y = coord[1];
@@ -126,11 +127,16 @@
         }
     }
 
+    function coordFromLngLat(loc) {
+        var p = t2mcs.projection();
+        return p ? p.invert([loc.lng, loc.lat]) : [0, 0];
+    }
+
     angular.module('ovTopo2')
     .factory('Topo2NodeModel',
         ['Topo2Model', 'FnService', 'RandomService', 'Topo2PrefsService',
-        'SvgUtilService', 'IconService', 'ThemeService',
-        function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_) {
+        'SvgUtilService', 'IconService', 'ThemeService', 'Topo2MapConfigService',
+        function (Model, _fn_, _RandomService_, _ps_, _sus_, _is_, _ts_, _t2mcs_) {
 
             randomService = _RandomService_;
             ts = _ts_;
@@ -138,11 +144,18 @@
             ps = _ps_;
             sus = _sus_;
             is = _is_;
+            t2mcs = _t2mcs_;
 
             return Model.extend({
                 initialize: function () {
                     this.node = this.createNode();
                 },
+                createNode: function () {
+                    this.set('class', this.nodeType);
+                    this.set('svgClass', this.svgClassName());
+                    positionNode(this);
+                    return this;
+                },
                 setUpEvents: function () {
                     var _this = this;
                     angular.forEach(this.events, function (handler, key) {
@@ -221,6 +234,10 @@
                         }
                     );
                 },
+                lngLatFromCoord: function (coord) {
+                    var p = t2mcs.projection();
+                    return p ? p.invert(coord) : [0, 0];
+                },
                 update: function () {
                     this.updateLabel();
                 },
@@ -236,15 +253,6 @@
                         .transition()
                         .attr(this.labelBox(devIconDim, labelWidth));
                 },
-                createNode: function () {
-                    var node = angular.extend({}, this.attributes);
-
-                    // Augment as needed...
-                    node.class = this.nodeType;
-                    node.svgClass = this.svgClassName();
-                    positionNode(node);
-                    return node;
-                },
                 onEnter: function (el) {
                     this.el = d3.select(el);
                     this.render();
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 5acc0ca..6ef80d3 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -141,6 +141,7 @@
     <script src="app/view/topo2/topo2Link.js"></script>
     <script src="app/view/topo2/topo2Map.js"></script>
     <script src="app/view/topo2/topo2MapCountryFilters.js"></script>
+    <script src="app/view/topo2/topo2MapConfig.js"></script>
     <script src="app/view/topo2/topo2MapDialog.js"></script>
     <script src="app/view/topo2/topo2Model.js"></script>
     <script src="app/view/topo2/topo2NodeModel.js"></script>