ONOS-2470: Implement "Reset Node Locations" function in topology view.
- also cleaned up some Long/Lat code.
- Note also that metadata from client is structured so 'lng/lat' properties
    (from repositioned node) are wrapped in 'equivLoc' object.

Change-Id: I5afc53d26ef56fc0932f8650e8f7df79b36c3947
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 49d5155..b700a54 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -357,18 +357,24 @@
             return;
         }
 
-        String slat = annotations.value(AnnotationKeys.LATITUDE);
         String slng = annotations.value(AnnotationKeys.LONGITUDE);
+        String slat = annotations.value(AnnotationKeys.LATITUDE);
+        boolean haveLng = slng != null && !slng.isEmpty();
+        boolean haveLat = slat != null && !slat.isEmpty();
         try {
-            if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
-                double lat = Double.parseDouble(slat);
+            if (haveLng && haveLat) {
                 double lng = Double.parseDouble(slng);
+                double lat = Double.parseDouble(slat);
                 ObjectNode loc = objectNode()
-                        .put("type", "latlng").put("lat", lat).put("lng", lng);
+                        .put("type", "lnglat")
+                        .put("lng", lng)
+                        .put("lat", lat);
                 payload.set("location", loc);
+            } else {
+                log.trace("missing Lng/Lat: lng={}, lat={}", slng, slat);
             }
         } catch (NumberFormatException e) {
-            log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
+            log.warn("Invalid geo data: longitude={}, latitude={}", slng, slat);
         }
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index ace9a0e..dbd5e0c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -55,7 +55,7 @@
             B: [toggleMap, 'Toggle background map'],
             S: [toggleSprites, 'Toggle sprite layer'],
 
-            //X: [toggleNodeLock, 'Lock / unlock node positions'],
+            X: [tfs.resetAllLocations, 'Reset node locations'],
             Z: [tos.toggleOblique, 'Toggle oblique view (Experimental)'],
             N: [fltr.clickAction, 'Cycle node layers'],
             L: [tfs.cycleDeviceLabels, 'Cycle device labels'],
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 21137af..8508f85 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -455,10 +455,17 @@
             ll;
 
         // if we are not clearing the position data (unpinning),
-        // attach the x, y, longitude, latitude...
+        // attach the x, y, (and equivalent longitude, latitude)...
         if (!clearPos) {
             ll = tms.lngLatFromCoord([d.x, d.y]);
-            metaUi = {x: d.x, y: d.y, lng: ll[0], lat: ll[1]};
+            metaUi = {
+                x: d.x,
+                y: d.y,
+                equivLoc: {
+                    lng: ll[0],
+                    lat: ll[1]
+                }
+            };
         }
         d.metaUi = metaUi;
         wss.sendEvent('updateMeta', {
@@ -580,6 +587,13 @@
         $timeout(updateLinks, 2000);
     }
 
+    function resetAllLocations() {
+        tms.resetAllLocations();
+        updateNodes();
+        tick(); // force nodes to be redrawn in their new locations
+        flash.flash('Reset Node Locations');
+    }
+
     // ==========================================
 
     function updateNodes() {
@@ -1169,6 +1183,7 @@
                 showMastership: showMastership,
                 showBadLinks: showBadLinks,
 
+                resetAllLocations: resetAllLocations,
                 addDevice: addDevice,
                 updateDevice: updateDevice,
                 removeDevice: removeDevice,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoModel.js b/web/gui/src/main/webapp/app/view/topo/topoModel.js
index a21fc93..418285c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoModel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoModel.js
@@ -62,7 +62,13 @@
             y = meta && meta.y,
             xy;
 
-        // If we have [x,y] already, use that...
+        // 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;
+        }
+
+        // else if we have [x,y] cached in meta data, use that...
         if (x && y) {
             node.fixed = true;
             node.px = node.x = x;
@@ -70,17 +76,6 @@
             return;
         }
 
-        var location = node.location,
-            coord;
-
-        if (location && location.type === 'latlng') {
-            coord = coordFromLngLat(location);
-            node.fixed = true;
-            node.px = node.x = coord[0];
-            node.py = node.y = coord[1];
-            return true;
-        }
-
         // if this is a node update (not a node add).. skip randomizer
         if (forUpdate) {
             return;
@@ -116,6 +111,25 @@
         angular.extend(node, xy);
     }
 
+    function setLongLat(node) {
+        var loc = node.location,
+            coord;
+
+        if (loc && loc.type === 'lnglat') {
+            coord = coordFromLngLat(loc);
+            node.fixed = true;
+            node.px = node.x = coord[0];
+            node.py = node.y = coord[1];
+            return true;
+        }
+    }
+
+    function resetAllLocations() {
+        nodes.forEach(function (d) {
+            setLongLat(d);
+        });
+    }
+
     function mkSvgCls(dh, t, on) {
         var ndh = 'node ' + dh,
             ndht = t ? ndh + ' ' + t : ndh;
@@ -428,6 +442,7 @@
                 destroyModel: destroyModel,
 
                 positionNode: positionNode,
+                resetAllLocations: resetAllLocations,
                 createDeviceNode: createDeviceNode,
                 createHostNode: createHostNode,
                 createHostLink: createHostLink,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
index c62e52b..deb49a5 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -51,6 +51,7 @@
         B: { id: 'bkgrnd-tog', gid: 'map', isel: false },
         S: { id: 'sprite-tog', gid: 'cloud', isel: false },
 
+        // TODO: add reset-node-locations button to toolbar
         //X: { id: 'nodelock-tog', gid: 'lock', isel: false },
         Z: { id: 'oblique-tog', gid: 'oblique', isel: false },
         N: { id: 'filters-btn', gid: 'filters' },
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
index 725bd47..99511fa 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
@@ -232,7 +232,7 @@
     it('should position a node by translating lng/lat', function () {
         var node = {
             location: {
-                type: 'latlng',
+                type: 'lnglat',
                 lng: 2008,
                 lat: 3009
             }
@@ -319,7 +319,7 @@
             type: 'yowser',
             online: true,
             location: {
-                type: 'latlng',
+                type: 'lnglat',
                 lng: 2048,
                 lat: 3096
             }
diff --git a/web/gui/src/test/_karma/ev/simple/ev_16_removeDevice_03.json b/web/gui/src/test/_karma/ev/simple/ev_16_removeDevice_03.json
index 9df9d61..396db5c 100644
--- a/web/gui/src/test/_karma/ev/simple/ev_16_removeDevice_03.json
+++ b/web/gui/src/test/_karma/ev/simple/ev_16_removeDevice_03.json
@@ -5,7 +5,7 @@
     "type": "switch",
     "online": false,
     "location": {
-      "type": "latlng",
+      "type": "lnglat",
       "lat": 40.7127,
       "lng": -74.0059
     },
diff --git a/web/gui/src/test/_karma/ev/simple/ev_17_removeDevice_08.json b/web/gui/src/test/_karma/ev/simple/ev_17_removeDevice_08.json
index 15e711d..b6093ee 100644
--- a/web/gui/src/test/_karma/ev/simple/ev_17_removeDevice_08.json
+++ b/web/gui/src/test/_karma/ev/simple/ev_17_removeDevice_08.json
@@ -6,7 +6,7 @@
     "online": false,
     "master": "myInstA",
     "location": {
-      "type": "latlng",
+      "type": "lnglat",
       "lat": 37.7833,
       "lng": -122.4167
     },
diff --git a/web/gui/src/test/_karma/ev/simple/ev_3_addDevice_08.json b/web/gui/src/test/_karma/ev/simple/ev_3_addDevice_08.json
index 9c16f2b..6f4137d 100644
--- a/web/gui/src/test/_karma/ev/simple/ev_3_addDevice_08.json
+++ b/web/gui/src/test/_karma/ev/simple/ev_3_addDevice_08.json
@@ -6,7 +6,7 @@
     "online": false,
     "master": "myInstA",
     "location": {
-      "type": "latlng",
+      "type": "lnglat",
       "lat": 37.7833,
       "lng": -122.4167
     },
diff --git a/web/gui/src/test/_karma/ev/simple/ev_4_addDevice_03.json b/web/gui/src/test/_karma/ev/simple/ev_4_addDevice_03.json
index 0b8f044..fb5027c 100644
--- a/web/gui/src/test/_karma/ev/simple/ev_4_addDevice_03.json
+++ b/web/gui/src/test/_karma/ev/simple/ev_4_addDevice_03.json
@@ -6,7 +6,7 @@
     "online": false,
     "master": "myInstB",
     "location": {
-      "type": "latlng",
+      "type": "lnglat",
       "lat": 40.7127,
       "lng": -74.0059
     },