Add a mini map to the changeset view in the data browser.
[rails.git] / app / views / browse / start.rjs
index 5ea29921ef2f745fdde4a591a63b32508a048aea..f38b1dc80937cb0c9f6355236fd87a2b50b01e49 100644 (file)
-page.replace_html :sidebar_title, 'Browse'
+page.replace_html :sidebar_title, 'Data'
 page.replace_html :sidebar_content, :partial => 'start'
 page << <<EOJ
- var gml, sf, obj_list, currentFeature;
- OpenLayers.Feature.Vector.style['default'].strokeWidth = 3;
- OpenLayers.Feature.Vector.style['default'].cursor = "pointer";
- function start() {
-   openSidebar({ onclose: stopBrowse });
-   vectors = new OpenLayers.Layer.Vector("Vector Layer", {
-     displayInLayerSwitcher: false
-   });
-   box = new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.RegularPolygon, { 
-     handlerOptions: {
-       sides: 4,
-       snapAngle: 90,
-       irregular: true,
-       persist: true,
-       callbacks: { done: endDrag }
-     }
-   });
-   map.addControl(box);
- }
- function stopBrowse() {
-     if (gml) {
-       gml.destroy();
-       gml = null; 
-     } 
-     if (sf) {   
-         sf.destroy();  
-         sf = null;
-     }  
- }
- function startDrag() {
-   $("drag_box").innerHTML='Drag a box on the map to select an area';
-    box.activate(); 
+  var browseBoxControl;
+  var browseActive;
+  var browseMode = "auto";
+  var browseBounds;
+  var browseFeatureList;
+  var browseActiveFeature;
+  var browseDataLayer;
+  var browseSelectControl;
+  var browseObjectList;
+
+  OpenLayers.Feature.Vector.style['default'].strokeWidth = 3;
+  OpenLayers.Feature.Vector.style['default'].cursor = "pointer";
+    
+  function startBrowse() {
+    browseActive = true;
+
+    openSidebar({ onclose: stopBrowse });
+
+    var vectors = new OpenLayers.Layer.Vector();
+    
+    browseBoxControl = new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.RegularPolygon, { 
+      handlerOptions: {
+        sides: 4,
+        snapAngle: 90,
+        irregular: true,
+        persist: true,
+        callbacks: { done: endDrag }
+      }
+    });
+    map.addControl(browseBoxControl);
+
+    map.events.register("moveend", map, showData);
+    map.events.triggerEvent("moveend");
+  }
+
+  function showData() {
+    if (browseMode == "auto") {
+      if (map.getZoom() >= 15) {
+          useMap();
+      } else {
+          setStatus("Zoom in or select an area of the map to view");
+      }    
+    }
+  }
+
+  function stopBrowse() {
+    if (browseActive) {
+      browseActive = false;
+
+      if (browseDataLayer) {
+        browseDataLayer.destroy();
+        browseDataLayer = null; 
+      } 
+
+      if (browseSelectControl) {   
+        browseSelectControl.destroy();  
+        browseSelectControl = null;
+      } 
+
+      if (browseBoxControl) {
+       browseBoxControl.destroy();
+       browseBoxControl = null;
+      }                
+
+      if (browseActiveFeature) {
+        browseActiveFeature.destroy(); 
+        browseActiveFeature = null; 
+      }
+
+      map.dataLayer.setVisibility(false);
+      map.events.unregister("moveend", map, showData);
+    }    
+  }
+
+  function startDrag() {
+    $("browse_select_box").innerHTML='Drag a box on the map to select an area';
+
+    browseBoxControl.activate();
+
     return false;
- };
+  };
+
+  $("browse_select_box").onclick = startDrag;
 
- $("drag_box").onclick = startDrag;
-  
   function useMap() {
-      var bounds = map.getExtent();
-      setBounds(bounds);
-      getData(bounds); 
+    var bounds = map.getExtent();
+    var projected = bounds.clone().transform(map.getProjectionObject(), epsg4326);
+
+    if (!browseBounds || !browseBounds.containsBounds(projected)) {
+      var center = bounds.getCenterLonLat();
+      var tileWidth = bounds.getWidth() * 1.2;
+      var tileHeight = bounds.getHeight() * 1.2;
+      var tileBounds = new OpenLayers.Bounds(center.lon - (tileWidth / 2),
+                                             center.lat - (tileHeight / 2),
+                                             center.lon + (tileWidth / 2),
+                                             center.lat + (tileHeight / 2));
+
+      browseBounds = tileBounds;
+      getData(tileBounds);
+
+      browseMode = "auto";
+
+      $("browse_select_view").style.display = "none";
+    }
+
+    return false;
   }
-  $("use_map").onclick = useMap;
-  function dataLoaded() { 
-      $("status").innerHTML = "Loaded " + this.features.length + " features. (<a href='"+ this.url+"'>API</a>)";
-      
-      obj_list = document.createElement("ul");
-      for (var i = 0; i < this.features.length; i++) {
-          var feature = this.features[i]; 
-          var type ="way";
-          if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
-              type = "node";
-          }   
-          var nice_name = type.substr(0,1).toUpperCase() + type.substr(1,type.length); 
-          var li = document.createElement("li");
-          li.appendChild(document.createTextNode(nice_name + " "));
-          var link = document.createElement("a");
-          link.href =  "/browse/"+type+"/"+feature.osm_id;
-          var name = feature.attributes.name || feature.osm_id;
-          link.appendChild(document.createTextNode(name));
-          li.appendChild(link);
-          li.appendChild(document.createTextNode(" "));
-          var link = document.createElement("a");
-          link.href =  "#";
-          link.appendChild(document.createTextNode("+"));
-          link.feature = feature;
-          link.onclick = OpenLayers.Function.bind(function() { 
-            var layer = this.feature.layer;
-            for (var i = 0; i < layer.selectedFeatures.length; i++) {
-                var f = layer.selectedFeatures[i]; 
-                layer.drawFeature(f, layer.styleMap.createSymbolizer(f, "default"));
-            }
-            on_feature_hover(this.feature);
-            map.setCenter(this.feature.geometry.getBounds().getCenterLonLat());  
-          }, link);   
-          li.appendChild(link);
-          obj_list.appendChild(li);
+
+  $("browse_select_view").onclick = useMap;
+
+  function endDrag(bbox) {
+    var bounds = bbox.getBounds();
+    var projected = bounds.clone().transform(map.getProjectionObject(), epsg4326);
+
+    browseBoxControl.deactivate();
+    browseBounds = projected;
+    getData(bounds);
+
+    browseMode = "manual";  
+
+    $("browse_select_box").innerHTML = "Manually select a different area";
+    $("browse_select_view").style.display = "inline";
+  }
+
+  function displayFeatureWarning() {
+    clearStatus();
+
+    var div = document.createElement("div");
+
+    var p = document.createElement("p");
+    p.appendChild(document.createTextNode("You have loaded an area which contains " + browseFeatureList.length + " features. In general, some browsers may not cope well with displaying this quantity of data. Generally, browsers work best at displaying less than 100 features at a time: doing anything else may make your browser slow/unresponsive. If you are sure you want to display this data, you may do so by clicking the button below."));
+    div.appendChild(p);
+
+    var input = document.createElement("input");
+    input.type = "submit";
+    input.value = "Load Data";
+    input.onclick = loadFeatureList;
+    div.appendChild(input); 
+
+    $("browse_content").innerHTML = "";
+    $("browse_content").appendChild(div);
+  }
+
+  function loadFeatureList() {
+    browseDataLayer.addFeatures(browseFeatureList);
+    browseDataLayer.events.triggerEvent("loadend");
+
+    browseFeatureList = []; 
+
+    return false;
+  }    
+
+  function customDataLoader(request) {
+    if (browseActive) {
+      var doc = request.responseXML;
+
+      if (!doc || !doc.documentElement) {
+        doc = request.responseText;
       }
-      $("object").innerHTML = "";
-      $("object").appendChild(obj_list); 
+
+      var options = {};
+
+      OpenLayers.Util.extend(options, this.formatOptions);
+
+      if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+        options.externalProjection = this.projection;
+        options.internalProjection = this.map.getProjectionObject();
+      }    
+
+      var gml = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+
+      browseFeatureList = gml.read(doc);
+
+      if (!this.maxFeatures || browseFeatureList.length <= this.maxFeatures) {
+        loadFeatureList();
+      } else {
+        displayFeatureWarning();
+      }
+    }
   }
+
   function getData(bounds) {
-    $("status").innerHTML = "Loading...";
-    bounds.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
-    var url = "/api/0.5/map?bbox="+bounds.toBBOX();
-    if (!gml) {
-        var def = OpenLayers.Feature.Vector.style['default'];
-        var style = new OpenLayers.Style();
-        style.addRules([new OpenLayers.Rule( 
-          {'symbolizer': 
-            {"Polygon": {'fillColor': '#ff0000', 'strokeColor': '#ff0000'},
-             "Line": {'fillColor': '#ffff00', 'strokeColor': '#000000', strokeOpacity: '0.4'},
-             "Point": {'fillColor': '#00ff00', 'strokeColor': '#00ff00'}}
-          }
-        )]);
-        gml = new OpenLayers.Layer.GML("Data",url, 
-                {format: OpenLayers.Format.OSM, formatOptions: {checkTags: true},
-                 styleMap: new OpenLayers.StyleMap({'default': style, 'select': {'strokeColor': '#0000ff'}})
-                }
-        );
-        gml.events.register("loadend", gml, dataLoaded );
-        map.addLayer(gml);
-        sf = new OpenLayers.Control.SelectFeature(gml, {'onSelect': on_feature_hover});
-        map.addControl(sf);
-        sf.activate();
+    var projected = bounds.clone().transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
+    var size = projected.getWidth() * projected.getHeight();
 
+    if (size > 0.25) {
+      setStatus("Unable to load: Bounding box size of " + size + " is too large (must be smaller than 0.25)");
     } else {
-        gml.setUrl(url);
+      loadGML("/api/#{API_VERSION}/map?bbox=" + projected.toBBOX());
     }
   }
-  function endDrag(bbox) {
-    var bounds = bbox.getBounds();
-    setBounds(bounds);
-    box.deactivate();
-    getData(bounds);
-    $("drag_box").innerHTML = "Select an area to see features";
 
+  function loadGML(url) {
+    setStatus("Loading...");
+    $("browse_content").innerHTML = "";
+
+    if (!browseDataLayer) {
+      var style = new OpenLayers.Style();
+
+      style.addRules([new OpenLayers.Rule({
+        symbolizer: {
+          Polygon: { fillColor: '#ff0000', strokeColor: '#ff0000' },
+          Line: { fillColor: '#ffff00', strokeColor: '#000000', strokeOpacity: '0.4' },
+          Point: { fillColor: '#00ff00', strokeColor: '#00ff00' }
+        }
+      })]);
+
+      browseDataLayer = new OpenLayers.Layer.GML("Data", url, {
+        format: OpenLayers.Format.OSM,
+        formatOptions: { checkTags: true },
+        maxFeatures: 100,
+        requestSuccess: customDataLoader,
+        displayInLayerSwitcher: false,
+        styleMap: new OpenLayers.StyleMap({
+          'default': style,
+          'select': { strokeColor: '#0000ff', strokeWidth: 8 }
+        })
+      });
+      browseDataLayer.events.register("loadend", browseDataLayer, dataLoaded );
+      map.addLayer(browseDataLayer);
+            
+      browseSelectControl = new OpenLayers.Control.SelectFeature(browseDataLayer, { onSelect: onFeatureSelect });
+      browseSelectControl.handlers.feature.stopDown = false;
+      browseSelectControl.handlers.feature.stopUp = false;
+      map.addControl(browseSelectControl);
+      browseSelectControl.activate();
+    } else {
+      browseDataLayer.setUrl(url);
+    }
+
+    browseActiveFeature = null;
   }
-  function loadObjList() {
-      $("object").innerHTML="";
-      $("object").appendChild(obj_list);
+
+  function dataLoaded() {
+    if (browseActive) {
+      clearStatus();
+        
+      browseObjectList = document.createElement("div")
+
+      var heading = document.createElement("p");
+      heading.className = "browse_heading";
+      heading.appendChild(document.createTextNode("Object list"));
+      browseObjectList.appendChild(heading);
+
+      var list = document.createElement("ul");
+
+      for (var i = 0; i < this.features.length; i++) {
+        var feature = this.features[i]; 
+            
+        // Type, for linking
+        var type = featureType(feature);
+        var typeName = ucFirst(type);
+        var li = document.createElement("li");
+        li.appendChild(document.createTextNode(typeName + " "));
+            
+        // Link, for viewing in the tab
+        var link = document.createElement("a");
+        link.href =  "/browse/" + type + "/" + feature.osm_id; 
+        var name = feature.attributes.name || feature.osm_id;
+        link.appendChild(document.createTextNode(name));
+        link.feature = feature;
+        link.onclick = OpenLayers.Function.bind(viewFeatureLink, link);   
+        li.appendChild(link);
+
+        list.appendChild(li);
+      }
+
+      browseObjectList.appendChild(list)
+
+      var link = document.createElement("a");
+      link.href = this.url;
+      link.appendChild(document.createTextNode("API"));
+      browseObjectList.appendChild(link);
+
+      $("browse_content").innerHTML = "";
+      $("browse_content").appendChild(browseObjectList); 
+    }
   }
     
-  function on_feature_hover(feature) {
-      if (currentFeature) {
-        currentFeature.layer.drawFeature(
-          currentFeature, currentFeature.layer.styleMap.createSymbolizer(currentFeature, "default")
-        );
-      }
-      feature.layer.drawFeature(
-        feature, feature.layer.styleMap.createSymbolizer(feature, "select")
+  function viewFeatureLink() {
+    var layer = this.feature.layer;
+
+    for (var i = 0; i < layer.selectedFeatures.length; i++) {
+      var f = layer.selectedFeatures[i]; 
+      layer.drawFeature(f, layer.styleMap.createSymbolizer(f, "default"));
+    }
+
+    onFeatureSelect(this.feature);
+
+    if (browseMode != "auto") {
+      map.setCenter(this.feature.geometry.getBounds().getCenterLonLat()); 
+    }
+
+    return false;
+  }
+    
+  function loadObjectList() {
+    $("browse_content").innerHTML="";
+    $("browse_content").appendChild(browseObjectList);
+
+    return false;
+  }
+      
+  function onFeatureSelect(feature) {
+    // Unselect previously selected feature
+    if (browseActiveFeature) {
+      browseActiveFeature.layer.drawFeature(
+        browseActiveFeature, 
+        browseActiveFeature.layer.styleMap.createSymbolizer(browseActiveFeature, "default")
       );
-      if ($("object").firstChild == $("object").obj_list) { 
-        $("object").removeChild(obj_list);
-      } else { 
-        $("object").innerHTML = "";
-      }   
-      var div = document.createElement("div");
-      var link = document.createElement("a");
-      link.href="#";
-      link.onclick = loadObjList;
-      link.appendChild(document.createTextNode("Back to Object List"));
-      div.appendChild(link)
-      $("object").appendChild(div);    
-      var ul = document.createElement("ul");
-      var type ="way";
-      if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
-          type = "node";
-      }    
+    }
+
+    // Redraw in selected style
+    feature.layer.drawFeature(
+      feature, feature.layer.styleMap.createSymbolizer(feature, "select")
+    );
+
+    // If the current object is the list, don't innerHTML="", since that could clear it.
+    if ($("browse_content").firstChild == browseObjectList) { 
+      $("browse_content").removeChild(browseObjectList);
+    } else { 
+      $("browse_content").innerHTML = "";
+    }   
+        
+    // Create a link back to the object list
+    var div = document.createElement("div");
+    div.style.textAlign = "center";
+    div.style.marginBottom = "20px";
+    $("browse_content").appendChild(div);
+    var link = document.createElement("a");
+    link.href = "#";
+    link.onclick = loadObjectList;
+    link.appendChild(document.createTextNode("Display object list"));
+    div.appendChild(link);
+
+    var table = document.createElement("table");
+    table.width = "100%";
+    table.className = "browse_heading";
+    $("browse_content").appendChild(table);
+
+    var tr = document.createElement("tr");
+    table.appendChild(tr);
+
+    var heading = document.createElement("td");
+    heading.appendChild(document.createTextNode(featureName(feature)));
+    tr.appendChild(heading);
+
+    var td = document.createElement("td");
+    td.align = "right";
+    tr.appendChild(td);
+
+    var type = featureType(feature);
+    var link = document.createElement("a");   
+    link.href = "/browse/" + type + "/" + feature.osm_id;
+    link.appendChild(document.createTextNode("Details"));
+    td.appendChild(link);
+
+    var div = document.createElement("div");
+    div.className = "browse_details";
+
+    $("browse_content").appendChild(div);
+
+    // Now the list of attributes
+    var ul = document.createElement("ul");
+    for (var key in feature.attributes) {
       var li = document.createElement("li");
-      var link = document.createElement("a");   
-      link.href =  "/browse/"+type+"/"+feature.osm_id;
-      link.appendChild(document.createTextNode(feature.osm_id));
-      li.appendChild(link);
+      var b = document.createElement("b");
+      b.appendChild(document.createTextNode(key));
+      li.appendChild(b);
+      li.appendChild(document.createTextNode(": " + feature.attributes[key]));
       ul.appendChild(li);
-      for (var key in feature.attributes) {
-          var li = document.createElement("li"); 
-          li.appendChild(document.createTextNode(key + ": " + feature.attributes[key]));
-          ul.appendChild(li);
-      }
-      $("object").appendChild(ul);
-      currentFeature = feature; 
+    }
+        
+    div.appendChild(ul);
+        
+    var link = document.createElement("a");   
+    link.href =  "/browse/" + type + "/" + feature.osm_id + "/history";
+    link.appendChild(document.createTextNode("Show history"));
+    link.onclick = OpenLayers.Function.bind(loadHistory, {
+      type: type, feature: feature, link: link
+    });
+        
+    div.appendChild(link);
+
+    // Stash the currently drawn feature
+    browseActiveFeature = feature; 
   }   
-  function setBounds(bounds) {
-    var epsg4326 = new OpenLayers.Projection("EPSG:4326");
-    var decimals = Math.pow(10, Math.floor(map.getZoom() / 3));
 
-    bounds = bounds.clone().transform(map.getProjectionObject(), epsg4326);
+  function loadHistory() {
+    this.link.href = "";
+    this.link.innerHTML = "Wait...";
+
+    new Ajax.Request("/api/#{API_VERSION}/" + this.type + "/" + this.feature.osm_id + "/history", {
+      onComplete: OpenLayers.Function.bind(displayHistory, this)
+    });
+
+    return false;
+  }
+
+  function displayHistory(request) {
+    if (browseActiveFeature.osm_id != this.feature.osm_id || $("browse_content").firstChild == browseObjectList)  { 
+        return false;
+    } 
+
+    this.link.parentNode.removeChild(this.link);
+
+    var doc = request.responseXML;
+
+    var table = document.createElement("table");
+    table.width = "100%";
+    table.className = "browse_heading";
+    $("browse_content").appendChild(table);
+
+    var tr = document.createElement("tr");
+    table.appendChild(tr);
+
+    var heading = document.createElement("td");
+    heading.appendChild(document.createTextNode("History for " + featureName(this.feature)));
+    tr.appendChild(heading);
+
+    var td = document.createElement("td");
+    td.align = "right";
+    tr.appendChild(td);
+
+    var link = document.createElement("a");   
+    link.href = "/browse/" + this.type + "/" + this.feature.osm_id + "/history";
+    link.appendChild(document.createTextNode("Details"));
+    td.appendChild(link);
+
+    var div = document.createElement("div");
+    div.className = "browse_details";
+
+    var nodes = doc.getElementsByTagName(this.type);
+    var history = document.createElement("ul");  
+    for (var i = nodes.length - 1; i >= 0; i--) {
+      var user = nodes[i].getAttribute("user") || "private user";
+      var timestamp = nodes[i].getAttribute("timestamp");
+      var item = document.createElement("li");
+      item.appendChild(document.createTextNode("Edited by " + user + " at " + timestamp));
+      history.appendChild(item);
+    }
+    div.appendChild(history);
+
+    $("browse_content").appendChild(div); 
+  }
+
+  function featureType(feature) {
+    if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+      return "node";
+    } else {
+      return "way";
+    }
+  }
+  
+  function featureName(feature) {
+    if (feature.attributes.name) {
+      return feature.attributes.name;
+    } else {
+      return ucFirst(featureType(feature)) + " " + feature.osm_id;
+    }
+  }
+
+  function setStatus(status) {
+    $("browse_status").innerHTML = status;
+    $("browse_status").style.display = "block";
+  }
+  
+  function clearStatus() {
+    $("browse_status").innerHTML = "";
+    $("browse_status").style.display = "none";
+  }
 
-    $("minlon").innerHTML = Math.round(bounds.left * decimals) / decimals;
-    $("minlat").innerHTML = Math.round(bounds.bottom * decimals) / decimals;
-    $("maxlon").innerHTML = Math.round(bounds.right * decimals) / decimals;
-    $("maxlat").innerHTML = Math.round(bounds.top * decimals) / decimals;
+  function ucFirst(str) {
+    return str.substr(0,1).toUpperCase() + str.substr(1,str.length);
   }
 
-start();
+  startBrowse();
 EOJ