return
end
if node_ids.length == 0
- render :text => "<osm version='0.5'></osm>", :content_type => "text/xml"
+ render :text => "<osm version='#{API_VERSION}'></osm>", :content_type => "text/xml"
return
end
- relations = Array.new
-
doc = OSM::API.new.get_xml_doc
# get ways
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.
api = XML::Node.new 'api'
version = XML::Node.new 'version'
- version['minimum'] = '0.5';
- version['maximum'] = '0.5';
+ version['minimum'] = "#{API_VERSION}";
+ version['maximum'] = "#{API_VERSION}";
api << version
area = XML::Node.new 'area'
area['maximum'] = MAX_REQUEST_AREA.to_s;
# 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])
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
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
# phrase from that, we can also put the error message into the status
# message. For now, rails won't let us)
def report_error(message)
- render :nothing => true, :status => :bad_request
+ render :text => message, :status => :bad_request
# Todo: some sort of escaping of problem characters in the message
response.headers['Error'] = message
end
def get_auth_data
if request.env.has_key? 'X-HTTP_AUTHORIZATION' # where mod_rewrite might have put it
authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split
+ elsif request.env.has_key? 'REDIRECT_X_HTTP_AUTHORIZATION' # mod_fcgi
+ authdata = request.env['REDIRECT_X_HTTP_AUTHORIZATION'].to_s.split
elsif request.env.has_key? 'HTTP_AUTHORIZATION' # regular location
authdata = request.env['HTTP_AUTHORIZATION'].to_s.split
end
old_relation.user_id = relation.user_id
old_relation.timestamp = relation.timestamp
old_relation.id = relation.id
+ old_relation.version = relation.version
old_relation.members = relation.members
old_relation.tags = relation.tags
return old_relation
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
el1['user'] = self.user.display_name if self.user.data_public?
+ el1['version'] = self.version.to_s
self.old_members.each do |member|
e = XML::Node.new 'member'
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
old_way.user_id = way.user_id
old_way.timestamp = way.timestamp
old_way.id = way.id
+ old_way.version = way.version
old_way.nds = way.nds
old_way.tags = way.tags
return old_way
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
el1['user'] = self.user.display_name if self.user.data_public?
+ el1['version'] = self.version.to_s
self.old_nodes.each do |nd| # FIXME need to make sure they come back in the right order
e = XML::Node.new 'nd'
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
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
p.string = xml
doc = p.parse
- relation = Relation.new
-
doc.find('//osm/relation').each do |pt|
- if !create and pt['id'] != '0'
- relation.id = pt['id'].to_i
- end
+ return Relation.from_xml_node(pt, create)
+ end
+ rescue
+ return nil
+ end
+ end
- if create
- relation.timestamp = Time.now
- relation.visible = true
- else
- if pt['timestamp']
- relation.timestamp = Time.parse(pt['timestamp'])
- end
- end
+ def self.from_xml_node(pt, create=false)
+ relation = Relation.new
- pt.find('tag').each do |tag|
- relation.add_tag_keyval(tag['k'], tag['v'])
- end
+ if !create and pt['id'] != '0'
+ relation.id = pt['id'].to_i
+ end
- pt.find('member').each do |member|
- relation.add_member(member['type'], member['ref'], member['role'])
- end
+ relation.version = pt['version']
+
+ if create
+ relation.timestamp = Time.now
+ relation.visible = true
+ else
+ if pt['timestamp']
+ relation.timestamp = Time.parse(pt['timestamp'])
end
- rescue
- relation = nil
+ end
+
+ pt.find('tag').each do |tag|
+ relation.add_tag_keyval(tag['k'], tag['v'])
+ end
+
+ pt.find('member').each do |member|
+ relation.add_member(member['type'], member['ref'], member['role'])
end
return relation
el1['id'] = self.id.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
+ el1['version'] = self.version.to_s
user_display_name_cache = {} if user_display_name_cache.nil?
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
def save_with_history!
Relation.transaction do
t = Time.now
+ self.version += 1
self.timestamp = t
self.save!
tags = self.tags
-
RelationTag.delete_all(['id = ?', self.id])
-
tags.each do |k,v|
tag = RelationTag.new
tag.k = k
end
members = self.members
-
RelationMember.delete_all(['id = ?', self.id])
-
members.each do |n|
mem = RelationMember.new
mem.id = self.id
end
end
+ def delete_with_history(user)
+ if self.visible
+ if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", self.id ])
+ raise OSM::APIPreconditionFailedError.new
+ else
+ self.user_id = user.id
+ self.tags = []
+ self.members = []
+ self.visible = false
+ save_with_history!
+ end
+ else
+ raise OSM::APIAlreadyDeletedError.new
+ end
+ end
+
+ def update_from(new_relation, user)
+ if !new_relation.preconditions_ok?
+ raise OSM::APIPreconditionFailedError.new
+ elsif new_relation.version != version
+ raise OSM::APIVersionMismatchError.new(new_relation.version, version)
+ else
+ self.user_id = user.id
+ self.tags = new_relation.tags
+ self.members = new_relation.members
+ self.visible = true
+ save_with_history!
+ end
+ 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
return false
end
+ # Temporary method to match interface to nodes
+ def tags_as_hash
+ return self.tags
+ end
end
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
p.string = xml
doc = p.parse
- way = Way.new
-
doc.find('//osm/way').each do |pt|
- if !create and pt['id'] != '0'
- way.id = pt['id'].to_i
- end
+ return Way.from_xml_node(pt, create)
+ end
+ rescue
+ return nil
+ end
+ end
- if create
- way.timestamp = Time.now
- way.visible = true
- else
- if pt['timestamp']
- way.timestamp = Time.parse(pt['timestamp'])
- end
- end
+ def self.from_xml_node(pt, create=false)
+ way = Way.new
- pt.find('tag').each do |tag|
- way.add_tag_keyval(tag['k'], tag['v'])
- end
+ if !create and pt['id'] != '0'
+ way.id = pt['id'].to_i
+ end
+
+ way.version = pt['version']
- pt.find('nd').each do |nd|
- way.add_nd_num(nd['ref'])
- end
+ if create
+ way.timestamp = Time.now
+ way.visible = true
+ else
+ if pt['timestamp']
+ way.timestamp = Time.parse(pt['timestamp'])
end
- rescue
- way = nil
+ end
+
+ pt.find('tag').each do |tag|
+ way.add_tag_keyval(tag['k'], tag['v'])
+ end
+
+ pt.find('nd').each do |nd|
+ way.add_nd_num(nd['ref'])
end
return way
el1['id'] = self.id.to_s
el1['visible'] = self.visible.to_s
el1['timestamp'] = self.timestamp.xmlschema
+ el1['version'] = self.version.to_s
user_display_name_cache = {} if user_display_name_cache.nil?
t = Time.now
Way.transaction do
+ self.version += 1
self.timestamp = t
self.save!
- end
- WayTag.transaction do
tags = self.tags
-
WayTag.delete_all(['id = ?', self.id])
-
tags.each do |k,v|
tag = WayTag.new
tag.k = k
tag.id = self.id
tag.save!
end
- end
- WayNode.transaction do
nds = self.nds
-
WayNode.delete_all(['id = ?', self.id])
-
sequence = 1
nds.each do |n|
nd = WayNode.new
nd.save!
sequence += 1
end
+
+ old_way = OldWay.from_way(self)
+ old_way.timestamp = t
+ old_way.save_with_dependencies!
end
+ end
- old_way = OldWay.from_way(self)
- old_way.timestamp = t
- old_way.save_with_dependencies!
+ def update_from(new_way, user)
+ if !new_way.preconditions_ok?
+ raise OSM::APIPreconditionFailedError.new
+ elsif new_way.version != version
+ raise OSM::APIVersionMismatchError.new(new_way.version, version)
+ else
+ self.user_id = user.id
+ self.tags = new_way.tags
+ self.nds = new_way.nds
+ self.visible = true
+ save_with_history!
+ end
end
def preconditions_ok?
return true
end
- # Delete the way and it's relations, but don't really delete it - set its visibility to false and update the history etc to maintain wiki-like functionality.
- def delete_with_relations_and_history(user)
+ def delete_with_history(user)
if self.visible
# FIXME
# this should actually delete the relations,
# not just throw a PreconditionFailed if it's a member of a relation!!
+
+ # FIXME: this should probably renamed to delete_with_history
if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id",
:conditions => [ "visible = 1 AND member_type='way' and member_id=?", self.id])
raise OSM::APIPreconditionFailedError
end
# delete a way and it's nodes that aren't part of other ways, with history
+
+ # FIXME: merge the potlatch code to delete the relations
def delete_with_relations_and_nodes_and_history(user)
node_ids = self.nodes.collect {|node| node.id }
self.user_id = user.id
- self.delete_with_relations_and_history(user)
+ self.delete_with_history(user)
end
+
+ # Temporary method to match interface to nodes
+ def tags_as_hash
+ return self.tags
+ end
end
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'
# Application constants needed for routes.rb - must go before Initializer call
-API_VERSION = ENV['OSM_API_VERSION'] || '0.5'
+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')
# 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.
ActionController::Routing::Routes.draw do |map|
# API
+ map.connect "api/#{API_VERSION}/changeset/create", :controller => 'changeset', :action => 'create'
+ map.connect "api/#{API_VERSION}/changeset/upload", :controller => 'changeset', :action => 'upload'
+ map.connect "api/#{API_VERSION}/changeset/:id", :controller => 'changeset', :action => 'read', :id => /\d+/
+ map.connect "api/#{API_VERSION}/changeset/:id/close", :controller => 'changeset', :action => 'close', :id =>/\d+/
+
map.connect "api/#{API_VERSION}/node/create", :controller => 'node', :action => 'create'
map.connect "api/#{API_VERSION}/node/:id/ways", :controller => 'way', :action => 'ways_for_node', :id => /\d+/
map.connect "api/#{API_VERSION}/node/:id/relations", :controller => 'relation', :action => 'relations_for_node', :id => /\d+/
# Potlatch API
+ map.connect "api/0.5/amf", :controller =>'amf', :action =>'talk'
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'
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'
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'