]> git.openstreetmap.org Git - rails.git/commitdiff
Merge changes from trunk 7673:8632.
authorShaun McDonald <shaun@shaunmcdonald.me.uk>
Thu, 3 Jul 2008 13:06:24 +0000 (13:06 +0000)
committerShaun McDonald <shaun@shaunmcdonald.me.uk>
Thu, 3 Jul 2008 13:06:24 +0000 (13:06 +0000)
62 files changed:
app/controllers/amf_controller.rb
app/controllers/api_controller.rb
app/controllers/application.rb
app/controllers/browse_controller.rb [new file with mode: 0644]
app/controllers/diary_entry_controller.rb
app/controllers/message_controller.rb
app/controllers/trace_controller.rb
app/controllers/user_controller.rb
app/helpers/browse_helper.rb [new file with mode: 0644]
app/models/node.rb
app/models/old_node.rb
app/models/old_relation.rb
app/models/old_relation_member.rb
app/models/old_relation_tag.rb
app/models/old_way.rb
app/models/relation.rb
app/models/relation_member.rb
app/models/trace.rb
app/models/tracepoint.rb
app/models/way.rb
app/views/browse/_common_details.rhtml [new file with mode: 0644]
app/views/browse/_containing_relation.rhtml [new file with mode: 0644]
app/views/browse/_map.rhtml [new file with mode: 0644]
app/views/browse/_navigation.rhtml [new file with mode: 0644]
app/views/browse/_node_details.rhtml [new file with mode: 0644]
app/views/browse/_relation_details.rhtml [new file with mode: 0644]
app/views/browse/_relation_member.rhtml [new file with mode: 0644]
app/views/browse/_start.rhtml [new file with mode: 0644]
app/views/browse/_tag.rhtml [new file with mode: 0644]
app/views/browse/_way_details.rhtml [new file with mode: 0644]
app/views/browse/index.rhtml [new file with mode: 0644]
app/views/browse/node.rhtml [new file with mode: 0644]
app/views/browse/node_history.rhtml [new file with mode: 0644]
app/views/browse/relation.rhtml [new file with mode: 0644]
app/views/browse/relation_history.rhtml [new file with mode: 0644]
app/views/browse/start.rjs [new file with mode: 0644]
app/views/browse/way.rhtml [new file with mode: 0644]
app/views/browse/way_history.rhtml [new file with mode: 0644]
app/views/diary_entry/new.rhtml
app/views/export/start.rjs
app/views/layouts/site.rhtml
app/views/message/_message_summary.rhtml
app/views/message/new.rhtml
app/views/message/read.rhtml
app/views/notifier/diary_comment_notification.rhtml
app/views/notifier/message_notification.rhtml
app/views/site/_search.rhtml
app/views/site/edit.rhtml
app/views/site/index.rhtml
app/views/site/offline.rhtml [new file with mode: 0644]
app/views/trace/view.rhtml
app/views/user/confirm.rhtml
config/environment.rb
config/initializers/composite_primary_keys.rb
config/routes.rb
lib/geo_record.rb [moved from app/models/geo_record.rb with 50% similarity]
lib/object_finder.rb [new file with mode: 0644]
public/export/embed.html
public/javascripts/map.js
public/openlayers/OpenStreetMap.js
public/potlatch/potlatch.swf
public/stylesheets/site.css

index 4214448696d6fb2d85f7f37fb1b0daec83ca30dd..11ddc3f7c7ba8fe9148fc4e9f65ae3ec47948d86 100644 (file)
@@ -123,7 +123,9 @@ class AmfController < ApplicationController
     points = nodes_not_used_in_area.collect { |n| [n.id, n.lon_potlatch(baselong,masterscale), n.lat_potlatch(basey,masterscale), n.tags_as_hash] }
 
     # find the relations used by those nodes and ways
-    relation_ids = (Relation.find_for_nodes_and_ways(nodes_in_area.collect {|n| n.id}, way_ids)).collect {|n| n.id}.uniq
+    relations = nodes_in_area.collect { |node| node.containing_relations.visible }.flatten +
+                way_ids.collect { |id| Way.find(id).containing_relations.visible }.flatten
+    relation_ids = relations.collect { |relation| relation.id }.uniq
 
     [way_ids,points,relation_ids]
   end
@@ -376,10 +378,11 @@ class AmfController < ApplicationController
 
     RAILS_DEFAULT_LOGGER.info("  Message: putway, id=#{originalway}")
 
-    # -- Check for null IDs or short ways
+    # -- Check for null IDs, short ways or lats=90
 
     points.each do |a|
       if a[2]==0 or a[2].nil? then return -2,"Server error - node with id 0 found in way #{originalway}." end
+      if coord2lat(a[1],masterscale,basey)==90 then return -2,"Server error - node with lat -90 found in way #{originalway}." end
     end
     
     if points.length<2 then return -2,"Server error - way is only #{points.length} points long." end
@@ -638,8 +641,8 @@ class AmfController < ApplicationController
     #   which means the SWF needs to allocate new ids
     # - if it's an invisible node, we can reuse the old node id
 
-    # get node list from specified version of way,
-    # and the _current_ lat/long/tags of each node
+    # -----    get node list from specified version of way,
+    #          and the _current_ lat/long/tags of each node
 
     row=ActiveRecord::Base.connection.select_one("SELECT timestamp FROM ways WHERE version=#{version} AND id=#{id}")
     waytime=row['timestamp']
@@ -654,31 +657,31 @@ class AmfController < ApplicationController
   EOF
     rows=ActiveRecord::Base.connection.select_all(sql)
 
-    # if historic (full revert), get the old version of each node
-    # - if it's in another way now, generate a new id
-    # - if it's not in another way, use the old ID
+    # -----    if historic (full revert), get the old version of each node
+    #          - if it's in another way now, generate a new id
+    #          - if it's not in another way, use the old ID
+
     if historic then
       rows.each_index do |i|
         sql=<<-EOF
     SELECT latitude*0.0000001 AS latitude,longitude*0.0000001 AS longitude,tags,cwn.id AS currentway 
       FROM nodes n
  LEFT JOIN current_way_nodes cwn
-      ON cwn.node_id=n.id
+ LEFT JOIN current_way_nodes cwn
+        ON cwn.node_id=n.id AND cwn.id!=#{id} 
      WHERE n.id=#{rows[i]['id']} 
        AND n.timestamp<="#{waytime}" 
-     AND cwn.id!=#{id} 
-     ORDER BY n.timestamp DESC 
+  ORDER BY n.timestamp DESC 
      LIMIT 1
     EOF
         row=ActiveRecord::Base.connection.select_one(sql)
-        unless row.nil? then
-          nx=row['longitude'].to_f
-          ny=row['latitude'].to_f
+        nx=row['longitude'].to_f
+        ny=row['latitude'].to_f
+        if (!row.nil?)
           if (row['currentway'] && (nx!=rows[i]['longitude'].to_f or ny!=rows[i]['latitude'].to_f or row['tags']!=rows[i]['tags'])) then rows[i]['id']=-1 end
-          rows[i]['longitude']=nx
-          rows[i]['latitude' ]=ny
-          rows[i]['tags'     ]=row['tags']
-        end
+               end
+        rows[i]['longitude']=nx
+        rows[i]['latitude' ]=ny
+        rows[i]['tags'     ]=row['tags']
       end
     end
     rows
index d7937cb01d8db241072e1de2d1b841486fce84b4..05dfb0133ae6ca9bafdf05a97025ea7059e322ac 100644 (file)
@@ -124,8 +124,6 @@ class ApiController < ApplicationController
       return
     end
 
-    relations = Array.new
-
     doc = OSM::API.new.get_xml_doc
 
     # get ways
@@ -170,19 +168,15 @@ class ApiController < ApplicationController
       end
     end 
 
-    relations = Relation.find_for_nodes_and_ways(visible_nodes.keys, way_ids)
+    relations = visible_nodes.values.collect { |node| node.containing_relations.visible }.flatten +
+                way_ids.collect { |id| Way.find(id).containing_relations.visible }.flatten
 
     # we do not normally return the "other" partners referenced by an relation, 
     # e.g. if we return a way A that is referenced by relation X, and there's 
     # another way B also referenced, that is not returned. But we do make 
     # an exception for cases where an relation references another *relation*; 
     # in that case we return that as well (but we don't go recursive here)
-    relation_ids = relations.collect { |relation| relation.id }
-    if relation_ids.length > 0
-        relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " +
-            "e.visible=1 and " +
-            "em.id = e.id and em.member_type='relation' and em.member_id in (#{relation_ids.join(',')})")
-    end
+    relations += relations.collect { |relation| relation.containing_relations.visible }.flatten
 
     # this "uniq" may be slightly inefficient; it may be better to first collect and output
     # all node-related relations, then find the *not yet covered* way-related ones etc.
index 8d082c2ca0efd7f3743f3ecad3816aad9c8bf02e..68359585e0eeb60d64c498a64b965af5791e87f4 100644 (file)
@@ -2,6 +2,10 @@
 # Likewise, all the methods added will be available for all controllers.
 class ApplicationController < ActionController::Base
 
+  if OSM_STATUS == :database_offline
+    session :off
+  end
+
   def authorize_web
     if session[:user]
       @user = User.find(session[:user])
@@ -39,8 +43,14 @@ class ApplicationController < ActionController::Base
     end 
   end 
 
+  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
+
   def check_read_availability
-    if API_STATUS == :offline
+    if OSM_STATUS == :database_offline or OSM_STATUS == :api_offline
       response.headers['Error'] = "Database offline for maintenance"
       render :nothing => true, :status => :service_unavailable
       return false
@@ -48,7 +58,7 @@ class ApplicationController < ActionController::Base
   end
 
   def check_write_availability
-    if API_STATUS == :offline or API_STATUS == :readonly
+    if OSM_STATUS == :database_offline or OSM_STATUS == :api_offline or OSM_STATUS == :api_readonly
       response.headers['Error'] = "Database offline for maintenance"
       render :nothing => true, :status => :service_unavailable
       return false
diff --git a/app/controllers/browse_controller.rb b/app/controllers/browse_controller.rb
new file mode 100644 (file)
index 0000000..f3a0451
--- /dev/null
@@ -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
index a3b37b931beabf3c7cd9fcbca435d62c41c37914..5159f73624b2bcf53f2c3344310da59ee5f147b3 100644 (file)
@@ -3,6 +3,7 @@ class DiaryEntryController < ApplicationController
 
   before_filter :authorize_web
   before_filter :require_user, :only => [:new]
+  before_filter :check_database_availability
 
   def new
     @title = 'new diary entry'
index 8aecef98e56a87059595550c72064170fca66501..85c0ac328f2fc0349bd733518f51dd343410a825 100644 (file)
@@ -17,25 +17,15 @@ class MessageController < ApplicationController
         Notifier::deliver_message_notification(@message)
         redirect_to :controller => 'message', :action => 'inbox', :display_name => @user.display_name
       end
-    end
-  end
-
-  def destroy
-    @message = Message.find(params[:message_id], :conditions => ["to_user_id = ? or from_user_id = ?", @user.id, @user.id ])
-    if !@message.message_read
-      flash[:notice] = 'Message not read and so not deleted'
-      redirect_to :controller => 'message', :action => 'inbox', :display_name => @user.display_name
     else
-      flash[:notice] = "Message '#{@message.title}' deleted"
-      @message.destroy
-      redirect_to :controller => 'message', :action => 'inbox', :display_name => @user.display_name
+      @title = params[:title]
     end
   end
 
   def reply
     message = Message.find(params[:message_id], :conditions => ["to_user_id = ? or from_user_id = ?", @user.id, @user.id ])
-    @body = "\n\nOn #{message.sent_on} #{message.sender.display_name} wrote:\n #{message.body}" 
-    @title = "Re: #{message.title}"
+    @body = "On #{message.sent_on} #{message.sender.display_name} wrote:\n\n#{message.body.gsub(/^/, '> ')}" 
+    @title = "Re: #{message.title.sub(/^Re:\s*/, '')}"
     @user_id = message.from_user_id
     render :action => 'new'
   rescue ActiveRecord::RecordNotFound
index 3bb0d3323b0e2fa0b3f12261847862c4785a35ac..0467e66bac49028fff5a56f21c4d84b55fe1e8ba 100644 (file)
@@ -1,7 +1,10 @@
 class TraceController < ApplicationController
+  layout 'site'
+
   before_filter :authorize_web  
   before_filter :authorize, :only => [:api_details, :api_data, :api_create]
-  layout 'site'
+  before_filter :check_database_availability, :except => [:api_details, :api_data, :api_create]
+  before_filter :check_read_availability, :only => [:api_details, :api_data, :api_create]
  
   # Counts and selects pages of GPX traces for various criteria (by user, tags, public etc.).
   #  target_user - if set, specifies the user to fetch traces for.  if not set will fetch all traces
@@ -84,20 +87,15 @@ class TraceController < ApplicationController
   def view
     @trace = Trace.find(params[:id])
 
-    unless @trace
-      flash[:notice] = "OH NOES! Trace not found!"
+    if @trace and @trace.visible? and
+       (@trace.public? or @trace.user.id == @user.id)
+      @title = "Viewing trace #{@trace.name}"
+    else
+      flash[:notice] = "Trace not found!"
       redirect_to :controller => 'trace', :action => 'list'
-      return
-    end
-
-    @title = "Viewing trace #{@trace.name}"
-    if !@trace.visible?
-      render :nothing => true, :status => :not_found
-    elsif !@trace.public? and @trace.user.id != @user.id
-      render :nothing => true, :status => :forbidden
     end
   rescue ActiveRecord::RecordNotFound
-    flash[:notice] = "GPX file not found"
+    flash[:notice] = "Trace not found!"
     redirect_to :controller => 'trace', :action => 'list'
   end
 
@@ -115,11 +113,11 @@ class TraceController < ApplicationController
       end
     else
       @trace = Trace.new({:name => "Dummy",
-                         :tagstring => params[:trace][:tagstring],
-                         :description => params[:trace][:description],
-                         :public => params[:trace][:public],
-                         :inserted => false, :user => @user,
-                         :timestamp => Time.now})
+                          :tagstring => params[:trace][:tagstring],
+                          :description => params[:trace][:description],
+                          :public => params[:trace][:public],
+                          :inserted => false, :user => @user,
+                          :timestamp => Time.now})
       @trace.valid?
       @trace.errors.add(:gpx_file, "can't be blank")
     end
@@ -294,7 +292,7 @@ class TraceController < ApplicationController
     end
   end
 
-  private
+private
 
   def do_create(file, tags, description, public)
     name = file.original_filename.gsub(/[^a-zA-Z0-9.]/, '_')
@@ -303,7 +301,7 @@ class TraceController < ApplicationController
     File.open(filename, "w") { |f| f.write(file.read) }
 
     @trace = Trace.new({:name => name, :tagstring => tags,
-                       :description => description, :public => public})
+                        :description => description, :public => public})
     @trace.inserted = false
     @trace.user = @user
     @trace.timestamp = Time.now
index e998e83c69d86e843f4763f2e42e41bfd295034c..5d26708fe0df2846058c3c869528192334346f48 100644 (file)
@@ -4,6 +4,8 @@ class UserController < ApplicationController
   before_filter :authorize, :only => [:api_details, :api_gpx_files]
   before_filter :authorize_web, :only => [:account, :go_public, :view, :diary, :make_friend, :remove_friend, :upload_image]
   before_filter :require_user, :only => [:set_home, :account, :go_public, :make_friend, :remove_friend, :upload_image]
+  before_filter :check_database_availability, :except => [:api_details, :api_gpx_files]
+  before_filter :check_read_availability, :only => [:api_details, :api_gpx_files]
 
   filter_parameter_logging :password, :pass_crypt, :pass_crypt_confirmation
 
@@ -143,17 +145,19 @@ class UserController < ApplicationController
   end
 
   def confirm
-    token = UserToken.find_by_token(params[:confirm_string])
-    if token and !token.user.active?
-      @user = token.user
-      @user.active = true
-      @user.save!
-      token.destroy
-      flash[:notice] = 'Confirmed your account, thanks for signing up!'
-      session[:user] = @user.id
-      redirect_to :action => 'account', :display_name => @user.display_name
-    else
-      flash[:notice] = 'Something went wrong confirming that user.'
+    if params[:confirm_action]
+      token = UserToken.find_by_token(params[:confirm_string])
+      if token and !token.user.active?
+        @user = token.user
+        @user.active = true
+        @user.save!
+        token.destroy
+        flash[:notice] = 'Confirmed your account, thanks for signing up!'
+        session[:user] = @user.id
+        redirect_to :action => 'account', :display_name => @user.display_name
+      else
+        flash[:notice] = 'Something went wrong confirming that user.'
+      end
     end
   end
 
diff --git a/app/helpers/browse_helper.rb b/app/helpers/browse_helper.rb
new file mode 100644 (file)
index 0000000..c86ad5b
--- /dev/null
@@ -0,0 +1,2 @@
+module BrowseHelper
+end
index 29b1d0b0a27f869545eb4336eaa1a6b9d925ca80..abfa44d676930d3310e4c0b73fc8bcc9bad194e5 100644 (file)
@@ -1,6 +1,8 @@
-class Node < GeoRecord
+class Node < ActiveRecord::Base
   require 'xml/libxml'
 
+  include GeoRecord
+
   set_table_name 'current_nodes'
   
   validates_presence_of :user_id, :timestamp
@@ -8,21 +10,21 @@ class Node < GeoRecord
   validates_numericality_of :latitude, :longitude
   validate :validate_position
 
+  belongs_to :user
+
   has_many :old_nodes, :foreign_key => :id
+
   has_many :way_nodes
-  has_many :node_tags, :foreign_key => :id
-  belongs_to :user
+  has_many :ways, :through => :way_nodes
+
+  has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
+  has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
+
+  # Sanity check the latitude and longitude and add an error if it's broken
   def validate_position
     errors.add_to_base("Node is not in the world") unless in_world?
   end
 
-  def in_world?
-    return false if self.lat < -90 or self.lat > 90
-    return false if self.lon < -180 or self.lon > 180
-    return true
-  end
-
   #
   # Search for nodes matching tags within bounding_box
   #
@@ -55,109 +57,64 @@ class Node < GeoRecord
       p = XML::Parser.new
       p.string = xml
       doc = p.parse
+  
+      node = Node.new
 
       doc.find('//osm/node').each do |pt|
-        return Node.from_xml_node(pt, create)
-      end
-    rescue
-      return nil
-    end
-  end
+        node.lat = pt['lat'].to_f
+        node.lon = pt['lon'].to_f
 
-  def self.from_xml_node(pt, create=false)
-    node = Node.new
-    
-    node.version = pt['version']
-    node.lat = pt['lat'].to_f
-    node.lon = pt['lon'].to_f
+        return nil unless node.in_world?
 
-    return nil unless node.in_world?
+        unless create
+          if pt['id'] != '0'
+            node.id = pt['id'].to_i
+          end
+        end
 
-    unless create
-      if pt['id'] != '0'
-        node.id = pt['id'].to_i
-      end
-    end
+        node.visible = pt['visible'] and pt['visible'] == 'true'
 
-    node.visible = pt['visible'] and pt['visible'] == 'true'
+        if create
+          node.timestamp = Time.now
+        else
+          if pt['timestamp']
+            node.timestamp = Time.parse(pt['timestamp'])
+          end
+        end
 
-    if create
-      node.timestamp = Time.now
-    else
-      if pt['timestamp']
-        node.timestamp = Time.parse(pt['timestamp'])
-      end
-    end
+        tags = []
 
-    tags = []
+        pt.find('tag').each do |tag|
+          tags << [tag['k'],tag['v']]
+        end
 
-    pt.find('tag').each do |tag|
-      node.add_tag_key_val(tag['k'],tag['v'])
+        node.tags = Tags.join(tags)
+      end
+    rescue
+      node = nil
     end
 
     return node
   end
 
+  # Save this node with the appropriate OldNode object to represent it's history.
   def save_with_history!
-    t = Time.now
     Node.transaction do
-      self.version += 1
-      self.timestamp = t
+      self.timestamp = Time.now
       self.save!
-
-      # Create a NodeTag
-      tags = self.tags
-      NodeTag.delete_all(['id = ?', self.id])
-      tags.each do |k,v|
-        tag = NodeTag.new
-        tag.k = k 
-        tag.v = v 
-        tag.id = self.id
-        tag.save!
-      end 
-
-      # Create an OldNode
       old_node = OldNode.from_node(self)
-      old_node.timestamp = t
-      old_node.save_with_dependencies!
-    end
-  end
-
-  def delete_with_history(user)
-    if self.visible
-      if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = 1 AND current_way_nodes.node_id = ?", self.id ])
-       raise OSM::APIPreconditionFailedError.new
-      elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='node' and member_id=?", self.id])
-       raise OSM::APIPreconditionFailedError.new
-      else
-       self.user_id = user.id
-       self.visible = 0
-       save_with_history!
-      end
-    else
-      raise OSM::APIAlreadyDeletedError.new
-    end
-  end
-
-  def update_from(new_node, user)
-    if new_node.version != version
-      raise OSM::APIVersionMismatchError.new(new_node.version, version)
+      old_node.save!
     end
-
-    self.user_id = user.id
-    self.latitude = new_node.latitude 
-    self.longitude = new_node.longitude
-    self.tags = new_node.tags
-    self.visible = true
-    save_with_history!
   end
 
+  # Turn this Node in to a complete OSM XML object with <osm> wrapper
   def to_xml
     doc = OSM::API.new.get_xml_doc
     doc.root << to_xml_node()
     return doc
   end
 
+  # Turn this Node in to an XML Node without the <osm> wrapper.
   def to_xml_node(user_display_name_cache = nil)
     el1 = XML::Node.new 'node'
     el1['id'] = self.id.to_s
@@ -176,7 +133,7 @@ class Node < GeoRecord
 
     el1['user'] = user_display_name_cache[self.user_id] unless user_display_name_cache[self.user_id].nil?
 
-    self.tags.each do |k,v|
+    Tags.split(self.tags) do |k,v|
       el2 = XML::Node.new('tag')
       el2['k'] = k.to_s
       el2['v'] = v.to_s
@@ -185,33 +142,15 @@ class Node < GeoRecord
 
     el1['visible'] = self.visible.to_s
     el1['timestamp'] = self.timestamp.xmlschema
-    el1['version'] = self.version.to_s
     return el1
   end
 
+  # Return the node's tags as a Hash of keys and their values
   def tags_as_hash
-    return tags
-  end
-
-  def tags
-    unless @tags
-      @tags = {}
-      self.node_tags.each do |tag|
-        @tags[tag.k] = tag.v
-      end
+    hash = {}
+    Tags.split(self.tags) do |k,v|
+      hash[k] = v
     end
-    @tags
-  end
-
-  def tags=(t)
-    @tags = t 
-  end 
-
-  def add_tag_key_val(k,v)
-    @tags = Hash.new unless @tags
-    @tags[k] = v
+    hash
   end
-
-
-
 end
index a3650173a6e5dfdaf3146eefe41b2a3adb0514e3..76eab8427b2c570cce79846887706eb6c10923b6 100644 (file)
@@ -1,4 +1,6 @@
-class OldNode < GeoRecord
+class OldNode < ActiveRecord::Base
+  include GeoRecord
+
   set_table_name 'nodes'
   
   validates_presence_of :user_id, :timestamp
@@ -27,15 +29,8 @@ class OldNode < GeoRecord
     old_node.timestamp = node.timestamp
     old_node.user_id = node.user_id
     old_node.id = node.id
-    old_node.version = node.version
     return old_node
   end
-  
-  def to_xml
-    doc = OSM::API.new.get_xml_doc
-    doc.root << to_xml_node()
-    return doc
-  end
 
   def to_xml_node
     el1 = XML::Node.new 'node'
@@ -44,7 +39,7 @@ class OldNode < GeoRecord
     el1['lon'] = self.lon.to_s
     el1['user'] = self.user.display_name if self.user.data_public?
 
-    self.tags.each do |k,v|
+    Tags.split(self.tags) do |k,v|
       el2 = XML::Node.new('tag')
       el2['k'] = k.to_s
       el2['v'] = v.to_s
@@ -53,41 +48,24 @@ class OldNode < GeoRecord
 
     el1['visible'] = self.visible.to_s
     el1['timestamp'] = self.timestamp.xmlschema
-    el1['version'] = self.version.to_s
     return el1
   end
-
-  def save_with_dependencies!
-    save!
-    #not sure whats going on here
-    clear_aggregation_cache
-    clear_association_cache
-    #ok from here
-    @attributes.update(OldNode.find(:first, :conditions => ['id = ? AND timestamp = ?', self.id, self.timestamp]).instance_variable_get('@attributes'))
-   
-    self.tags.each do |k,v|
-      tag = OldNodeTag.new
-      tag.k = k
-      tag.v = v
-      tag.id = self.id
-      tag.version = self.version
-      tag.save!
+  
+  def tags_as_hash
+    hash = {}
+    Tags.split(self.tags) do |k,v|
+      hash[k] = v
     end
+    hash
   end
 
-  def tags
-    unless @tags
-        @tags = Hash.new
-        OldNodeTag.find(:all, :conditions => ["id = ? AND version = ?", self.id, self.version]).each do |tag|
-            @tags[tag.k] = tag.v
-        end
-    end
-    @tags = Hash.new unless @tags
-    @tags
+  # Pretend we're not in any ways
+  def ways
+    return []
   end
 
-  def tags=(t)
-    @tags = t 
-  end 
-
+  # Pretend we're not in any relations
+  def containing_relation_members
+    return []
+  end
 end
index 03d5aebfff3006eaa91036b5efa26118835b7223..f5885f39ffb59adc799f4b5ea7832066f9654f5a 100644 (file)
@@ -109,5 +109,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
index 9e2cce4c2ec8b76367edece77931f282e011936f..d8b68585428da54515eb579d9a31ca4f04f04576 100644 (file)
@@ -1,6 +1,3 @@
 class OldRelationMember < ActiveRecord::Base
-  belongs_to :user
-
   set_table_name 'relation_members'
-
 end
index 6ad4b452e4ed50790a340cd4c60a5eb35eb87d10..7ce6f694e633bb2a2110711229b1be8b6886e275 100644 (file)
@@ -1,6 +1,3 @@
 class OldRelationTag < ActiveRecord::Base
-  belongs_to :user
-
   set_table_name 'relation_tags'
-
 end
index 136e647938122d69a83ed5669521dbe4e64f54ec..edf66aac324922d26ea8090ba88062954e40df15 100644 (file)
@@ -110,5 +110,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
index 984732c71c98f6b0bc9c1463029135fb2345f64a..eb3b06a130bc1a22c0f27170aabb0d2dc21c6c0b 100644 (file)
@@ -1,14 +1,17 @@
 class Relation < ActiveRecord::Base
   require 'xml/libxml'
   
+  set_table_name 'current_relations'
+
   belongs_to :user
 
+  has_many :old_relations, :foreign_key => 'id', :order => 'version'
+
   has_many :relation_members, :foreign_key => 'id'
   has_many :relation_tags, :foreign_key => 'id'
 
-  has_many :old_relations, :foreign_key => 'id', :order => 'version'
-
-  set_table_name 'current_relations'
+  has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
+  has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
 
   def self.from_xml(xml, create=false)
     begin
@@ -109,29 +112,6 @@ class Relation < ActiveRecord::Base
     return el1
   end 
 
-    
-  # collect relationships. currently done in one big block at the end;
-  # may need to move this upwards if people want automatic completion of
-  # relationships, i.e. deliver referenced objects like we do with ways... 
-  # FIXME: rip out the fucking SQL
-  def self.find_for_nodes_and_ways(node_ids, way_ids)
-    relations = []
-
-    if node_ids.length > 0
-      relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " +
-            "e.visible=1 and " +
-            "em.id = e.id and em.member_type='node' and em.member_id in (#{node_ids.join(',')})")
-    end
-    if way_ids.length > 0
-      relations += Relation.find_by_sql("select e.* from current_relations e,current_relation_members em where " +
-            "e.visible=1 and " +
-            "em.id = e.id and em.member_type='way' and em.member_id in (#{way_ids.join(',')})")
-    end
-
-    relations # if you don't do this then it returns nil and not []
-  end
-
-
   # FIXME is this really needed?
   def members
     unless @members
@@ -236,22 +216,49 @@ class Relation < ActiveRecord::Base
   end
 
   def preconditions_ok?
+    # These are hastables that store an id in the index of all 
+    # the nodes/way/relations that have already been added.
+    # Once we know the id of the node/way/relation exists
+    # we check to see if it is already existing in the hashtable
+    # if it does, then we return false. Otherwise
+    # we add it to the relevant hash table, with the value true..
+    # Thus if you have nodes with the ids of 50 and 1 already in the
+    # relation, then the hash table nodes would contain:
+    # => {50=>true, 1=>true}
+    nodes = Hash.new
+    ways = Hash.new
+    relations = Hash.new
     self.members.each do |m|
       if (m[0] == "node")
         n = Node.find(:first, :conditions => ["id = ?", m[1]])
         unless n and n.visible 
           return false
         end
+        if nodes[m[1]]
+          return false
+        else
+          nodes[m[1]] = true
+        end
       elsif (m[0] == "way")
         w = Way.find(:first, :conditions => ["id = ?", m[1]])
         unless w and w.visible and w.preconditions_ok?
           return false
         end
+        if ways[m[1]]
+          return false
+        else
+          ways[m[1]] = true
+        end
       elsif (m[0] == "relation")
         e = Relation.find(:first, :conditions => ["id = ?", m[1]])
         unless e and e.visible and e.preconditions_ok?
           return false
         end
+        if relations[m[1]]
+          return false
+        else
+          relations[m[1]] = true
+        end
       else
         return false
       end
@@ -261,4 +268,8 @@ class Relation < ActiveRecord::Base
     return false
   end
 
+  # Temporary method to match interface to nodes
+  def tags_as_hash
+    return self.tags
+  end
 end
index 79102853e9c218a362a52eedd5acf30b52233e19..9ff4f46f3b8b0b6ae4fb1b02ba49ccab25a71b66 100644 (file)
@@ -1,30 +1,23 @@
 class RelationMember < ActiveRecord::Base
   set_table_name 'current_relation_members'
-
-  # problem with RelationMember is that it may link to any one 
-  # object (a node, a way, another relation), and belongs_to is
-  # not flexible enough for that. So we do this, which is ugly,
-  # but fortunately rails won't actually run the SQL behind that
-  # unless someone really accesses .node, .way, or
-  # .relation - which is what we do below based on member_type.
-  # (and no: the :condition on belongs_to doesn't work here as
-  # it is a condition on the *referenced* object not the 
-  # *referencing* object!)
   
-  belongs_to :node, :foreign_key => "member_id"
-  belongs_to :way, :foreign_key => "member_id"
-  belongs_to :relation, :foreign_key => "member_id"
+  belongs_to :member, :polymorphic => true, :foreign_type => :member_class
+  belongs_to :relation, :foreign_key => :id
+
+  def after_find
+    self[:member_class] = self.member_type.capitalize
+  end
 
-  # so we define this "member" function that returns whatever it
-  # is.
-  def member()
-    return (member_type == "node") ? node : (member_type == "way") ? way : relation
+  def after_initialize
+    self[:member_class] = self.member_type.capitalize
   end
 
-  # NOTE - relations are SUBJECTS of memberships. The fact that nodes, 
-  # ways, and relations can be the OBJECT of a membership,
-  # i.e. a node/way/relation can be referenced throgh a
-  # RelationMember object, is NOT modelled in rails, i.e. these links
-  # have to be resolved manually, on demand. 
+  def before_save
+    self.member_type = self[:member_class].downcase
+  end
+
+  def member_type=(type)
+    self[:member_type] = type
+    self[:member_class] = type.capitalize
+  end
 end
index 0e9e7bcc1598df1ffd54f447f18afa75d6fbae1a..d28c2c6f42f98a1bf6bff40b769c9707b0e71515 100644 (file)
@@ -183,7 +183,7 @@ class Trace < ActiveRecord::Base
     # If there are any existing points for this trace then delete
     # them - we check for existing points first to avoid locking
     # the table in the common case where there aren't any.
-    if Tracepoint.exists?(['gpx_id = ?', self.id])
+    if Tracepoint.find(:first, :conditions => ['gpx_id = ?', self.id])
       Tracepoint.delete_all(['gpx_id = ?', self.id])
     end
 
index 8648e555d2ec4c62fd9332fc6fd3bfbb6e9530e2..bf3cdd7c06557978e40614360394b22e85ba78db 100644 (file)
@@ -1,4 +1,6 @@
-class Tracepoint < GeoRecord
+class Tracepoint < ActiveRecord::Base
+  include GeoRecord
+
   set_table_name 'gps_points'
 
   validates_numericality_of :trackid, :only_integer => true
index ea027fb47908d2148ae7d07476843972aeeebd86..34afc6585041e7fa3c723dc08bde40a8b24af83c 100644 (file)
@@ -1,15 +1,19 @@
 class Way < ActiveRecord::Base
   require 'xml/libxml'
 
+  set_table_name 'current_ways'
+
   belongs_to :user
 
-  has_many :nodes, :through => :way_nodes, :order => 'sequence_id'
+  has_many :old_ways, :foreign_key => 'id', :order => 'version'
+
   has_many :way_nodes, :foreign_key => 'id', :order => 'sequence_id'
-  has_many :way_tags, :foreign_key => 'id'
+  has_many :nodes, :through => :way_nodes, :order => 'sequence_id'
 
-  has_many :old_ways, :foreign_key => 'id', :order => 'version'
+  has_many :way_tags, :foreign_key => 'id'
 
-  set_table_name 'current_ways'
+  has_many :containing_relation_members, :class_name => "RelationMember", :as => :member
+  has_many :containing_relations, :class_name => "Relation", :through => :containing_relation_members, :source => :relation, :extend => ObjectFinder
 
   def self.from_xml(xml, create=false)
     begin
@@ -270,4 +274,9 @@ class Way < ActiveRecord::Base
     self.delete_with_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 (file)
index 0000000..ee5f22c
--- /dev/null
@@ -0,0 +1,22 @@
+<tr>
+  <th>Edited at:</th>
+  <td><%= h(common_details.timestamp) %></td>
+</tr>
+
+<% if common_details.user.data_public %>
+  <tr>
+    <th>Edited by:</th>
+    <td><%= link_to h(common_details.user.display_name), :controller => "user", :action => "view", :display_name => common_details.user.display_name %></td>
+  </tr>
+<% end %>
+
+<% unless common_details.tags_as_hash.empty? %>
+  <tr valign="top">
+    <th>Tags:</th>
+    <td>
+      <table padding="0">
+        <%= render :partial => "tag", :collection => common_details.tags_as_hash %>
+      </table>
+    </td>
+  </tr>      
+<% end %>
diff --git a/app/views/browse/_containing_relation.rhtml b/app/views/browse/_containing_relation.rhtml
new file mode 100644 (file)
index 0000000..b33dea5
--- /dev/null
@@ -0,0 +1,8 @@
+<tr>
+  <td>
+    <%= 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 %>
+  </td>
+</tr>
diff --git a/app/views/browse/_map.rhtml b/app/views/browse/_map.rhtml
new file mode 100644 (file)
index 0000000..ad2d2d3
--- /dev/null
@@ -0,0 +1,64 @@
+<%= javascript_include_tag '/openlayers/OpenLayers.js' %>
+<%= javascript_include_tag '/openlayers/OpenStreetMap.js' %>
+<%= javascript_include_tag 'map.js' %>
+<td align="right">
+  <% if map.visible %>
+    <div id="small_map" style="width:250px; height: 300px; border: solid 1px black">
+    </div>
+    <span id="loading">Loading...</span>
+    <a id="larger_map" href=""></a>
+  <% else %>
+    Deleted
+  <% end %>
+</td>
+<script type="text/javascript">
+  function init() {
+    var obj_type = "<%= map.class.name.downcase %>";
+    var obj_id = <%= map.id %>;
+    var url = "/api/<%= "#{API_VERSION}" %>/<%= map.class.name.downcase %>/<%= map.id %>";
+
+    if (obj_type != "node") {
+      url += "/full";
+    }
+
+    var map = createMap("small_map", {
+      controls: [ new OpenLayers.Control.Navigation() ]
+    });
+
+    var osm_layer = new OpenLayers.Layer.GML("OSM", url, {
+      format: OpenLayers.Format.OSM,
+      projection: new OpenLayers.Projection("EPSG:4326")
+    });
+
+    osm_layer.events.register("loadend", osm_layer, function() {
+      $("loading").innerHTML = "";
+
+      if (this.features.length) { 
+        var extent =  this.features[0].geometry.getBounds();
+
+        for (var i = 1; i < this.features.length; i++) {
+          extent.extend(this.features[i].geometry.getBounds());
+        }
+
+        if (extent) {
+          this.map.zoomToExtent(extent);
+        } else {
+          this.map.zoomToMaxExtent();
+       }
+
+       var center = getMapCenter();
+        $("larger_map").href = '/?lat='+center.lat+'&lon='+center.lon+'&zoom='+this.map.getZoom();
+        $("larger_map").innerHTML = "View Larger Map";
+      } else {
+        $("small_map").style.display = "none";
+      }
+    });
+
+    map.addLayer(osm_layer);
+
+    osm_layer.loadGML();
+    osm_layer.loaded = true;
+  }
+
+  window.onload = init;
+</script>
diff --git a/app/views/browse/_navigation.rhtml b/app/views/browse/_navigation.rhtml
new file mode 100644 (file)
index 0000000..57e724d
--- /dev/null
@@ -0,0 +1,13 @@
+<div style="float:right; text-align:center; width: 250px;">
+  <% if @prev %>
+    &lt;
+    <%= 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 %>
+    &gt;
+  <% end %>
+</div>
diff --git a/app/views/browse/_node_details.rhtml b/app/views/browse/_node_details.rhtml
new file mode 100644 (file)
index 0000000..e3d11e0
--- /dev/null
@@ -0,0 +1,19 @@
+<table>
+
+  <%= render :partial => "common_details", :object => node_details %>
+
+  <% unless node_details.ways.empty? and node_details.containing_relation_members.empty? %>
+    <tr valign="top">
+      <th>Part of:</th>
+      <td>
+        <table padding="0">
+          <% node_details.ways.each do |way| %>
+            <tr><td><%= link_to "Way " + way.id.to_s, :action => "way", :id => way.id.to_s %></td></tr>
+          <% end %>
+          <%= render :partial => "containing_relation", :collection => node_details.containing_relation_members %>
+        </table>
+      </td>
+    </tr>      
+  <% end %>
+
+</table>
diff --git a/app/views/browse/_relation_details.rhtml b/app/views/browse/_relation_details.rhtml
new file mode 100644 (file)
index 0000000..b8874d9
--- /dev/null
@@ -0,0 +1,27 @@
+<table>
+
+  <%= render :partial => "common_details", :object => relation_details %>
+
+  <% unless relation_details.relation_members.empty? %>
+    <tr valign="top">
+      <th>Members:</th>
+      <td>
+        <table padding="0">
+          <%= render :partial => "relation_member", :collection => relation_details.relation_members %>
+        </table>
+      </td>
+    </tr>   
+  <% end %>
+
+  <% unless relation_details.containing_relation_members.empty? %>
+    <tr>
+      <th>Part of:</th>
+      <td>
+        <table padding="0">
+          <%= render :partial => "containing_relation", :collection => relation_details.containing_relation_members %>
+        </table>
+      </td>
+    </tr>      
+  <% end %>
+
+</table>
diff --git a/app/views/browse/_relation_member.rhtml b/app/views/browse/_relation_member.rhtml
new file mode 100644 (file)
index 0000000..516d9e3
--- /dev/null
@@ -0,0 +1,10 @@
+<tr>
+  <td>
+    <%= 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 %>
+  </td>
+</tr> 
diff --git a/app/views/browse/_start.rhtml b/app/views/browse/_start.rhtml
new file mode 100644 (file)
index 0000000..2e120c1
--- /dev/null
@@ -0,0 +1,13 @@
+<div>
+  <div style="text-align: center">
+    <p style="margin-top: 10px; margin-bottom: 20px">
+      <a id="browse_select_view" href="#">View data for current map view</a>
+      <br />
+      <a id="browse_select_box" href="#">Manually select a different area</a>  
+    </p>
+  </div>
+  <div id="browse_status" style="text-align: center; display: none">
+  </div>
+  <div id="browse_content">
+  </div>
+</div>
diff --git a/app/views/browse/_tag.rhtml b/app/views/browse/_tag.rhtml
new file mode 100644 (file)
index 0000000..8a57387
--- /dev/null
@@ -0,0 +1,3 @@
+<tr>
+  <td><%= h(tag[0]) %> = <%= h(tag[1]) %></td>
+</tr> 
diff --git a/app/views/browse/_way_details.rhtml b/app/views/browse/_way_details.rhtml
new file mode 100644 (file)
index 0000000..99b8e1d
--- /dev/null
@@ -0,0 +1,27 @@
+<table>
+
+  <%= render :partial => "common_details", :object => way_details %>
+
+  <tr valign="top">
+    <th>Nodes:</th>
+    <td>
+      <table padding="0">
+        <% way_details.way_nodes.each do |wn| %>
+          <tr><td><%= link_to "Node " + wn.node_id.to_s, :action => "node", :id => wn.node_id.to_s %></td></tr>
+        <% end %>
+      </table>
+    </td>
+  </tr>
+
+  <% unless way_details.containing_relation_members.empty? %>
+    <tr>
+      <th>Part of:</th>
+      <td>
+        <table padding="0">
+          <%= render :partial => "containing_relation", :collection => way_details.containing_relation_members %>
+        </table>
+      </td>
+    </tr>      
+  <% end %>
+
+</table>
diff --git a/app/views/browse/index.rhtml b/app/views/browse/index.rhtml
new file mode 100644 (file)
index 0000000..2cd5cc9
--- /dev/null
@@ -0,0 +1,12 @@
+<h2><%= @nodes.length %> Recently Changed Nodes</h2> 
+<ul>
+<% @nodes.each do |node| 
+   name = node.tags_as_hash['name'].to_s 
+   if name.length == 0:
+     name = "(No name)"
+   end
+   name = name + " - " + node.id.to_s 
+%>
+   <li><%= link_to h(name), :action => "node", :id => node.id %></li>
+<% end %>
+</ul>
diff --git a/app/views/browse/node.rhtml b/app/views/browse/node.rhtml
new file mode 100644 (file)
index 0000000..e2db18f
--- /dev/null
@@ -0,0 +1,20 @@
+<table width="100%">
+  <tr>
+    <td>
+      <h2>Node: <%= h(@name) %></h2>
+    </td>
+    <td>
+      <%= render :partial => "navigation" %>
+    </td>
+  </tr>
+  <tr valign="top">
+    <td>
+      <%= render :partial => "node_details", :object => @node %>
+      <hr />
+      <%= link_to "Download XML", :controller => "node", :action => "read" %>
+      or
+      <%= link_to "view history", :action => "node_history" %>
+    </td>
+    <%= render :partial => "map", :object => @node %>
+  </tr>
+</table>
diff --git a/app/views/browse/node_history.rhtml b/app/views/browse/node_history.rhtml
new file mode 100644 (file)
index 0000000..5fd3278
--- /dev/null
@@ -0,0 +1,16 @@
+<h2>Node History: <%= h(@name) %></h2>
+
+<table width="100%">
+  <tr valign="top">
+    <td>
+      <% @node.old_nodes.reverse.each do |node| %>
+        <%= render :partial => "node_details", :object => node %>
+        <hr />
+      <% end %>
+      <%= link_to "Download XML", :controller => "old_node", :action => "history" %>
+      or
+      <%= link_to "view details", :action => "node" %>
+    </td>
+    <%= render :partial => "map", :object => @node %>
+  </tr>
+</table>
diff --git a/app/views/browse/relation.rhtml b/app/views/browse/relation.rhtml
new file mode 100644 (file)
index 0000000..f981938
--- /dev/null
@@ -0,0 +1,20 @@
+<table width="100%">
+  <tr>
+    <td>
+      <h2>Relation: <%= h(@name) %></h2>
+    </td>
+    <td>
+      <%= render :partial => "navigation" %>
+    </td>
+  </tr>
+  <tr valign="top">
+    <td>
+      <%= render :partial => "relation_details", :object => @relation %>
+      <hr />
+      <%= link_to "Download XML", :controller => "relation", :action => "read" %>
+      or
+      <%= link_to "view history", :action => "relation_history" %>
+    </td>
+    <%= render :partial => "map", :object => @relation %>
+  </tr>
+</table>
diff --git a/app/views/browse/relation_history.rhtml b/app/views/browse/relation_history.rhtml
new file mode 100644 (file)
index 0000000..60e0ffd
--- /dev/null
@@ -0,0 +1,16 @@
+<h2>Relation History: <%= h(@name) %></h2>
+
+<table width="100%">
+  <tr valign="top">
+    <td>
+      <% @relation.old_relations.reverse.each do |relation| %>
+        <%= render :partial => "relation_details", :object => relation %>
+        <hr />
+      <% end %>
+      <%= link_to "Download XML", :controller => "old_relation", :action => "history" %>
+      or
+      <%= link_to "view details", :action => "relation" %>
+    </td>
+    <%= render :partial => "map", :object => @relation %>
+  </tr>
+</table>
diff --git a/app/views/browse/start.rjs b/app/views/browse/start.rjs
new file mode 100644 (file)
index 0000000..f22796a
--- /dev/null
@@ -0,0 +1,481 @@
+page.replace_html :sidebar_title, 'Data'
+page.replace_html :sidebar_content, :partial => 'start'
+page << <<EOJ
+  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() {
+    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");
+
+    browseActive = true;
+  }
+
+  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;
+
+  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 (file)
index 0000000..2e86e65
--- /dev/null
@@ -0,0 +1,20 @@
+<table width="100%">
+  <tr>
+    <td>
+      <h2>Way: <%= h(@name) %></h2>
+    </td>
+    <td>
+      <%= render :partial => "navigation" %>
+    </td>
+  </tr>
+  <tr valign="top">
+    <td>
+      <%= render :partial => "way_details", :object => @way %>
+      <hr />
+      <%= link_to "Download XML", :controller => "way", :action => "read" %>
+      or
+      <%= link_to "view history", :action => "way_history" %>
+    </td>
+    <%= render :partial => "map", :object => @way %>
+  </tr>
+</table>
diff --git a/app/views/browse/way_history.rhtml b/app/views/browse/way_history.rhtml
new file mode 100644 (file)
index 0000000..f44405f
--- /dev/null
@@ -0,0 +1,16 @@
+<h2>Way History: <%= h(@name) %></h2>
+
+<table width="100%">
+  <tr valign="top">
+    <td>
+      <% @way.old_ways.reverse.each do |way| %>
+        <%= render :partial => "way_details", :object => way %>
+        <hr />
+      <% end %>
+      <%= link_to "Download XML", :controller => "old_way", :action => "history" %>
+      or
+      <%= link_to "view details", :action => "way" %>
+    </td>
+    <%= render :partial => "map", :object => @way %>
+  </tr>
+</table>
index 6c40eaeb58af046d383bb988b20816f823055e56..0a5203a66053ce49f2e5c8eaf9f62974c6efa982 100644 (file)
@@ -66,7 +66,7 @@
       removeMarkerFromMap(marker);
     }
 
-    marker = addMarkerToMap(merc, null, "Diary entry location");
+    marker = addMarkerToMap(lonlat, null, "Diary entry location");
   }
 
   function openMap() {
index 2babcc982e2d78ac5df6380d10836b21cfe94736..47cf9ce6bf21feddbcec4cc630233677fd30205c 100644 (file)
@@ -246,7 +246,9 @@ page << <<EOJ
     bounds.transform(epsg4326, epsg900913);
     var zoom = map.getZoomForExtent(bounds);
     
-    html += '<br /><small><a href="http://#{SERVER_URL}/?lat='+center.lat+'&lon='+center.lon+'&zoom='+zoom+markerUrl+'">View Larger Map</a></small>';
+    var layers = getMapLayers();
+
+    html += '<br /><small><a href="http://#{SERVER_URL}/?lat='+center.lat+'&lon='+center.lon+'&zoom='+zoom+'&layers='+layers+markerUrl+'">View Larger Map</a></small>';
 
     $("export_html_text").value = html;
 
index 33e02449d7dcab70a16a1347e26ec9ada740c3ea..52e7b6b242847869e54c8feb7f2f1ab9530c74bb 100644 (file)
@@ -52,7 +52,7 @@
         <li><%= link_to 'Export', {:controller => 'site', :action => 'export'}, {:id => 'exportanchor', :title => 'export map data', :class => exportclass} %></li>
         <% end %>
         <li><%= link_to 'GPS Traces', {:controller => 'trace', :action => 'list'}, {:id => 'traceanchor', :title => 'manage traces', :class => traceclass} %></li>
-        <li><%= link_to 'User Diaries', {:controller => 'diary_entry', :action => 'list'}, {:id => 'diaryanchor', :title => 'view user diaries', :class => diaryclass} %></li>
+        <li><%= link_to 'User Diaries', {:controller => 'diary_entry', :action => 'list', :display_name => nil}, {:id => 'diaryanchor', :title => 'view user diaries', :class => diaryclass} %></li>
       </ul>
     </div>
 
       </div>
       <% end %>
 
-      <% if API_STATUS == :offline %>
+      <% if OSM_STATUS == :database_offline or OSM_STATUS == :api_offline %>
       <div id="alert">
         The OpenStreetMap database is currently offline while
         essential database maintenance work is carried out.
       </div>
-      <% elsif API_STATUS == :readonly %>
+      <% elsif OSM_STATUS == :api_readonly %>
       <div id="alert">
         The OpenStreetMap database is currently in read-only mode while
         essential database maintenance work is carried out.
index dbd71f0173fdcafb7294b7160fe361f821cefafa..6d45d33dd33f0eefca339ad37039ea59d35c9b2f 100644 (file)
@@ -10,6 +10,4 @@
     <td><%= button_to 'Mark as read', :controller => 'message', :action => 'mark', :message_id => message_summary.id, :mark => 'read' %></td>
   <% end %>
   <td><%= button_to 'Reply', :controller => 'message', :action => 'reply', :message_id => message_summary.id %></td>
-  <td><%= button_to 'Delete', :controller => 'message', :action => 'destroy', :message_id => message_summary.id %></td>
-
 </tr>
index 723cb1f10277bcffcac5c8dc9b725f54c06b2411..d7bb18f8e59461492982ab5ec2ccea114a2b5499 100644 (file)
@@ -1,5 +1,5 @@
-<% display_name = User.find_by_id(params[:user_id] || @user_id).display_name %>
-<% title = params[:message] ? params[:message][:title] : params[:title] %>
+<% user_id = params[:user_id] || @user_id %>
+<% display_name = User.find_by_id(user_id).display_name %>
 
 <h2>Send a new message to <%= h(display_name) %></h2>
 
 
 <%= error_messages_for 'message' %>
 
-<% form_for :message, :url => {:user_id => params[:user_id] || @user_id,  :action => "new" } do |f| %>
+<% form_for :message, :url => { :action => "new", :user_id => user_id } do |f| %>
   <table>
     <tr valign="top">
       <th>Subject</th>
-      <td><%= text_field_tag 'message[title]', title, :size => 60, :value => @title %></td>
+      <td><%= f.text_field :title, :size => 60, :value => @title %></td>
     </tr>
     <tr valign="top">
       <th>Body</th>
@@ -22,7 +22,7 @@
     </tr>
     <tr>
       <th></th>
-      <td><%= submit_tag 'Send', :action => 'new' %></td>
+      <td><%= submit_tag 'Send' %></td>
     </tr>
   </table>
 <% end %>
index eccb26050fb7948cabf70b54355aad685226b50c..d44859029dea35f7cadda8002239b8120943d419 100644 (file)
@@ -32,7 +32,6 @@
   <tr>
     <td><%= button_to 'Reply', :controller => 'message', :action => 'reply', :message_id => @message.id %></td>
     <td><%= button_to 'Mark as unread', :controller => 'message', :action => 'mark', :message_id => @message.id, :mark => 'unread' %></td>
-    <td><%= button_to 'Delete', :controller => 'message', :action => 'destroy', :message_id => @message.id %></td>
     <td><%= link_to 'Back to inbox', :controller => 'message', :action => 'inbox', :display_name => @user.display_name %></td>
   </tr>
 </table>
index 05b1f0886b9d59eb37e67ba19ca7ad9a0c06dc27..9d000e9d346d6b8a20709b4edae4589e02e78361 100644 (file)
@@ -1,5 +1,17 @@
 ***************************************************************************
-Please do not reply to this email. Use the OpenStreetMap web site to reply.
+*                                                                         *
+*                   Please do not reply to this email.                    *
+*                Use the OpenStreetMap web site to reply.                 *
+*                                                                         *
+*               Bitte antworten Sie nicht auf diese E-Mail.               *
+*          Verwenden Sie die OpenStreetMap Website zum antworten.         *
+*                                                                         *
+*                 Por favor, no responda a este mensaje.                  *
+*           Utilice el OpenStreetMap sitio web para responder.            *
+*                                                                         *
+*            S’il vous plaît de ne pas répondre Ã  ce message.             *
+*           Utilisez le OpenStreetMap site Web pour y répondre.           *
+*                                                                         *
 ***************************************************************************
 
 Hi <%= @to_user %>,
@@ -16,5 +28,17 @@ and you can comment at <%= @commenturl %>
 or reply at <%= @replyurl %>
 
 ***************************************************************************
-Please do not reply to this email. Use the OpenStreetMap web site to reply.
+*                                                                         *
+*                   Please do not reply to this email.                    *
+*                Use the OpenStreetMap web site to reply.                 *
+*                                                                         *
+*               Bitte antworten Sie nicht auf diese E-Mail.               *
+*          Verwenden Sie die OpenStreetMap Website zum antworten.         *
+*                                                                         *
+*                 Por favor, no responda a este mensaje.                  *
+*           Utilice el OpenStreetMap sitio web para responder.            *
+*                                                                         *
+*            S’il vous plaît de ne pas répondre Ã  ce message.             *
+*           Utilisez le OpenStreetMap site Web pour y répondre.           *
+*                                                                         *
 ***************************************************************************
index 82efbb1a9690f6b1d624ca12eacc042dfa56a966..55e464e1550e50421376302d276d529a9e6f9f09 100644 (file)
@@ -1,5 +1,17 @@
 ***************************************************************************
-Please do not reply to this email. Use the OpenStreetMap web site to reply.
+*                                                                         *
+*                   Please do not reply to this email.                    *
+*                Use the OpenStreetMap web site to reply.                 *
+*                                                                         *
+*               Bitte antworten Sie nicht auf diese E-Mail.               *
+*          Verwenden Sie die OpenStreetMap Website zum antworten.         *
+*                                                                         *
+*                 Por favor, no responda a este mensaje.                  *
+*           Utilice el OpenStreetMap sitio web para responder.            *
+*                                                                         *
+*            S’il vous plaît de ne pas répondre Ã  ce message.             *
+*           Utilisez le OpenStreetMap site Web pour y répondre.           *
+*                                                                         *
 ***************************************************************************
 
 Hi <%= @to_user %>,
@@ -14,5 +26,17 @@ You can also read the message at <%= @readurl %>
 and you can reply at <%= @replyurl %>
 
 ***************************************************************************
-Please do not reply to this email. Use the OpenStreetMap web site to reply.
+*                                                                         *
+*                   Please do not reply to this email.                    *
+*                Use the OpenStreetMap web site to reply.                 *
+*                                                                         *
+*               Bitte antworten Sie nicht auf diese E-Mail.               *
+*          Verwenden Sie die OpenStreetMap Website zum antworten.         *
+*                                                                         *
+*                 Por favor, no responda a este mensaje.                  *
+*           Utilice el OpenStreetMap sitio web para responder.            *
+*                                                                         *
+*            S’il vous plaît de ne pas répondre Ã  ce message.             *
+*           Utilisez le OpenStreetMap site Web pour y répondre.           *
+*                                                                         *
 ***************************************************************************
index 43853dcf028b3293e531aa1e546dfd42c69e6b28..5b4e74b34f4b130c072b3f6754155c773a47db0c 100644 (file)
@@ -39,9 +39,8 @@
                        :complete => "endSearch()",
                        :url => { :controller => :geocoder, :action => :search }) do %>
       <%= text_field_tag :query, h(params[:query]) %>
-  
-      <%= submit_tag 'Go' %></td>
-      <% end %>
+      <%= submit_tag "Go" %>
+    <% end %>
     </span>
     <p id="search_active">Searching...</p>
     </div>
index ac54cb5e5f74c101f962b92fdb3c32657e479bd6..a3896bc4824ea99966f5850592fb100c3ed1ac76 100644 (file)
@@ -1,8 +1,8 @@
-<% if API_STATUS == :offline %>
+<% if OSM_STATUS == :database_offline or OSM_STATUS == :api_offline %>
 <p>The OpenStreetMap database is currently offline while
    essential database maintenance work is carried out.
 </p>
-<% elsif API_STATUS == :readonly %>
+<% elsif OSM_STATUS == :api_readonly %>
 <p>The OpenStreetMap database is currently in read-only mode while
    essential database maintenance work is carried out.
 </p>
 
     resizeMap();
   }
+
+  function maximiseMap() {
+    $("left").style.display = "none";
+    $("greeting").style.display = "none";
+    $("tabnav").style.display = "none";
+
+    $("content").style.top = "10px";
+    $("content").style.left = "10px";
+
+    handleResize();
+  }
   
+  function minimiseMap() {
+    $("left").style.display = "";
+    $("greeting").style.display = "";
+    $("tabnav").style.display = "";
+
+    $("content").style.top = "35px";
+    $("content").style.left = "192px";
+
+    handleResize();
+  }
+
   handleResize();
 
   window.onload = handleResize;
index 94b24debf54d41162759c182f069ab03e29d8108..2cca4d5292317b8a3728e94ed33e3ec6e057312a 100644 (file)
@@ -8,6 +8,14 @@
 <%= render :partial => 'key' %>
 <%= render :partial => 'search' %>
 
+<noscript>
+  <div id="noscript">
+    <p>You are either using a browser that doesn't support javascript, or you have disabled javascript.</p>
+    <p>OpenStreetMap uses javascript for its slippy map.</p>
+    <p>You may want to try the <a href="http://tah.openstreetmap.org/Browse/">Tiles@Home static tile browser</a> if you are unable to enable javascript.</p>
+  </div>
+</noscript>
+
 <div id="map">
 <div id="permalink"><a href="/" id="permalinkanchor">Permalink</a></div>
 </div> 
@@ -21,7 +29,7 @@
 <tr>
 <td colspan="2" align="center">
 Licensed under the Creative Commons Attribution-Share Alike 2.0 license
-by the OpenStreetMap project and it's contributors.
+by the OpenStreetMap project and its contributors.
 </td>
 </table>
 </div>
@@ -50,8 +58,8 @@ by the OpenStreetMap project and it's contributors.
 <% lat = h(params['mlat']) %>
 <% zoom =  h(params['zoom'] || '12') %>
 <% layers = h(params['layers']) %>
-<% elsif cookies.key?("location") %>
-<% lon,lat,zoom,layers = cookies["location"].split(",") %>
+<% elsif cookies.key?("_osm_location") %>
+<% lon,lat,zoom,layers = cookies["_osm_location"].split("|") %>
 <% elsif @user and !@user.home_lon.nil? and !@user.home_lat.nil? %> 
 <% lon =  @user.home_lon %>
 <% lat =  @user.home_lat %>
@@ -68,8 +76,8 @@ by the OpenStreetMap project and it's contributors.
 <% lon =  '-0.1' %>
 <% lat =  '51.5' %>
 <% zoom =  h(params['zoom'] || '5') %>
-<% layers = h(params['layers']) %>
 <% end %>
+<% layers = h(params['layers']) %>
 <% end %>
 
 <%= javascript_include_tag '/openlayers/OpenLayers.js' %>
@@ -86,6 +94,12 @@ by the OpenStreetMap project and it's 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 %>);
 
@@ -101,7 +115,7 @@ by the OpenStreetMap project and it's contributors.
     setMapCenter(centre, zoom);
     <% end %>
 
-    <% if layers %>
+    <% if !layers.nil? and !layers.empty? %>
     setMapLayers("<%= layers %>");
     <% end %>
 
@@ -118,6 +132,14 @@ by the OpenStreetMap project and it's contributors.
     handleResize();
   }
 
+  function toggleData() {
+    if (map.dataLayer.visibility) {
+      <%= remote_function :url => { :controller => 'browse', :action => 'start' } %>
+    } else {
+      closeSidebar();
+    }
+  }
+
   function getPosition() {
     return getMapCenter();
   }
@@ -140,7 +162,7 @@ by the OpenStreetMap project and it's contributors.
 
     updatelinks(lonlat.lon, lonlat.lat, zoom, layers);
 
-    document.cookie = "location=" + lonlat.lon + "," + lonlat.lat + "," + zoom + "," + layers;
+    document.cookie = "_osm_location=" + lonlat.lon + "|" + lonlat.lat + "|" + zoom + "|" + layers;
   }
 
   function resizeContent() {
diff --git a/app/views/site/offline.rhtml b/app/views/site/offline.rhtml
new file mode 100644 (file)
index 0000000..d97a6ca
--- /dev/null
@@ -0,0 +1,3 @@
+<p>The OpenStreetMap database is currently offline while
+   essential database maintenance work is carried out.
+</p>
index d4bdb9745522f9666834f5908c91906a3576bedb..f547f05db4ddeafd85fc1ad4e90888d5026317f9 100644 (file)
@@ -1,6 +1,10 @@
 <h2><%= h(@title) %></h2>
 
-<img src="<%= url_for :controller => 'trace', :action => 'picture', :id => @trace.id, :display_name => @trace.user.display_name %>">
+  <% if @trace.inserted %>
+    <img src="<%= url_for :controller => 'trace', :action => 'picture', :id => @trace.id, :display_name => @trace.user.display_name %>">
+  <% else %>
+    <span style="color:red">PENDING</span>
+  <% end %>
 
 <table border="0">
   <tr>
index 8b137891791fe96927ad78e64b0aad7bded08bdc..5577b7068889c6c2694644ed477bbea77cf21fd7 100644 (file)
@@ -1 +1,10 @@
+<h1>Confirm a user account</h1>
+
+<p>Press the confirm button below to activate your account.</p>
+
+<form method="post">
+<input type="hidden" name="confirm_string" value="<%= params[:confirm_string] %>">
+<input type="submit" name="confirm_action" value="Confrm">
+</form>
+
 
index 8aaab730289311ff083812694406de99cf4ec8ce..fb7573d2a5b13b7177c6381435f77e25c98ec3db 100644 (file)
@@ -5,8 +5,7 @@
 ENV['RAILS_ENV'] ||= 'production'
 
 # Specifies gem version of Rails to use when vendor/rails is not present
-# DO NOT BUMP THIS TO 2.0.2 AS THE LIVE SERVERS CAN'T RUN THAT
-RAILS_GEM_VERSION = '2.0.1' unless defined? RAILS_GEM_VERSION
+RAILS_GEM_VERSION = '2.0.2' unless defined? RAILS_GEM_VERSION
 
 # Set the server URL
 SERVER_URL = ENV['OSM_SERVER_URL'] || 'www.openstreetmap.org'
@@ -14,9 +13,14 @@ SERVER_URL = ENV['OSM_SERVER_URL'] || 'www.openstreetmap.org'
 # Application constants needed for routes.rb - must go before Initializer call
 API_VERSION = ENV['OSM_API_VERSION'] || '0.6'
 
-# Set to :readonly to put the API in read-only mode or :offline to
-# take it completely offline
-API_STATUS = :online
+# Set application status - possible settings are:
+#
+#   :online - online and operating normally
+#   :api_readonly - site online but API in read-only mode
+#   :api_offline - site online but API offline
+#   :database_offline - database offline with site in emergency mode
+#
+OSM_STATUS = :online
 
 # Bootstrap the Rails environment, frameworks, and default configuration
 require File.join(File.dirname(__FILE__), 'boot')
@@ -29,7 +33,9 @@ Rails::Initializer.run do |config|
 
   # Skip frameworks you're not going to use (only works if using vendor/rails).
   # To use Rails without a database, you must remove the Active Record framework
-  # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
+  if OSM_STATUS == :database_offline
+    config.frameworks -= [ :active_record ]
+  end
 
   # Only load the plugins named here, in the order given. By default, all plugins 
   # in vendor/plugins are loaded in alphabetical order.
index 8fc93577889287d385596897c027c9bd608c8168..430bcfac277d8d8e5113c74cf1c121821b43ef1e 100644 (file)
@@ -1 +1,3 @@
+require 'rubygems'
+gem 'composite_primary_keys', '= 0.9.93'
 require 'composite_primary_keys'
index 5ca4f5a5362efd90b288a636827732a174691cc8..592178474286fd009d36522163112fc4fe48baa0 100644 (file)
@@ -63,6 +63,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'
@@ -70,6 +80,7 @@ ActionController::Routing::Routes.draw do |map|
   map.connect '/export', :controller => 'site', :action => 'export'
   map.connect '/login', :controller => 'user', :action => 'login'
   map.connect '/logout', :controller => 'user', :action => 'logout'
+  map.connect '/offline', :controller => 'site', :action => 'offline'
   map.connect '/user/new', :controller => 'user', :action => 'new'
   map.connect '/user/save', :controller => 'user', :action => 'save'
   map.connect '/user/confirm', :controller => 'user', :action => 'confirm'
@@ -150,7 +161,6 @@ ActionController::Routing::Routes.draw do |map|
   map.connect '/message/read/:message_id', :controller => 'message', :action => 'read'
   map.connect '/message/mark/:message_id', :controller => 'message', :action => 'mark'
   map.connect '/message/reply/:message_id', :controller => 'message', :action => 'reply'
-  map.connect '/message/delete/:message_id', :controller => 'message', :action => 'destroy'
 
   # fall through
   map.connect ':controller/:id/:action'
similarity index 50%
rename from app/models/geo_record.rb
rename to lib/geo_record.rb
index 28ee037dd0841173aa977ffa141a25335ace8fb6..025bbe4a87b38b13a960751bdc2e63304e75e762 100644 (file)
@@ -1,11 +1,13 @@
-class GeoRecord < ActiveRecord::Base
-  before_save :update_tile
+module GeoRecord
+  def self.included(base)
+    base.extend(ClassMethods)
+  end
 
-  # This is a scaling factor for going between the lat and lon via the API
-  # and the longitude and latitude that is stored in the database
-  SCALE = 10000000
+  def before_save
+    self.update_tile
+  end
 
-  # Is this node within -90 <= latitude <= 90 and -180 <= longitude <= 180
+  # Is this node within -90 >= latitude >= 90 and -180 >= longitude >= 180
   # * returns true/false
   def in_world?
     return false if self.lat < -90 or self.lat > 90
@@ -13,32 +15,26 @@ class GeoRecord < ActiveRecord::Base
     return true
   end
 
-  def self.find_by_area(minlat, minlon, maxlat, maxlon, options)
-    self.with_scope(:find => {:conditions => OSM.sql_for_area(minlat, minlon, maxlat, maxlon)}) do
-      return self.find(:all, options)
-    end
-  end
-
   def update_tile
     self.tile = QuadTile.tile_for_point(lat, lon)
   end
 
   def lat=(l)
-    self.latitude = (l * SCALE).round
+    self.latitude = (l * 10000000).round
   end
 
   def lon=(l)
-    self.longitude = (l * SCALE).round
+    self.longitude = (l * 10000000).round
   end
 
   # Return WGS84 latitude
   def lat
-    return self.latitude.to_f / SCALE
+    return self.latitude.to_f / 10000000
   end
 
   # Return WGS84 longitude
   def lon
-    return self.longitude.to_f / SCALE
+    return self.longitude.to_f / 10000000
   end
 
   # Potlatch projections
@@ -50,11 +46,18 @@ class GeoRecord < ActiveRecord::Base
     -(lat2y(self.lat)-basey)*masterscale
   end
   
-  private
+private
   
   def lat2y(a)
     180/Math::PI * Math.log(Math.tan(Math::PI/4+a*(Math::PI/180)/2))
   end
 
+  module ClassMethods
+    def find_by_area(minlat, minlon, maxlat, maxlon, options)
+      self.with_scope(:find => {:conditions => OSM.sql_for_area(minlat, minlon, maxlat, maxlon)}) do
+        return self.find(:all, options)
+      end
+    end
+  end
 end
 
diff --git a/lib/object_finder.rb b/lib/object_finder.rb
new file mode 100644 (file)
index 0000000..26608a7
--- /dev/null
@@ -0,0 +1,5 @@
+module ObjectFinder
+  def visible
+    find :all, :conditions => "#{proxy_reflection.table_name}.visible = 1"
+  end
+end
index ae82eb54a9361af21ce84fb484d43aeb875c237a..af45a6b1f99cea1eb631c0e19a6863d76763a128 100644 (file)
                    attribution: attribution
                 });
                 map.addLayer(mapnik);
-            } else {
+            } else if (args.layer == "osmarender") {
                 var osmarender = new OpenLayers.Layer.OSM.Osmarender("Osmarender", {
                    displayOutsideMaxExtent: true,
                    wrapDateLine: true,
                    attribution: attribution
                 });
                 map.addLayer(osmarender);
+            } else if (args.layer == "cycle map") {
+                var cyclemap = new OpenLayers.Layer.OSM.CycleMap("Cycle Map", {
+                   displayOutsideMaxExtent: true,
+                   wrapDateLine: true,
+                   attribution: attribution
+                });
+                map.addLayer(cyclemap);
             }
 
             if (args.marker) {
index db5b8561638afd45ba4fa403c881b11196ddcf9f..291fd18fa29ff645520a1038b0dfa92e338d9bc6 100644 (file)
@@ -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
    });
 
@@ -34,6 +36,12 @@ function createMap(divName) {
    });
    map.addLayer(osmarender);
 
+   var cyclemap = new OpenLayers.Layer.OSM.CycleMap("Cycle Map", {
+      displayOutsideMaxExtent: true,
+      wrapDateLine: true
+   });
+   map.addLayer(cyclemap);
+
    var maplint = new OpenLayers.Layer.OSM.Maplint("Maplint", {
       displayOutsideMaxExtent: true,
       wrapDateLine: true
@@ -118,33 +126,41 @@ function getEventPosition(event) {
 }
 
 function getMapLayers() {
-   var layers = "";
+   var layerConfig = "";
 
-   for (var i=0; i< this.map.layers.length; i++) {
-      var layer = this.map.layers[i];
+   for (var layers = map.getLayersBy("isBaseLayer", true), i = 0; i < layers.length; i++) {
+      layerConfig += layers[i] == map.baseLayer ? "B" : "0";
+   }
 
-      if (layer.isBaseLayer) {
-         layers += (layer == this.map.baseLayer) ? "B" : "0";
-      } else {
-         layers += (layer.getVisibility()) ? "T" : "F";
-      }
+   for (var layers = map.getLayersBy("isBaseLayer", false), i = 0; i < layers.length; i++) {
+      layerConfig += layers[i].getVisibility() ? "T" : "F";
    }
 
-   return layers;
+   return layerConfig;
 }
 
-function setMapLayers(layers) {
-   for (var i=0; i < layers.length; i++) {
-      var layer = map.layers[i];
+function setMapLayers(layerConfig) {
+   var l = 0;
+
+   for (var layers = map.getLayersBy("isBaseLayer", true), i = 0; i < layers.length; i++) {
+      var c = layerConfig.charAt(l++);
+
+      if (c == "B") {
+         map.setBaseLayer(layers[i]);
+      }
+   }
+
+   while (layerConfig.charAt(l) == "B" || layerConfig.charAt(l) == "0") {
+      l++;
+   }
 
-      if (layer) {
-         var c = layers.charAt(i);
+   for (var layers = map.getLayersBy("isBaseLayer", false), i = 0; i < layers.length; i++) {
+      var c = layerConfig.charAt(l++);
 
-         if (c == "B") {
-            map.setBaseLayer(layer);
-         } else if ( (c == "T") || (c == "F") ) {
-            layer.setVisibility(c == "T");
-         }
+      if (c == "T") {
+         layers[i].setVisibility(true);
+      } else if(c == "F") {
+         layers[i].setVisibility(false);
       }
    }
 }
index c2acf2e0c8ef7035fe6d25cc20ca9265e6e28fc3..69e8840b2a33e5152da97bbc1a0d824cc225f416 100644 (file)
@@ -48,8 +48,8 @@ OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.TMS, {
     initialize: function(name, url, options) {
         options = OpenLayers.Util.extend({
             attribution: "Data by <a href='http://openstreetmap.org/'>OpenStreetMap</a>",
-            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"
@@ -155,6 +155,34 @@ OpenLayers.Layer.OSM.Osmarender = OpenLayers.Class(OpenLayers.Layer.OSM, {
     CLASS_NAME: "OpenLayers.Layer.OSM.Osmarender"
 });
 
+/**
+ * Class: OpenLayers.Layer.OSM.CycleMap
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.CycleMap = OpenLayers.Class(OpenLayers.Layer.OSM, {
+    /**
+     * Constructor: OpenLayers.Layer.OSM.CycleMap
+     *
+     * Parameters:
+     * name - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        var url = [
+            "http://a.thunderflames.org/tiles/cycle/",
+            "http://b.thunderflames.org/tiles/cycle/",
+            "http://c.thunderflames.org/tiles/cycle/"
+        ];
+        options = OpenLayers.Util.extend({ numZoomLevels: 17 }, options);
+        var newArguments = [name, url, options];
+        OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.OSM.CycleMap"
+});
+
 /**
  * Class: OpenLayers.Layer.OSM.Maplint
  *
index ede135a1fd56a0894378c64d4987e5b26e174341..721840fe4ec4d006d7b62d134313f7e90fffee54 100755 (executable)
Binary files a/public/potlatch/potlatch.swf and b/public/potlatch/potlatch.swf differ
index e1b877e039b3dc48ff34492b34e56bdca093f228..c0cef39cb5558bfd2324afa24f3d0f471c2b0765 100644 (file)
@@ -354,14 +354,21 @@ hides rule from IE5-Mac \*/
   padding: 0px;
 }
 
+#search_field form {
+  width: 100%;
+}
+
+#search_field input[type="text"] {
+  width: 116px;
+}
 
-.optionalbox input[type="text"] {
-  width: 110px;
+#search_field input[type="submit"] {
+  width: 26px;
 }
 
 .search_form {
   height: 16px;
-  padding-bottom: 2px;
+  padding-bottom: 6px;
 }
 
 #search_active {
@@ -447,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;
@@ -635,3 +654,10 @@ input[type="submit"] {
   width: 100%;
   text-align: center;
 }
+
+#noscript {
+  z-index:20000000;
+  position:absolute;
+  top:15px;
+  left:15px
+}