From: Tom Hughes Date: Fri, 27 Jun 2008 23:09:05 +0000 (+0000) Subject: Merge data browser branch to trunk. X-Git-Tag: live~8456 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/97aefa23d0606edaee71d04cf6c1a2006689b1fa?hp=563172b2c877598f18c056a8eb6c0104a8df450b Merge data browser branch to trunk. --- diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 7570fd82a..918e4b617 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -43,8 +43,8 @@ class ApplicationController < ActionController::Base end end - def check_database_availability - if OSM_STATUS == :database_offline + def check_database_availability(need_api = false) + if OSM_STATUS == :database_offline or (need_api and OSM_STATUS == :api_offline) redirect_to :controller => 'site', :action => 'offline' end end diff --git a/app/controllers/browse_controller.rb b/app/controllers/browse_controller.rb new file mode 100644 index 000000000..f3a04519c --- /dev/null +++ b/app/controllers/browse_controller.rb @@ -0,0 +1,109 @@ +class BrowseController < ApplicationController + layout 'site' + + before_filter :authorize_web + before_filter { |c| c.check_database_availability(true) } + + def start + end + + def index + @nodes = Node.find(:all, :order => "timestamp DESC", :limit=> 20) + end + + def relation + begin + @relation = Relation.find(params[:id]) + + @name = @relation.tags['name'].to_s + if @name.length == 0: + @name = "#" + @relation.id.to_s + end + + @title = 'Relation | ' + (@name) + @next = Relation.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @relation.id }] ) + @prev = Relation.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @relation.id }] ) + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end + + def relation_history + begin + @relation = Relation.find(params[:id]) + + @name = @relation.tags['name'].to_s + if @name.length == 0: + @name = "#" + @relation.id.to_s + end + + @title = 'Relation History | ' + (@name) + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end + + def way + begin + @way = Way.find(params[:id]) + + @name = @way.tags['name'].to_s + if @name.length == 0: + @name = "#" + @way.id.to_s + end + + @title = 'Way | ' + (@name) + @next = Way.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @way.id }] ) + @prev = Way.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @way.id }] ) + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end + + def way_history + begin + @way = Way.find(params[:id]) + + @name = @way.tags['name'].to_s + if @name.length == 0: + @name = "#" + @way.id.to_s + end + + @title = 'Way History | ' + (@name) + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end + + def node + begin + @node = Node.find(params[:id]) + + @name = @node.tags_as_hash['name'].to_s + if @name.length == 0: + @name = "#" + @node.id.to_s + end + + @title = 'Node | ' + (@name) + @next = Node.find(:first, :order => "id ASC", :conditions => [ "visible = true AND id > :id", { :id => @node.id }] ) + @prev = Node.find(:first, :order => "id DESC", :conditions => [ "visible = true AND id < :id", { :id => @node.id }] ) + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end + + def node_history + begin + @node = Node.find(params[:id]) + + @name = @node.tags_as_hash['name'].to_s + if @name.length == 0: + @name = "#" + @node.id.to_s + end + + @title = 'Node History | ' + (@name) + rescue ActiveRecord::RecordNotFound + render :nothing => true, :status => :not_found + end + end +end diff --git a/app/helpers/browse_helper.rb b/app/helpers/browse_helper.rb new file mode 100644 index 000000000..c86ad5b71 --- /dev/null +++ b/app/helpers/browse_helper.rb @@ -0,0 +1,2 @@ +module BrowseHelper +end diff --git a/app/models/old_node.rb b/app/models/old_node.rb index 891b89731..76eab8427 100644 --- a/app/models/old_node.rb +++ b/app/models/old_node.rb @@ -50,4 +50,22 @@ class OldNode < ActiveRecord::Base el1['timestamp'] = self.timestamp.xmlschema return el1 end + + def tags_as_hash + hash = {} + Tags.split(self.tags) do |k,v| + hash[k] = v + end + hash + end + + # Pretend we're not in any ways + def ways + return [] + end + + # Pretend we're not in any relations + def containing_relation_members + return [] + end end diff --git a/app/models/old_relation.rb b/app/models/old_relation.rb index 6da7814c2..bac03c4d2 100644 --- a/app/models/old_relation.rb +++ b/app/models/old_relation.rb @@ -107,5 +107,20 @@ class OldRelation < ActiveRecord::Base el1 << e end return el1 - end + end + + # Temporary method to match interface to nodes + def tags_as_hash + return self.tags + end + + # Temporary method to match interface to relations + def relation_members + return self.old_members + end + + # Pretend we're not in any relations + def containing_relation_members + return [] + end end diff --git a/app/models/old_way.rb b/app/models/old_way.rb index a2b165e42..1abb23bbb 100644 --- a/app/models/old_way.rb +++ b/app/models/old_way.rb @@ -108,5 +108,20 @@ class OldWay < ActiveRecord::Base el1 << e end return el1 - end + end + + # Temporary method to match interface to nodes + def tags_as_hash + return self.tags + end + + # Temporary method to match interface to ways + def way_nodes + return self.old_nodes + end + + # Pretend we're not in any relations + def containing_relation_members + return [] + end end diff --git a/app/models/relation.rb b/app/models/relation.rb index bdba25f8f..9ee118f6e 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -234,4 +234,8 @@ class Relation < ActiveRecord::Base return false end + # Temporary method to match interface to nodes + def tags_as_hash + return self.tags + end end diff --git a/app/models/way.rb b/app/models/way.rb index a77f35fe9..64b11cf67 100644 --- a/app/models/way.rb +++ b/app/models/way.rb @@ -257,4 +257,9 @@ class Way < ActiveRecord::Base self.delete_with_relations_and_history(user) end + + # Temporary method to match interface to nodes + def tags_as_hash + return self.tags + end end diff --git a/app/views/browse/_common_details.rhtml b/app/views/browse/_common_details.rhtml new file mode 100644 index 000000000..ee5f22cee --- /dev/null +++ b/app/views/browse/_common_details.rhtml @@ -0,0 +1,22 @@ + + Edited at: + <%= h(common_details.timestamp) %> + + +<% if common_details.user.data_public %> + + Edited by: + <%= link_to h(common_details.user.display_name), :controller => "user", :action => "view", :display_name => common_details.user.display_name %> + +<% end %> + +<% unless common_details.tags_as_hash.empty? %> + + Tags: + + + <%= render :partial => "tag", :collection => common_details.tags_as_hash %> +
+ + +<% end %> diff --git a/app/views/browse/_containing_relation.rhtml b/app/views/browse/_containing_relation.rhtml new file mode 100644 index 000000000..b33dea590 --- /dev/null +++ b/app/views/browse/_containing_relation.rhtml @@ -0,0 +1,8 @@ + + + <%= link_to "Relation " + containing_relation.id.to_s, :action => "relation", :id => containing_relation.id.to_s %> + <% unless containing_relation.member_role.blank? %> + (as <%= h(containing_relation.member_role) %>) + <% end %> + + diff --git a/app/views/browse/_map.rhtml b/app/views/browse/_map.rhtml new file mode 100644 index 000000000..ad2d2d307 --- /dev/null +++ b/app/views/browse/_map.rhtml @@ -0,0 +1,64 @@ +<%= javascript_include_tag '/openlayers/OpenLayers.js' %> +<%= javascript_include_tag '/openlayers/OpenStreetMap.js' %> +<%= javascript_include_tag 'map.js' %> + + <% if map.visible %> +
+
+ Loading... + + <% else %> + Deleted + <% end %> + + diff --git a/app/views/browse/_navigation.rhtml b/app/views/browse/_navigation.rhtml new file mode 100644 index 000000000..57e724d42 --- /dev/null +++ b/app/views/browse/_navigation.rhtml @@ -0,0 +1,13 @@ +
+ <% if @prev %> + < + <%= link_to @prev.id.to_s, :id => @prev.id %> + <% end %> + <% if @prev and @next %> + | + <% end %> + <% if @next %> + <%= link_to @next.id.to_s, :id => @next.id %> + > + <% end %> +
diff --git a/app/views/browse/_node_details.rhtml b/app/views/browse/_node_details.rhtml new file mode 100644 index 000000000..e3d11e0fe --- /dev/null +++ b/app/views/browse/_node_details.rhtml @@ -0,0 +1,19 @@ + + + <%= render :partial => "common_details", :object => node_details %> + + <% unless node_details.ways.empty? and node_details.containing_relation_members.empty? %> + + + + + <% end %> + +
Part of: + + <% node_details.ways.each do |way| %> + + <% end %> + <%= render :partial => "containing_relation", :collection => node_details.containing_relation_members %> +
<%= link_to "Way " + way.id.to_s, :action => "way", :id => way.id.to_s %>
+
diff --git a/app/views/browse/_relation_details.rhtml b/app/views/browse/_relation_details.rhtml new file mode 100644 index 000000000..b8874d9ce --- /dev/null +++ b/app/views/browse/_relation_details.rhtml @@ -0,0 +1,27 @@ + + + <%= render :partial => "common_details", :object => relation_details %> + + <% unless relation_details.relation_members.empty? %> + + + + + <% end %> + + <% unless relation_details.containing_relation_members.empty? %> + + + + + <% end %> + +
Members: + + <%= render :partial => "relation_member", :collection => relation_details.relation_members %> +
+
Part of: + + <%= render :partial => "containing_relation", :collection => relation_details.containing_relation_members %> +
+
diff --git a/app/views/browse/_relation_member.rhtml b/app/views/browse/_relation_member.rhtml new file mode 100644 index 000000000..516d9e37f --- /dev/null +++ b/app/views/browse/_relation_member.rhtml @@ -0,0 +1,10 @@ + + + <%= h(relation_member.member_type.capitalize) %> + <%= link_to relation_member.member_id.to_s, :action => relation_member.member_type, :id => relation_member.member_id %> + <% unless relation_member.member_role.blank? %> + as + <%= h(relation_member.member_role) %> + <% end %> + + diff --git a/app/views/browse/_start.rhtml b/app/views/browse/_start.rhtml new file mode 100644 index 000000000..2e120c100 --- /dev/null +++ b/app/views/browse/_start.rhtml @@ -0,0 +1,13 @@ +
+
+

+ View data for current map view +
+ Manually select a different area +

+
+ +
+
+
diff --git a/app/views/browse/_tag.rhtml b/app/views/browse/_tag.rhtml new file mode 100644 index 000000000..8a57387bf --- /dev/null +++ b/app/views/browse/_tag.rhtml @@ -0,0 +1,3 @@ + + <%= h(tag[0]) %> = <%= h(tag[1]) %> + diff --git a/app/views/browse/_way_details.rhtml b/app/views/browse/_way_details.rhtml new file mode 100644 index 000000000..99b8e1d16 --- /dev/null +++ b/app/views/browse/_way_details.rhtml @@ -0,0 +1,27 @@ + + + <%= render :partial => "common_details", :object => way_details %> + + + + + + + <% unless way_details.containing_relation_members.empty? %> + + + + + <% end %> + +
Nodes: + + <% way_details.way_nodes.each do |wn| %> + + <% end %> +
<%= link_to "Node " + wn.node_id.to_s, :action => "node", :id => wn.node_id.to_s %>
+
Part of: + + <%= render :partial => "containing_relation", :collection => way_details.containing_relation_members %> +
+
diff --git a/app/views/browse/index.rhtml b/app/views/browse/index.rhtml new file mode 100644 index 000000000..2cd5cc9da --- /dev/null +++ b/app/views/browse/index.rhtml @@ -0,0 +1,12 @@ +

<%= @nodes.length %> Recently Changed Nodes

+ diff --git a/app/views/browse/node.rhtml b/app/views/browse/node.rhtml new file mode 100644 index 000000000..e2db18f62 --- /dev/null +++ b/app/views/browse/node.rhtml @@ -0,0 +1,20 @@ + + + + + + + + <%= render :partial => "map", :object => @node %> + +
+

Node: <%= h(@name) %>

+
+ <%= render :partial => "navigation" %> +
+ <%= render :partial => "node_details", :object => @node %> +
+ <%= link_to "Download XML", :controller => "node", :action => "read" %> + or + <%= link_to "view history", :action => "node_history" %> +
diff --git a/app/views/browse/node_history.rhtml b/app/views/browse/node_history.rhtml new file mode 100644 index 000000000..5fd3278c9 --- /dev/null +++ b/app/views/browse/node_history.rhtml @@ -0,0 +1,16 @@ +

Node History: <%= h(@name) %>

+ + + + + <%= render :partial => "map", :object => @node %> + +
+ <% @node.old_nodes.reverse.each do |node| %> + <%= render :partial => "node_details", :object => node %> +
+ <% end %> + <%= link_to "Download XML", :controller => "old_node", :action => "history" %> + or + <%= link_to "view details", :action => "node" %> +
diff --git a/app/views/browse/relation.rhtml b/app/views/browse/relation.rhtml new file mode 100644 index 000000000..f981938a7 --- /dev/null +++ b/app/views/browse/relation.rhtml @@ -0,0 +1,20 @@ + + + + + + + + <%= render :partial => "map", :object => @relation %> + +
+

Relation: <%= h(@name) %>

+
+ <%= render :partial => "navigation" %> +
+ <%= render :partial => "relation_details", :object => @relation %> +
+ <%= link_to "Download XML", :controller => "relation", :action => "read" %> + or + <%= link_to "view history", :action => "relation_history" %> +
diff --git a/app/views/browse/relation_history.rhtml b/app/views/browse/relation_history.rhtml new file mode 100644 index 000000000..60e0ffd17 --- /dev/null +++ b/app/views/browse/relation_history.rhtml @@ -0,0 +1,16 @@ +

Relation History: <%= h(@name) %>

+ + + + + <%= render :partial => "map", :object => @relation %> + +
+ <% @relation.old_relations.reverse.each do |relation| %> + <%= render :partial => "relation_details", :object => relation %> +
+ <% end %> + <%= link_to "Download XML", :controller => "old_relation", :action => "history" %> + or + <%= link_to "view details", :action => "relation" %> +
diff --git a/app/views/browse/start.rjs b/app/views/browse/start.rjs new file mode 100644 index 000000000..04f5e12c9 --- /dev/null +++ b/app/views/browse/start.rjs @@ -0,0 +1,481 @@ +page.replace_html :sidebar_title, 'Data' +page.replace_html :sidebar_content, :partial => 'start' +page << <= 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; + + function useMap() { + 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; + } + + $("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; + } + + 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) { + 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 { + loadGML("/api/0.5/map?bbox=" + projected.toBBOX()); + } + } + + 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.handler.stopDown = false; + browseSelectControl.handler.stopUp = false; + map.addControl(browseSelectControl); + browseSelectControl.activate(); + } else { + browseDataLayer.setUrl(url); + } + + browseActiveFeature = null; + } + + 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 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") + ); + } + + // 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 b = document.createElement("b"); + b.appendChild(document.createTextNode(key)); + li.appendChild(b); + li.appendChild(document.createTextNode(": " + feature.attributes[key])); + ul.appendChild(li); + } + + 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 loadHistory() { + this.link.href = ""; + this.link.innerHTML = "Wait..."; + + new Ajax.Request("/api/0.5/" + 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"; + } + + function ucFirst(str) { + return str.substr(0,1).toUpperCase() + str.substr(1,str.length); + } + + startBrowse(); +EOJ diff --git a/app/views/browse/way.rhtml b/app/views/browse/way.rhtml new file mode 100644 index 000000000..2e86e65f2 --- /dev/null +++ b/app/views/browse/way.rhtml @@ -0,0 +1,20 @@ + + + + + + + + <%= render :partial => "map", :object => @way %> + +
+

Way: <%= h(@name) %>

+
+ <%= render :partial => "navigation" %> +
+ <%= render :partial => "way_details", :object => @way %> +
+ <%= link_to "Download XML", :controller => "way", :action => "read" %> + or + <%= link_to "view history", :action => "way_history" %> +
diff --git a/app/views/browse/way_history.rhtml b/app/views/browse/way_history.rhtml new file mode 100644 index 000000000..f44405ffa --- /dev/null +++ b/app/views/browse/way_history.rhtml @@ -0,0 +1,16 @@ +

Way History: <%= h(@name) %>

+ + + + + <%= render :partial => "map", :object => @way %> + +
+ <% @way.old_ways.reverse.each do |way| %> + <%= render :partial => "way_details", :object => way %> +
+ <% end %> + <%= link_to "Download XML", :controller => "old_way", :action => "history" %> + or + <%= link_to "view details", :action => "way" %> +
diff --git a/app/views/site/index.rhtml b/app/views/site/index.rhtml index a79ea10c3..2cca4d529 100644 --- a/app/views/site/index.rhtml +++ b/app/views/site/index.rhtml @@ -94,6 +94,12 @@ by the OpenStreetMap project and its contributors. function mapInit(){ map = createMap("map"); + <% unless OSM_STATUS == :api_offline or OSM_STATUS == :database_offline %> + map.dataLayer = new OpenLayers.Layer("Data", { "visibility": false }); + map.dataLayer.events.register("visibilitychanged", map.dataLayer, toggleData); + map.addLayer(map.dataLayer); + <% end %> + <% if bbox %> var bbox = new OpenLayers.Bounds(<%= minlon %>, <%= minlat %>, <%= maxlon %>, <%= maxlat %>); @@ -126,6 +132,14 @@ by the OpenStreetMap project and its contributors. handleResize(); } + function toggleData() { + if (map.dataLayer.visibility) { + <%= remote_function :url => { :controller => 'browse', :action => 'start' } %> + } else { + closeSidebar(); + } + } + function getPosition() { return getMapCenter(); } diff --git a/config/routes.rb b/config/routes.rb index 290ad6e08..854e7f003 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -57,6 +57,16 @@ ActionController::Routing::Routes.draw do |map| map.connect "api/#{API_VERSION}/amf", :controller =>'amf', :action =>'talk' map.connect "api/#{API_VERSION}/swf/trackpoints", :controller =>'swf', :action =>'trackpoints' + # Data browsing + map.connect '/browse', :controller => 'browse', :action => 'index' + map.connect '/browse/start', :controller => 'browse', :action => 'start' + map.connect '/browse/way/:id', :controller => 'browse', :action => 'way', :id => /\d+/ + map.connect '/browse/way/:id/history', :controller => 'browse', :action => 'way_history', :id => /\d+/ + map.connect '/browse/node/:id', :controller => 'browse', :action => 'node', :id => /\d+/ + map.connect '/browse/node/:id/history', :controller => 'browse', :action => 'node_history', :id => /\d+/ + map.connect '/browse/relation/:id', :controller => 'browse', :action => 'relation', :id => /\d+/ + map.connect '/browse/relation/:id/history', :controller => 'browse', :action => 'relation_history', :id => /\d+/ + # web site map.connect '/', :controller => 'site', :action => 'index' diff --git a/public/javascripts/map.js b/public/javascripts/map.js index 9f9be0987..291fd18fa 100644 --- a/public/javascripts/map.js +++ b/public/javascripts/map.js @@ -7,9 +7,11 @@ OpenLayers._getScriptLocation = function () { return "/openlayers/"; } -function createMap(divName) { +function createMap(divName, options) { + options = options || {}; + map = new OpenLayers.Map(divName, { - controls: [ + controls: options.controls || [ new OpenLayers.Control.ArgParser(), new OpenLayers.Control.Attribution(), new OpenLayers.Control.LayerSwitcher(), @@ -18,7 +20,7 @@ function createMap(divName) { new OpenLayers.Control.ScaleLine() ], units: "m", - maxResolution: 156543, + maxResolution: 156543.0339, numZoomLevels: 20 }); diff --git a/public/openlayers/OpenStreetMap.js b/public/openlayers/OpenStreetMap.js index c783dba20..69e8840b2 100644 --- a/public/openlayers/OpenStreetMap.js +++ b/public/openlayers/OpenStreetMap.js @@ -48,8 +48,8 @@ OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.TMS, { initialize: function(name, url, options) { options = OpenLayers.Util.extend({ attribution: "Data by OpenStreetMap", - maxExtent: new OpenLayers.Bounds(-20037508,-20037508,20037508,20037508), - maxResolution: 156543, + maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34), + maxResolution: 156543.0339, units: "m", projection: "EPSG:900913", transitionEffect: "resize" diff --git a/public/stylesheets/site.css b/public/stylesheets/site.css index df04b7c99..c0cef39cb 100644 --- a/public/stylesheets/site.css +++ b/public/stylesheets/site.css @@ -454,6 +454,18 @@ hides rule from IE5-Mac \*/ background: #bbb; } +.browse_heading { + margin: 0px; + padding: 3px 6px 3px 6px; + border: 1px solid #ccc; + background: #ddd; +} + +.browse_details { + margin: 0px; + padding: 0px 6px 0px 6px; +} + .search_results_heading { margin: 0px; padding: 3px 6px 3px 6px;