From: Alex Bennee <a...@cbnl.com>

This patch adds support for slippy maps to display a live geo-location
aware status view of your network. The new display (called node-map)
is scoped by surveillance categories. By selecting a category the map
will snap to the size to fit all elements in. If nodes are too close
to each other they are globed together until the map zooms to a
suitable level when they can be expanded into individual icons.

Configuration
=============

You will require a working OpenLayers and Tile Cache setup. In our
case we run it on our NMS machine but there is no reason to. For
Debian/Ubuntu systems the package dependencies look like:

python-mapnik (>=0.7.1-2), openlayers, tilecache, osm2pgsql,
osm-mapnik-utils, osm-base-data

The osm-base-data provides a world map, you will need additional data
if you want more detail.

OpenNMS uses the following parameters in
/etc/opennms/opennms.properties:

For a locally setup map server just use non http:// prefixed relative
links and the node map pages will automatically munge them to be
served from port 80. This prevents confusion with multi-homed setups.
If you are serving the map data from something other than port 80 you
will need to use long explicit form URLs for the map data.

Geo-location data
=================

Currently the only way to import geo-location data for nodes is
through the model-importer (which is what CBNL uses as OpenNMS cannot
discover network topology on its own). The node element now takes an
optional geolocation tag, e.g.:

   <geolocation lat="52.227352" lon="0.154387"></geolocation>

Updates
=======

  * Re-factored the code into Map/Table and Data controllers
  * Cleaned up DOM manipulation using jQuery methods
  * Changed mapping for map markers based on alarm and availability
  * re-organises the layout in into more OpenNMS like 3 pane view
  * Add OpenNMS style info boxes to the display
  * Add the ability to display child nodes
  * Made the pop-up box use OpenNMS table styling
  * Removed a bunch of dead code, generalised a bunch more
  * add mock console object so non-Chromium/Firebug enabled
browsers don't get confused.
  * fix loading of Javascript so cache state is not critical to working
  * create the ALL and Un-categorised entries first so the drop down is
correctly set on Firefox
  * handle local relative links
  * only use time string instead of full date

Written-by: Gareth Bailey <g...@kynesim.co.uk>
Updated-by: Alex Bennée <a...@cbnl.com>
Signed-off-by: Alex Bennée <a...@cbnl.com>
---
 .../src/main/filtered/etc/opennms.properties       |    9 +
 .../src/main/webapp/WEB-INF/dispatcher-servlet.xml |    6 +
 opennms-webapp/src/main/webapp/node_map/index.jsp  |  128 ++++
 opennms-webapp/src/main/webapp/node_map/js/main.js |  636 ++++++++++++++++++++
 .../src/main/webapp/node_map/js/node_map_marker.js |  251 ++++++++
 .../src/main/webapp/node_map/js/options.jsp        |   55 ++
 .../src/main/webapp/node_map/js/utils.js           |   24 +
 7 files changed, 1109 insertions(+), 0 deletions(-)
 create mode 100644 opennms-webapp/src/main/webapp/node_map/index.jsp
 create mode 100644 opennms-webapp/src/main/webapp/node_map/js/main.js
 create mode 100644 
opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js
 create mode 100644 opennms-webapp/src/main/webapp/node_map/js/options.jsp
 create mode 100644 opennms-webapp/src/main/webapp/node_map/js/utils.js

diff --git a/opennms-base-assembly/src/main/filtered/etc/opennms.properties 
b/opennms-base-assembly/src/main/filtered/etc/opennms.properties
index 710b3d6..aaefa00 100644
--- a/opennms-base-assembly/src/main/filtered/etc/opennms.properties
+++ b/opennms-base-assembly/src/main/filtered/etc/opennms.properties
@@ -380,6 +380,15 @@ gwt.geocoder.email=
 # Open MapQuest tile server
 gwt.openlayers.url=http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png
 
+###### SLIPPY MAPS ######
+# In addition to the remote poller maps, an implementation of OpenLayers 
slippy maps has
+# been implemented which allows you to view nodes with geolocation data.
+#
+# To use it, set these properties:
+
+# slippy.openlayers.url=http://SERVER/OpenLayers 
+# slippy.tilecache.url=http://SERVER/tilecache-cgi
+
 ###### UI DISPLAY OPTIONS ######
 
 # This value allows you to show or hide the Acknowledge event button. This is 
only
diff --git a/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml 
b/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
index fda9629..7dd6fd0 100644
--- a/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
+++ b/opennms-webapp/src/main/webapp/WEB-INF/dispatcher-servlet.xml
@@ -455,6 +455,12 @@
           <property name="url" value="RemotePollerMap/index.jsp"/>
           <property name="locationMatch" value="RemotePollerMap"/>
         </bean>
+       
+        <bean class="org.opennms.web.navigate.LocationBasedNavBarEntry">
+          <property name="name" value="Node Map"/>
+          <property name="url" value="node_map/index.jsp"/>
+          <property name="locationMatch" value="node_map"/>
+        </bean>
 
         <bean class="org.opennms.web.navigate.FileBasedNavBarEntry">
           <property name="name" value="Vulnerabilities"/>
diff --git a/opennms-webapp/src/main/webapp/node_map/index.jsp 
b/opennms-webapp/src/main/webapp/node_map/index.jsp
new file mode 100644
index 0000000..2124eda
--- /dev/null
+++ b/opennms-webapp/src/main/webapp/node_map/index.jsp
@@ -0,0 +1,128 @@
+<!--
+
+//
+// This file is part of the OpenNMS(R) Application.
+//
+// OpenNMS(R) is Copyright (C) 2002-2007 The OpenNMS Group, Inc.  All rights 
reserved.
+// OpenNMS(R) is a derivative work, containing both original code, included 
code and modified
+// code that was published under the GNU General Public License. Copyrights 
for modified 
+// and included code are below.
+//
+// OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
+//
+// Copyright (C) 2003 Networked Knowledge Systems, Inc.  All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+//
+// For more information contact:
+//      OpenNMS Licensing       <lice...@opennms.org>
+//      http://www.opennms.org/
+//      http://www.opennms.com/
+//
+
+-->
+<%@ page contentType="text/html;charset=UTF-8" language="java"%>
+
+<% 
+   String breadcrumb1 = "Node Map";
+
+   // Do special handling for slippy.openlayers.url doesn't contain a leading 
http://
+   String openlayers_url = System.getProperty("slippy.openlayers.url");
+   if (!openlayers_url.startsWith("http://";)) {
+     StringBuffer requestURL = request.getRequestURL();
+     String baseURL = requestURL.substring(0, requestURL.indexOf(":8980"));
+     openlayers_url = baseURL + openlayers_url;
+   }
+
+   String openlayers_location = "<script defer src=\"" + openlayers_url + 
"/OpenLayers.js\" type=\"text/javascript\"></script>";
+
+   //avoid cache
+   response.setHeader("Cache-Control","no-store");
+   response.setHeader("Pragma","no-cache");
+   response.setHeader("Expires","0"); 
+
+%>
+                
+                
+<jsp:include page="/includes/header.jsp" flush="false">
+  <jsp:param name="title" value="Node Map" />
+  <jsp:param name="breadcrumb" value="<%=breadcrumb1%>" />
+  
+  <jsp:param name="script" value="<%=openlayers_location%>" />
+  <jsp:param name="script" value='<script defer src="js/jquery/jquery.js" 
type="text/javascript"> </script>' />
+  <jsp:param name="script" value='<script defer src="node_map/js/options.jsp" 
type="text/javascript"> </script>' />
+  <jsp:param name="script" value='<script defer src="node_map/js/utils.js" 
type="text/javascript"> </script>' />
+  <jsp:param name="script" value='<script defer 
src="node_map/js/node_map_marker.js" type="text/javascript"> </script>' />
+  <jsp:param name="script" value='<script defer src="node_map/js/main.js" 
type="text/javascript"> </script>' />
+</jsp:include>
+
+  <!-- Left Column -->
+  <div id="index-contentleft">
+
+    <h3 class="o-box">Map Information</h3>
+    <table class="o-box">
+      <tr>
+       <th>
+         Select Category:
+       </th>
+       <td>
+         <form>
+           <select name="menu" id="categories"/>
+         </form>
+       </td>
+      </tr>
+      <tr>
+        <th>Include Child Nodes</th>
+        <td>
+         <input type="radio" name="include_children" value="yes" 
onClick="osm_reload()"/> <label for="on">Yes</label>&nbsp;&nbsp;&nbsp;
+         <input type="radio" name="include_children" value="no" 
onClick="osm_reload()" CHECKED/> <label for="off">No</label> 
+       </td>
+      </tr>
+      <tr>
+        <th>Update Time</th>
+        <td>
+         <div id="update_time" style="height: 1.5em; margin-top: 10px;"></div>
+       </td>
+      </tr>
+      <tr>
+        <th>Location</th>
+        <td>
+           <div id="location" style="clear:both; height: 1em;" alt="position 
of last right click on map"></div>
+       </td>
+      </tr>
+    </table>
+
+
+  </div>
+
+  <!-- Middle Column -->
+  <div id="index-contentmiddle">
+    <div id="map" style="float: center; width: 95%; height: 500px; "> </div>
+  </div>
+
+  <!-- Right Column -->
+  <div id="index-contentright">
+    <div style="overflow: auto; padding: 0px; margin: 0px; width: 90%; ">
+      
+      <h3 class="o-box" style=" margin: 0px;">Node List</h3>      
+      <table id="link_table" class="standard o-box"></table>
+    </div>
+
+   </div>
+
+<jsp:include page="/includes/footer.jsp" flush="false" >
+  <jsp:param name="location" value="map" />
+</jsp:include>
+    
diff --git a/opennms-webapp/src/main/webapp/node_map/js/main.js 
b/opennms-webapp/src/main/webapp/node_map/js/main.js
new file mode 100644
index 0000000..e337b4d
--- /dev/null
+++ b/opennms-webapp/src/main/webapp/node_map/js/main.js
@@ -0,0 +1,636 @@
+/*
+  Slippy Maps, main javascript.
+
+  Code starts at osm_init() on load.
+
+  data_controller - object for getting data from OpenNMS
+  map_controller - object for for updating the OpenLayers map
+  table_controller - object for updating the element table on the right
+*/
+
+var data_controller = null;
+var map_controller = null;
+var table_controller = null;
+
+/*
+ * Create a mock console object if it doesn't exist, this stops any spare
+ * console logging operations from breaking browsers without it.
+ */
+
+if(!window.console) {
+  window.console = new function() {
+    this.log = function(str) {};
+    this.dir = function(str) {};
+  };
+}
+
+var onms;
+if (!onms) onms = {};
+if (!onms.vs) onms.vs = {};
+
+/*
+ * DataContoller
+ * 
+ * This object is responsible for fetching data from OpenNMS and making it
+ * available to the rest of the page. This is principally done with XmlHttp
+ * requests to the NodeMapServlet in OpenNMS.
+ * 
+ */
+if (!onms.vs.DataController) {
+
+    onms.vs.DataController = function DataController(node_request_url, 
+                                                    category_url)  {
+       /* 
+        * node_list, flattened_node_list
+        * 
+        * The first is the json data as fetched from OpenNMS
+        * The second is the data with all children included as nodes themselves
+        */ 
+       this.node_list = [];
+       this.flattened_node_list = [];
+
+       /*
+        * category_data
+        */
+       this.category_data = null;
+
+       /*
+        * Simple wrapper to get the XmlHttp request object or error out
+        */
+       function getXmlHttp()
+       {
+           var xmlHttp;
+           try{
+               // Firefox, Opera 8.0+, Safari
+               xmlHttp=new XMLHttpRequest();
+           }
+           catch (e){
+               alert("Browser doesn't support xmlHttpRequest(), please use a 
better browser");
+           }
+           return xmlHttp;
+       }
+
+       /* 
+        * Do a XML request and pass the data back to the callback function.
+        * This requires OpenNMS to have authorised the request (which it should
+        *  have at this point given we are here).
+        */
+       function do_request(me, url, processing_callback, notify_callback) {
+           var xmlHttp = getXmlHttp();
+           console.log("do_request: "+url);
+       
+           xmlHttp.onreadystatechange=function()
+            {
+               if(xmlHttp.readyState==4 && xmlHttp.status == 200){
+                   try {
+                       var data = eval("(" + xmlHttp.responseText + ")");
+                       processing_callback(me, data);
+                   } catch (e) {
+                       console.log("do_request: error "+e);
+                       alert("Error "+e+" while processing: " + url);
+                   }
+
+                   if (notify_callback) {
+                       notify_callback()
+                   }
+               }
+            };
+           xmlHttp.open("GET", url, true);
+           xmlHttp.send(null);
+
+           return;
+       }
+
+       /*
+        * Handle Category data
+        */
+       function process_category_data(me, cat_data) {
+           if (me) {
+               me.category_data = cat_data;
+           } else {
+               console.log("process_category_data() no data controller yet");
+           }
+       }
+
+       this.get_category_data = function(callback) {
+           do_request(this, category_url, process_category_data, callback);
+           return;
+       };
+
+       /*
+        * Handle fetching the node data
+        */
+       function add_children(array, children) {
+           if (typeof children.length == "undefined") {
+               console.log("odd state");
+           }
+           
+           var i;
+           for (i=0; i<children.length; i++) {
+               var child = children[i];
+               var id = child.nodeId;
+               if (!array[id]) {
+                   array[id] = child;
+               }
+               if (child.children) {
+                   add_children(array, child.children);
+               }
+           }
+       }
+       function process_node_data(me, node_data) {
+           if (me) {
+               me.node_list = node_data;
+               me.flattened_node_list = [];
+
+               /* Process the nodes in a hash Array so
+                * we can de-duplicate any child nodes we see
+                */
+
+               var hash_array = new Array();
+               add_children(hash_array, node_data);
+               for (var key in hash_array) {
+                   var node = hash_array[key];
+                   me.flattened_node_list.push(node);
+               }
+
+           } else {
+               console.log("process_node_data() can't see data_controller 
yet");
+           }
+       }
+
+       this.get_node_data = function(node_callback, category) {
+           // Do we include children?
+           var url = node_request_url;
+
+           if ($("input[name=include_children]:checked").val() == "yes") {
+               url = url + "?depth=2";
+           } else {
+               url = url + "?depth=0";
+           }
+
+           // Category selected?
+           if (category) {
+               url = url + "&cat=" + category;
+           }
+
+           do_request(this, url, process_node_data, node_callback);
+           return;
+       };
+    };
+}
+
+/*
+ * TableController
+ * 
+ * This object is responsible for interfacing with the node table on the
+ * right side of the screen.
+ */
+if (!onms.vs.TableController) {
+
+    onms.vs.TableController = function TableController() {
+
+       /* Start by loading all nodes/cpes/aps */
+       this.category = "ALL";
+
+       /*
+        Update the table based on category data
+        */
+       onms.vs.TableController.prototype.category_data_updated_cb = function() 
{
+           console.log("category_data_updated_cb()");
+           var cat_data = data_controller.category_data;
+
+           if (cat_data) {
+               var cats = $("#categories");
+               var selected = 0;
+               var add_option = function(id, name) {
+                   if (name == this.category) {
+                       selected = id;
+                   }
+                   return $("<option></option>").val(id).html(name);
+               };
+
+               cats.append(add_option("ALL", "All"));
+               cats.append(add_option("UCAT", "Uncategorised"));
+               for (var i = 0; i < cat_data.length; i++) {
+                   cats.append(add_option(cat_data[i].id, cat_data[i].name));
+               }
+
+           } else {
+               console.log("category_data_updated_cb(), no data :-(");
+           }
+       };
+
+       /* Mappings for severity of alarms. See:
+        *
+        *./opennms-qosdaemon/src/main/java/org/openoss/opennms/...
+        *    .../spring/dao/OnmsAlarmOssjMapper.java
+        *
+        * public static final int INDETERMINATE_SEVERITY = 1;
+        * public static final int CLEARED_SEVERITY = 2;
+        * public static final int NORMAL_SEVERITY = 3;
+        * public static final int WARNING_SEVERITY = 4;
+        * public static final int MINOR_SEVERITY = 5;
+        * public static final int MAJOR_SEVERITY = 6;
+        * public static final int CRITICAL_SEVERITY = 7;
+        */
+       severity_mapping = [];
+       severity_mapping[0] = "Normal";
+       severity_mapping[1] = "Indeterminate";
+       severity_mapping[2] = "Cleared";
+       severity_mapping[3] = "Normal";
+       severity_mapping[4] = "Warning";
+       severity_mapping[5] = "Minor";
+       severity_mapping[6] = "Major";
+       severity_mapping[7] = "Critical";
+
+       /* Create table element dom nodes. */
+       function create_table_element(node) {   
+           var elm = document.createElement('tr');
+           var node_elm = document.createElement('td');
+           var alarm_elm = document.createElement('td');
+           var alarm_link = document.createElement('a');
+           var node_link = document.createElement('a');
+           var node_text = document.createTextNode(node.nodeLabel);
+           var alarm_text = document.createTextNode("view alarms");
+           var text = document.createTextNode("");
+
+           node_link.href = "/opennms/element/node.jsp?node=" + node.nodeId;
+           node_link.name = node.nodeId;
+           alarm_link.href = "/opennms/alarm/list.htm?filter=node%3d" + 
node.nodeId;
+           
+           node_elm.id = "tb_node_"+node.nodeId;
+
+           if (node.severity == 0) {
+               if (node.avail < 95.0) {
+                   elm.className = "Warning";
+               } else {
+                   elm.className = "Normal";
+               }
+           } else {
+               elm.className = severity_mapping[node.severity];
+           }
+
+           node_elm.className = "divider";
+           alarm_elm.className = "divider";
+
+           alarm_link.appendChild(alarm_text);
+           node_link.appendChild(node_text);
+           node_elm.appendChild(node_link);
+           alarm_elm.appendChild(alarm_link);
+
+           elm.appendChild(node_elm);
+           elm.appendChild(alarm_elm);
+
+           return elm;
+       }
+
+       onms.vs.TableController.prototype.update_table = function() {   
+           var nodes = data_controller.flattened_node_list;
+           var table = $("#link_table");
+           var i, j;
+           
+           // clear the table
+           table.find("tr").remove();
+       
+           for (i = 0; i < nodes.length; i++) {
+               var elm = create_table_element(nodes[i]);
+               table.append(elm);
+           }
+       };
+
+       function category_change() {
+           onms.vs.NodeMapController.prototype.update_map();
+       }
+       
+       /*
+        * initialisation functions
+        */
+       console.log("TableController started");
+       $("#categories").change(category_change);
+       data_controller.get_category_data(this.category_data_updated_cb);
+    };
+}
+
+
+
+if (!onms.vs.NodeMapController) {
+
+    onms.vs.NodeMapController = function NodeMapController(map_id, 
+                                                          tilecache_url, 
+                                                          update_id,
+                                                          cluster_threshold) {
+       /* Start by loading all nodes/cpes/aps */
+       this.category = "ALL";
+       this.refresh_time = 30000;
+
+       /* Some id's for html tags */
+       this.update_id = update_id;
+
+       /* The distance in pixels.  Nodes spaced within this radius 
+        * are clustered together under one marker.
+        */
+       this.cluster_threshold = cluster_threshold;
+
+       /* Make 'this' accessable to our closures */
+       var this_map_cont = this;
+       function mapEvent(e) {
+           if (e.type == "zoomend") {
+               this_map_cont.current_extent = this_map_cont.map.getExtent();
+               this_map_cont.clear_popups();
+               this_map_cont.refresh();
+           }
+       }
+
+       /*
+        * define the map 
+        */
+       var map_options = { 
+           restrictedExtent: new OpenLayers.Bounds(-20037508.34,
+                                                   -20037508.34,
+                                                   20037508.34,
+                                                   20037508.34),
+           sphericalMercator: true,
+           maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,
+                                            20037508.34,20037508.34),
+           units: 'm',
+           maxResolution: 78271.516950000005,
+           projection: "EPSG:900913",  
+
+           controls : [
+               new OpenLayers.Control.MouseDefaults(),
+               new OpenLayers.Control.ArgParser(),
+               new OpenLayers.Control.PanZoom()
+           ],
+           eventListeners: {
+               "zoomend": mapEvent
+           }
+       };      
+
+       /* create some openlayers objects. */
+       this.map = new OpenLayers.Map(map_id, map_options);
+       this.osm_layer = new OpenLayers.Layer.WMS("VffMap0", 
+                                                 tilecache_url, 
+                                                 ({layers: 'osm', 
+                                                   format: 'image/png'}));
+    
+       this.map.addLayer(this.osm_layer);
+       this.map.setCenter(new OpenLayers.LonLat(0 ,0), 0);
+
+       if (!this.map.getCenter()) this.map.zoomToMaxExtent();
+       this.current_extent = this.map.getExtent();
+
+       this.markers_layer = new OpenLayers.Layer.Markers("Markers");
+       this.map.addLayer(this.markers_layer);
+
+       /* Disable oncontext menu for right clicks */
+       this.map.div.oncontextmenu = function(){return false;}; 
+
+       /* Handle mouse up events */
+       this.map.div.onmouseup = function(e){
+
+           /* right click, show location */
+           if (OpenLayers.Event.isRightClick(e)){
+               e.xy = this_map_cont.map.events.getMousePosition(e);
+               e.geoxy = this_map_cont.map.getLonLatFromPixel(e.xy);
+
+               var n = {};
+               n.longitude = e.geoxy.lon;
+               n.latitude = e.geoxy.lat;
+
+               e.lonlat = 
onms.vs.utils.projection_lonlat_from_4326(this_map_cont.map, n);
+               var pos_string = "lon="+OpenLayers.Util.toFloat(e.lonlat.lon, 
7)+",lat="+OpenLayers.Util.toFloat(e.lonlat.lat, 7);
+               $("#location").html(pos_string);
+           }
+       };
+       
+       
+       /* Now we keep track of map drags so that we can clear popups on
+        * a click to the map but not when the map is dragged.
+        */
+       function mouse_down_handler(e) { 
+           if (OpenLayers.Event.isLeftClick(e)){
+               /* We've not been dragged yet but the left buton was clicked */
+               this_map_cont.phase_down = true;
+               this_map_cont.is_dragged = false;
+           }
+       }
+       this.map.events.register('mousedown', this, mouse_down_handler);
+       
+
+       this.map.div.onmousemove = function(e){
+           if (this_map_cont.phase_down) {
+               /* the mouse move while the button was held down. */
+               this_map_cont.is_dragged = true;
+           }
+       };
+
+       this.map.div.onclick = function(e){
+           if (OpenLayers.Event.isLeftClick(e)){
+               if (!this_map_cont.is_dragged) {
+                   /* if the click cam after a drag don't clear popup */
+                   this_map_cont.clear_popups();
+               }
+
+               /* remember, mouse button is up */
+               this_map_cont.phase_down = false;
+               this_map_cont.is_dragged = false;
+           }
+
+       };
+
+    };
+
+    onms.vs.NodeMapController.prototype.clear_popups = function(){     
+       for(var i=0; i < this.map.popups.length; i++) {
+           this.map.popups[i].related_node.clear_highlight();
+           this.map.removePopup(this.map.popups[i]);
+           this.markers_layer.selectedFeature = null;
+       }
+    };
+
+/*    onms.vs.NodeMapController.prototype. */
+
+
+    onms.vs.NodeMapController.prototype.set_refresh_timer = function(ms) {
+       /* Just in case... */
+       this.clear_refresh_timer();
+
+       var this_map_cont = this;       
+       function node_data_cb(node_data) {
+           console.log("node_data_cb");
+
+           this_map_cont.node_data = node_data;
+           this_map_cont.set_last_update();
+           this_map_cont.clear_popups(); 
+           this_map_cont.refresh();
+       }
+
+       function on_refresh() {
+           cat = $("#categories").val();
+           data_controller.get_node_data(node_data_cb, cat);
+       }
+       
+       this.refresh_timer = setTimeout(on_refresh, ms);
+    };
+
+    onms.vs.NodeMapController.prototype.clear_refresh_timer = function(ms) {
+       console.log("clear_refresh_timer");
+       clearTimeout(this.refresh_timer);
+    };
+
+       
+    onms.vs.NodeMapController.prototype.refresh = function() {
+       console.log("refresh");
+       
+       /* Remove the markers... */
+       if (this.nms) {
+           for (var j = 0; j < this.nms.length; j++) {
+               this.nms[j].remove_from_map(this.markers_layer, true);
+           }
+       }
+
+       table_controller.update_table();
+       this.draw_map();
+    };
+
+
+
+
+    onms.vs.NodeMapController.prototype.draw_map = function() {
+       console.log("draw_map");
+       var i, j, k;
+
+       /* clear previous nodes */
+       this.nms = [];
+       var nms = this.nms;
+       var node_data = data_controller.flattened_node_list;
+
+       
+       for (i = 0; i < node_data.length; i++) {
+           var node = node_data[i];
+           var added = false;
+           var pos   = onms.vs.utils.projection_lonlat(this.map, node);
+
+           /* 
+            * Is the node within range of any of the markers we have added so 
far?
+            */
+           for (j = 0; j < nms.length; j++) {
+               if (nms[j].check_proximity(node, this.cluster_threshold )) {
+                   nms[j].add(node);
+                   added = true;
+                   break;
+               }
+           }
+               
+           if (!added) {
+               nms.push(new onms.vs.NodeMapMarker(this.map, node));
+           }
+       
+       }
+
+       for (j = 0; j < nms.length; j++) {
+           nms[j].add_to_map(this.markers_layer);
+       }
+
+       /* Refresh in */
+       this.set_refresh_timer(this.refresh_time);
+    };
+
+    onms.vs.NodeMapController.prototype.set_last_update = function() {
+       var current_time = new Date();
+       $("#update_time").fadeOut("fast", function() {
+                                     
$("#update_time").text(current_time.toTimeString());
+                                     $("#update_time").fadeIn();
+                                 });
+    };
+
+    onms.vs.NodeMapController.prototype.zoom_to_bounds = function(nodes) {
+       console.log("zoom to bounds");
+       var i;
+       var node_bounds = new OpenLayers.Bounds();
+       var last_zoom = this.map.getZoom() ;
+
+       for (i = 0; i < nodes.length; i++) {
+           /* need to project between coordinate systems */
+           node_bounds.extend(onms.vs.utils.projection_lonlat(this.map, 
+                                                              nodes[i]));
+       }
+       
+       if (nodes.length > 0) {
+           this.map.zoomToExtent(node_bounds);
+           if (last_zoom == this.map.getZoom()) {
+               /* If we didn't actually zoom, redraw here, otherwise
+                * redraw in the zoom event that occurs soon.
+                */
+               this.clear_popups();
+               this.refresh();
+           }
+       } else {
+           /* No nodes to show, show the whole world map */
+           this.map.zoomToMaxExtent();
+       }
+    };
+
+    /*
+     * Handle updates
+     */
+    onms.vs.NodeMapController.prototype.node_data_updated_cb = function () {
+       console.log("node_data_updated_cb");
+       var node_data = data_controller.flattened_node_list;
+       map_controller.zoom_to_bounds(node_data);
+       map_controller.set_last_update(node_data);
+    };
+
+
+
+    /*
+     * Update map, triggered when we select a new category or want
+     * to include children in the display.
+     */
+    onms.vs.NodeMapController.prototype.update_map = function() {
+       console.log("update_map()");
+       /* clear refresh time so it doesn't go in the middle of this
+        * request.
+        */
+       this.clear_refresh_timer();
+       cat = $("#categories").val();
+       data_controller.get_node_data(this.node_data_updated_cb, cat);
+       console.log("update_map() done");
+    };
+
+
+    /* Kick things off at the start of time. */
+    onms.vs.NodeMapController.prototype.run_map = function() {
+       console.log("run_map()");
+       data_controller.get_node_data(this.node_data_updated_cb);
+       console.log("run_map() done");
+    };
+}
+
+
+
+/* Initialise the page and it's objects */
+function osm_init(){  
+    console.log("osm_init()");
+
+    data_controller = new 
onms.vs.DataController(onms.vs.NodeMapOptions.NODE_REQUEST_URL,
+                                                
onms.vs.NodeMapOptions.CAT_REQUEST_URL);
+
+    table_controller = new onms.vs.TableController();
+
+    map_controller = new 
onms.vs.NodeMapController(onms.vs.NodeMapOptions.MAP_ID,
+                                                  
onms.vs.NodeMapOptions.TILECACHE_URL,
+                                                  
onms.vs.NodeMapOptions.UPDATE_ID,
+                                                  
onms.vs.NodeMapOptions.CLUSTER_THRESH);
+
+
+    console.log("osm_init: running map");
+    map_controller.run_map();
+}
+
+/* Trigger a reload of the map data */
+function osm_reload() {
+    onms.vs.NodeMapController.prototype.update_map();
+}
+
+$("document").ready(osm_init());
diff --git a/opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js 
b/opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js
new file mode 100644
index 0000000..bc1395d
--- /dev/null
+++ b/opennms-webapp/src/main/webapp/node_map/js/node_map_marker.js
@@ -0,0 +1,251 @@
+/*
+  Slippy Maps, Node Markers
+
+  This file is just concerned with the display and creation of node markers on 
the map
+  and the associated popup info box. Each marker represents one or more nodes 
in the system.
+*/
+
+var onms;
+if (!onms) onms = {};
+if (!onms.vs) onms.vs = {};
+
+if (!onms.vs.NodeMapMarker) {
+    /* A node map marked represents the nodes that will show as
+     * one marker on the map.
+     */
+
+
+    onms.vs.NodeMapMarker = function(map, node) {
+       this.nodes = [];
+       this.map = map; 
+
+       /* Add the first node */
+       this.add(node);
+    };
+    
+    /* The type of popup to use. */
+    onms.vs.NodeMapMarker.prototype.NodeInfoBubble = 
OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
+            'autoSize': false
+    });
+
+    /* If a node is clustered we add it to this array */    
+    onms.vs.NodeMapMarker.prototype.add = function(node) {
+       this.nodes.push(node);
+    };
+
+    /* coords need projecting between different coordinate systems */
+    onms.vs.NodeMapMarker.prototype.projection_lonlat = function(node) {
+       return onms.vs.utils.projection_lonlat(this.map, node);
+    };
+
+    /* check to see if a new node if close to the base node */
+    onms.vs.NodeMapMarker.prototype.check_proximity = function(node, radius) { 
+       if (!this.nodes[0].projected_lonlat) {
+           this.nodes[0].projected_lonlat = 
this.projection_lonlat(this.nodes[0]);
+       }
+
+       var pll = node.projected_lonlat = this.projection_lonlat(node);
+       var pix1 = this.map.getPixelFromLonLat( this.nodes[0].projected_lonlat);
+       var pix2 = this.map.getPixelFromLonLat(pll);
+
+       var d = Math.sqrt(((pix1.x - pix2.x) * (pix1.x - pix2.x)) + ((pix1.y - 
pix2.y) * (pix1.y - pix2.y)));
+
+       if (d < radius) {
+           return true;
+       } else {
+           return false;
+       }
+    };
+
+    onms.vs.NodeMapMarker.prototype.NODE_DOWN_ICON = 
onms.vs.NodeMapOptions.OPENLAYERS_BASE + "/img/marker.png";
+    onms.vs.NodeMapMarker.prototype.NODE_UP_ICON = 
onms.vs.NodeMapOptions.OPENLAYERS_BASE + "/img/marker-green.png";
+    onms.vs.NodeMapMarker.prototype.NODE_WARNING_ICON = 
onms.vs.NodeMapOptions.OPENLAYERS_BASE + "/img/marker-gold.png";
+
+    /*
+      Calculate the image marker based on:
+
+      * is the node having an outage?
+      * does the node have current alarms?
+      * does the node have children with alarms?
+      * has the node had recent outages?
+      */
+    onms.vs.NodeMapMarker.prototype.get_icon_img_path = function() {
+       var icon = this.NODE_UP_ICON;
+
+       var alarms = this.count_alarms();
+       if (alarms.count > 0) {
+           if (alarms.severity > 4) {
+               return  this.NODE_DOWN_ICON;
+           } else if (alarms.severity > 3) {
+               return this.NODE_WARNING_ICON;
+           }
+       }
+
+       // Now check availability...
+       if (alarms.avail < 95.0) {    
+           icon = this.NODE_WARNING_ICON;
+       }
+       return icon;
+    };
+
+    /* 
+     * Summarise the alarm information across the nodes in this marker
+     * 
+     * This boils down to the maximum alarm severity and the minimum 
availability. 
+     */
+    onms.vs.NodeMapMarker.prototype.count_alarms = function() {
+       var i;
+       var count = 0;
+       var max_severity = 0;
+       var min_avail = 100.0;
+
+       for (i = 0; i < this.nodes.length; i++) {
+           if (this.nodes[i].severity > 0) {
+               max_severity = Math.max(max_severity, this.nodes[i].severity);
+               count++;
+           }
+           min_avail = Math.min(min_avail, this.nodes[i].avail);
+       }
+
+       return { count: count, severity: max_severity, avail: min_avail };
+    };
+
+    /* Mappings for severity of alarms. See:
+     *
+     *./opennms-qosdaemon/src/main/java/org/openoss/opennms/...
+     *    .../spring/dao/OnmsAlarmOssjMapper.java
+     *
+     * public static final int INDETERMINATE_SEVERITY = 1;
+     * public static final int CLEARED_SEVERITY = 2;
+     * public static final int NORMAL_SEVERITY = 3;
+     * public static final int WARNING_SEVERITY = 4;
+     * public static final int MINOR_SEVERITY = 5;
+     * public static final int MAJOR_SEVERITY = 6;
+     * public static final int CRITICAL_SEVERITY = 7;
+     */
+    severity_mapping = [];
+    severity_mapping[0] = "Normal";
+    severity_mapping[1] = "Indeterminate";
+    severity_mapping[2] = "Cleared";
+    severity_mapping[3] = "Normal";
+    severity_mapping[4] = "Warning";
+    severity_mapping[5] = "Minor";
+    severity_mapping[6] = "Major";
+    severity_mapping[7] = "Critical";
+
+    /* Display one nodes info in a line in the popup */    
+    function summarise_one_node(node) {
+       var span_class = "Normal";
+       if (node.severity>0) {
+           span_class = severity_mapping[node.severity];
+       } else if (node.avail < 95.0 ) {
+           span_class = "Warning";
+       }
+           
+       var node_html = "<tr class='"+span_class+"'>";
+       node_html += "<td><a 
href='/opennms/element/node.jsp?node="+node.nodeId+"'>";
+       node_html += node.nodeLabel;
+       node_html += "</a></td>";
+       node_html += "<td>"+node.avail+"%</td><td>";
+       if (node.severity>0) {
+           node_html += "<a 
href='/opennms/alarm/list.htm?filter=node%3d"+node.nodeId+"'>Alarms</a>";
+       } else {
+           node_html += "&nbsp;";
+       }
+       node_html += "</td></tr>";
+       return node_html;
+    }
+
+    onms.vs.NodeMapMarker.prototype.get_summary = function(report_cpe) {
+       /* Return HTML to go in popup box. */
+       var out_html = "<div><table class='standard 
o-box'><th>Node</th><th>Avail</th><th>&nbsp;</th><tbody>";
+
+       for (i = 0; i < this.nodes.length; i++) {
+           out_html += summarise_one_node(this.nodes[i]);
+       }
+       out_html += "</tbody></table></div>";
+       return out_html;
+    };
+        
+    onms.vs.NodeMapMarker.prototype.clear_highlight = function() {
+       this.set_table_class("divider");
+    };
+
+    onms.vs.NodeMapMarker.prototype.highlight = function() {
+       this.set_table_class("divider bright");
+       this.view();
+    };
+
+    /* make sure at least one node is on view in the table */
+    onms.vs.NodeMapMarker.prototype.view = function() {
+       var elm = document.getElementById("tb_node_"+this.nodes[0].nodeId);     
+       elm.scrollIntoView(true);
+    };
+
+    /* Highlight nodes in the summary table when clicked */
+    onms.vs.NodeMapMarker.prototype.set_table_class = function(classes) {
+       for (var i = 0; i < this.nodes.length; i++) {
+           var elm = document.getElementById("tb_node_" + 
this.nodes[i].nodeId);       
+           elm.className = classes;
+       }
+    };
+
+    onms.vs.NodeMapMarker.prototype.remove_from_map = function(markers_layer, 
report_cpe) {
+       markers_layer.removeMarker(this.marker);
+    };
+
+    /* create the openlayers markers and event handling for popups */
+    onms.vs.NodeMapMarker.prototype.add_to_map = function(markers_layer) {
+       var node       = this.nodes[0];
+       var popup_html = this.get_summary();
+       var location   = (this.nodes[0].projected_lonlat 
+                         || this.projection_lonlat(node));
+
+       var icon_img = this.get_icon_img_path(node);
+
+       var feature = new OpenLayers.Feature(markers_layer.markers, location); 
+       feature.closeBox = false;
+       feature.popupClass = this.NodeInfoBubble;
+       feature.data.popupContentHTML = popup_html;
+       feature.data.overflow = "auto";
+       feature.size = new OpenLayers.Size(21,25);
+       feature.data.icon = new OpenLayers.Icon(icon_img);
+       feature.data.icon.size = new OpenLayers.Size(21,25);
+       feature.data.icon.offset = new 
OpenLayers.Pixel(-(feature.data.icon.size.w/2), 
+                                                       
-feature.data.icon.size.h);
+
+       var marker = feature.createMarker();
+       
+       var this_node = this;
+       var markerClick = function (evt) {
+           {
+               var sameMarkerClicked = (this == markers_layer.selectedFeature);
+               markers_layer.selectedFeature = (!sameMarkerClicked) ? this : 
null;
+               
+               /* remove the popups already showing */
+               for(var i=0; i < this_node.map.popups.length; i++) {
+                   this_node.map.popups[i].related_node.clear_highlight();     
            
+                   this_node.map.removePopup(this_node.map.popups[i]);
+               }
+
+               if (!sameMarkerClicked) {
+                   /* then we need a new one... to replace the one we closed
+                    */
+                   var pop = this.createPopup(this.closeBox);
+
+                   pop.related_node = this_node;
+                   this_node.highlight();
+                   this_node.map.addPopup(pop);
+               }
+
+           }
+       
+           OpenLayers.Event.stop(evt);
+           return false;
+       };
+       marker.events.register("click", feature, markerClick);
+       this.marker = marker;
+
+       markers_layer.addMarker(marker);
+    };
+};
diff --git a/opennms-webapp/src/main/webapp/node_map/js/options.jsp 
b/opennms-webapp/src/main/webapp/node_map/js/options.jsp
new file mode 100644
index 0000000..a6840e4
--- /dev/null
+++ b/opennms-webapp/src/main/webapp/node_map/js/options.jsp
@@ -0,0 +1,55 @@
+<%@ page contentType="application/javascript" language="java"%>
+
+<%!
+   // Do special handling for external URLs without a leading http://
+   //
+   // This basically allows handling of the OpenLayers and Tilecache URLs if
+   // the services are running on the same machine as OpenNMS. To avoid hassle
+   // with multiple - potentially invisible to each other - network interfaces
+   // we just munge the relative URL to the normal apache port.
+   //
+   // Of course if your serving from somewhere else or OpenNMS isn't on 8980 
this
+   // will break fairly hard.
+   // 
+   public static String rationalise_external_url(HttpServletRequest request, 
String url) {
+     if (!url.startsWith("http://";)) {
+       StringBuffer requestURL = request.getRequestURL();
+       String baseURL = requestURL.substring(0, requestURL.indexOf(":8980"));
+       url = baseURL + url;
+     }
+     return url;
+   }
+%>
+
+<%
+      String openlayers_url = rationalise_external_url(request, 
System.getProperty("slippy.openlayers.url"));
+      String tilecache_url = rationalise_external_url(request, 
System.getProperty("slippy.tilecache.url"));
+%>
+/*
+  Node Map Options
+
+  This JSP JavaScript is responsible for setting the various options needed
+  to instantiate the OpenLayers map. Some if fixed, some needs to come from
+  OpenNMS itself.
+*/
+
+
+var onms;
+if (!onms) onms = {};
+if (!onms.vs) onms.vs = {};
+
+if (!onms.vs.NodeMapOptions) {
+    onms.vs.NodeMapOptions = {};
+
+    // OpenNMS servlets
+    onms.vs.NodeMapOptions.NODE_REQUEST_URL = "/opennms/nodemap/getMapNodes";
+    onms.vs.NodeMapOptions.CAT_REQUEST_URL = 
"/opennms/nodemap/getMapViewCategories";
+    onms.vs.NodeMapOptions.MAP_ID = "map";
+    onms.vs.NodeMapOptions.CAT_ID = "categories";
+    onms.vs.NodeMapOptions.UPDATE_ID = "update_time";
+    onms.vs.NodeMapOptions.CLUSTER_THRESH = 20;
+
+    // Location of OpenLayers and Tilecache
+    <% out.println("onms.vs.NodeMapOptions.TILECACHE_URL=\"" + tilecache_url + 
"/tilecache.cgi?\";"); %>
+    <% out.println("onms.vs.NodeMapOptions.OPENLAYERS_BASE=\"" + 
openlayers_url + "\";"); %>
+}
diff --git a/opennms-webapp/src/main/webapp/node_map/js/utils.js 
b/opennms-webapp/src/main/webapp/node_map/js/utils.js
new file mode 100644
index 0000000..92bf6a7
--- /dev/null
+++ b/opennms-webapp/src/main/webapp/node_map/js/utils.js
@@ -0,0 +1,24 @@
+/*
+  Node Map utility functions
+*/
+var onms;
+if (!onms) onms = {};
+if (!onms.vs) onms.vs = {};
+if (!onms.vs.utils) onms.vs.utils = {};
+
+if (!onms.vs.utils.projection_lonlat) {
+    onms.vs.utils.projection_lonlat = function(map, node) {
+       var location    = new OpenLayers.LonLat(node.longitude, node.latitude);
+       var latlon_proj = new OpenLayers.Projection("EPSG:4326");
+       return location.transform(latlon_proj, map.getProjectionObject());      
+    };
+}
+
+if (!onms.vs.utils.projection_lonlat_from_4326) {
+    onms.vs.utils.projection_lonlat_from_4326 = function(map, node) {
+       var location     = new OpenLayers.LonLat(node.longitude, node.latitude);
+       var latlon_proj  = new OpenLayers.Projection("EPSG:900913");
+       var latlon_proj2 = new OpenLayers.Projection("EPSG:4326");
+       return location.transform(latlon_proj, latlon_proj2);   
+    };
+}
-- 
1.7.5.2


------------------------------------------------------------------------------
Simplify data backup and recovery for your virtual environment with vRanger. 
Installation's a snap, and flexible recovery options mean your data is safe,
secure and there when you need it. Data protection magic?
Nope - It's vRanger. Get your free trial download today. 
http://p.sf.net/sfu/quest-sfdev2dev
_______________________________________________
Please read the OpenNMS Mailing List FAQ:
http://www.opennms.org/index.php/Mailing_List_FAQ

opennms-devel mailing list

To *unsubscribe* or change your subscription options, see the bottom of this 
page:
https://lists.sourceforge.net/lists/listinfo/opennms-devel

Reply via email to