Added support for grid coordinates in the legacy topology view.

Change-Id: I48533f24eded919673af92a59cc5e2edefef46b3
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
index 7387bcb..dceed74 100644
--- a/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/config/basics/BasicElementConfig.java
@@ -15,6 +15,8 @@
  */
 package org.onosproject.net.config.basics;
 
+import java.util.Objects;
+
 /**
  * Basic configuration for network elements, e.g. devices, hosts. Such elements
  * can have a friendly name, geo-coordinates (or grid-coordinates),
@@ -202,16 +204,12 @@
 
     /**
      * Returns true if the grid coordinates (gridY and gridX) are set on
-     * this element; false otherwise.
-     * <p>
-     * It is assumed that elements will not be placed at {@code (0,0)}.
-     * If you really need to position the element there, consider setting the
-     * coordinates to something like {@code (0.000001, 0.000001)} instead.
+     * this element, i.e. if locType is set to 'grid'; false otherwise.
      *
      * @return true if grid coordinates are set; false otherwise.
      */
     public boolean gridCoordsSet() {
-        return !doubleIsZero(gridY()) || !doubleIsZero(gridX());
+        return Objects.equals(locType(), LOC_TYPE_GRID);
     }
 
     /**
diff --git a/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java b/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java
index c87d6b1..8c56a8c 100644
--- a/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java
+++ b/core/api/src/test/java/org/onosproject/net/config/basics/BasicElementConfigTest.java
@@ -117,7 +117,7 @@
 
     @Test
     public void someGridCoords() {
-        cfg.gridX(35.0).gridY(49.7);
+        cfg.gridX(35.0).gridY(49.7).locType(GRID);
         print(cfg);
         assertTrue("grid at origin?", cfg.gridCoordsSet());
         assertEquals("gridx", 35.0, cfg.gridX(), ZERO_THRESHOLD);
diff --git a/tools/test/topos/access-null b/tools/test/topos/access-null
index 0d9a13f..3ecefc3 100755
--- a/tools/test/topos/access-null
+++ b/tools/test/topos/access-null
@@ -61,15 +61,22 @@
     echo "$@" >> $CMDS
 }
 
+function y {
+    let p="${3:-300} * ($1 - 1) - (${3:-300} * ($2 - 1)) / 2"
+    echo $p
+}
+
 # Create spines
 for spine in $(seq 1 $spines); do
-    sim "null-create-device switch Spine-${spine} ${spinePorts}"
+    sim "null-create-device switch Spine-${spine} ${spinePorts} 0 $(y $spine $spines) grid"
 done
 
 # Create 2 leaf pairs with dual links to the spines and a link between the pair
 for pair in $serviceLeafGroups; do
-    sim "null-create-device switch Leaf-${pair}1 ${leafPorts}"
-    sim "null-create-device switch Leaf-${pair}2 ${leafPorts}"
+    [ $pair = A ] && l1=1 || l1=3
+    [ $pair = A ] && l2=2 || l2=4
+    sim "null-create-device switch Leaf-${pair}1 ${leafPorts} -200 $(y $l1 4) grid"
+    sim "null-create-device switch Leaf-${pair}2 ${leafPorts} -200 $(y $l2 4) grid"
     sim "null-create-link direct Leaf-${pair}1 Leaf-${pair}2"
 
     for spine in $(seq 1 $spines); do
@@ -88,7 +95,7 @@
 
 # Create single access leafs with dual links to the spines
 for access in $(seq $accessLeaves); do
-    sim "null-create-device switch Access-${access} ${accessPorts}"
+    sim "null-create-device switch Access-${access} ${accessPorts} 200 $(y $access $accessLeaves) grid"
 
     for spine in $(seq 1 $spines); do
         for link in $(seq 1 $spineLinks); do
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 789c252..8b70c43 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
@@ -308,6 +308,7 @@
         payload.set("labels", labels("", name, device.id().toString()));
         payload.set("props", props(device.annotations()));
         addGeoLocation(device, payload);
+        addGridLocation(device, payload);
         addMetaUi(device.id().toString(), payload);
 
         String type = DEVICE_EVENT.get(event.type());
@@ -354,6 +355,7 @@
         payload.set("labels", labels(nameForHost(host), ip, host.mac().toString(), ""));
         payload.set("props", props(host.annotations()));
         addGeoLocation(host, payload);
+        addGridLocation(host, payload);
         addMetaUi(host.id().toString(), payload);
 
         String type = HOST_EVENT.get(event.type());
@@ -428,6 +430,30 @@
         }
     }
 
+    // Adds a grid location JSON to the specified payload object.
+    private void addGridLocation(Annotated annotated, ObjectNode payload) {
+        Annotations annotations = annotated.annotations();
+        if (annotations == null) {
+            return;
+        }
+
+        String xs = annotations.value(AnnotationKeys.GRID_X);
+        String ys = annotations.value(AnnotationKeys.GRID_Y);
+        if (xs != null && ys != null) {
+            try {
+                double x = Double.parseDouble(xs);
+                double y = Double.parseDouble(ys);
+                ObjectNode loc = objectNode()
+                        .put("locType", "grid")
+                        .put("latOrY", y)
+                        .put("longOrX", x);
+                payload.set("location", loc);
+            } catch (NumberFormatException e) {
+                log.warn("Invalid grid data: x={}, y={}", xs, ys);
+            }
+        }
+    }
+
     // Updates meta UI information for the specified object.
     protected void updateMetaUi(ObjectNode payload) {
         metaUi.put(JsonUtils.string(payload, "id"),
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 2ef94a6..6adf1f3 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoModel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoModel.js
@@ -55,6 +55,20 @@
         return p ? p.invert(coord) : [0, 0];
     }
 
+    function coordFromXY(loc) {
+        var bgWidth = 1000,
+            bgHeight = 1000;
+
+        var scale = 1000 / bgWidth,
+            yOffset = (1000 - (bgHeight * scale)) / 2;
+
+        // 1000 is a hardcoded HTML value of the SVG element (topo2.html)
+        var x = scale * loc.longOrX,
+            y = (scale * loc.latOrY) + yOffset;
+
+        return [x, y];
+    }
+
     function positionNode(node, forUpdate) {
         var meta = node.metaUi,
             x = meta && meta.x,
@@ -114,8 +128,8 @@
         var loc = node.location,
             coord;
 
-        if (loc && loc.locType === 'geo') {
-            coord = coordFromLngLat(loc);
+        if (loc) {
+            coord = loc.locType === 'geo' ? coordFromLngLat(loc) : coordFromXY(loc);
             node.fixed = true;
             node.px = node.x = coord[0];
             node.py = node.y = coord[1];