+ # open a new changeset
+ if opennew!=0
+ cs = Changeset.new
+ cs.tags = cstags
+ cs.user_id = user.id
+ if !closecomment.empty? then cs.tags['comment']=closecomment end
+ # smsm1 doesn't like the next two lines and thinks they need to be abstracted to the model more/better
+ cs.created_at = Time.now.getutc
+ cs.closed_at = cs.created_at + Changeset::IDLE_TIMEOUT
+ cs.save_with_tags!
+ return [0,'',cs.id]
+ else
+ return [0,'',nil]
+ end
+ end
+ end
+
+ # Return presets (default tags, localisation etc.):
+ # uses POTLATCH_PRESETS global, set up in OSM::Potlatch.
+
+ def getpresets(lang) #:doc:
+ lang.gsub!(/[^\w\-]/,'')
+
+ begin
+ localised = YAML::load(File.open("#{RAILS_ROOT}/config/potlatch/localised/#{lang}/localised.yaml"))
+ rescue
+ localised = "" #Â guess we'll just have to use the hardcoded English text instead
+ end
+
+ begin
+ help = File.read("#{RAILS_ROOT}/config/potlatch/localised/#{lang}/help.html")
+ rescue
+ help = File.read("#{RAILS_ROOT}/config/potlatch/localised/en/help.html")
+ end
+ return POTLATCH_PRESETS+[localised,help]
+ end
+
+ ##
+ # Find all the ways, POI nodes (i.e. not part of ways), and relations
+ # in a given bounding box. Nodes are returned in full; ways and relations
+ # are IDs only.
+ #
+ # return is of the form:
+ # [success_code, success_message,
+ # [[way_id, way_version], ...],
+ # [[node_id, lat, lon, [tags, ...], node_version], ...],
+ # [[rel_id, rel_version], ...]]
+ # where the ways are any visible ways which refer to any visible
+ # nodes in the bbox, nodes are any visible nodes in the bbox but not
+ # used in any way, rel is any relation which refers to either a way
+ # or node that we're returning.
+ def whichways(xmin, ymin, xmax, ymax) #:doc:
+ amf_handle_error_with_timeout("'whichways'") do
+ enlarge = [(xmax-xmin)/8,0.01].min
+ xmin -= enlarge; ymin -= enlarge
+ xmax += enlarge; ymax += enlarge
+
+ # check boundary is sane and area within defined
+ # see /config/application.yml
+ check_boundaries(xmin, ymin, xmax, ymax)
+
+ if POTLATCH_USE_SQL then
+ ways = sql_find_ways_in_area(xmin, ymin, xmax, ymax)
+ points = sql_find_pois_in_area(xmin, ymin, xmax, ymax)
+ relations = sql_find_relations_in_area_and_ways(xmin, ymin, xmax, ymax, ways.collect {|x| x[0]})
+ else
+ # find the way ids in an area
+ nodes_in_area = Node.find_by_area(ymin, xmin, ymax, xmax, :conditions => ["current_nodes.visible = ?", true], :include => :ways)
+ ways = nodes_in_area.inject([]) { |sum, node|
+ visible_ways = node.ways.select { |w| w.visible? }
+ sum + visible_ways.collect { |w| [w.id,w.version] }
+ }.uniq
+ ways.delete([])
+
+ # find the node ids in an area that aren't part of ways
+ nodes_not_used_in_area = nodes_in_area.select { |node| node.ways.empty? }
+ points = nodes_not_used_in_area.collect { |n| [n.id, n.lon, n.lat, n.tags, n.version] }.uniq
+
+ # find the relations used by those nodes and ways
+ relations = Relation.find_for_nodes(nodes_in_area.collect { |n| n.id }, :conditions => {:visible => true}) +
+ Relation.find_for_ways(ways.collect { |w| w[0] }, :conditions => {:visible => true})
+ relations = relations.collect { |relation| [relation.id,relation.version] }.uniq
+ end
+
+ [0, '', ways, points, relations]
+ end
+ end
+
+ # Find deleted ways in current bounding box (similar to whichways, but ways
+ # with a deleted node only - not POIs or relations).
+
+ def whichways_deleted(xmin, ymin, xmax, ymax) #:doc:
+ amf_handle_error_with_timeout("'whichways_deleted'") do
+ enlarge = [(xmax-xmin)/8,0.01].min
+ xmin -= enlarge; ymin -= enlarge
+ xmax += enlarge; ymax += enlarge
+
+ # check boundary is sane and area within defined
+ # see /config/application.yml
+ check_boundaries(xmin, ymin, xmax, ymax)
+
+ nodes_in_area = Node.find_by_area(ymin, xmin, ymax, xmax, :conditions => ["current_ways.visible = ?", false], :include => :ways_via_history)
+ way_ids = nodes_in_area.collect { |node| node.ways_via_history_ids }.flatten.uniq
+
+ [0,'',way_ids]
+ end
+ end
+
+ # Get a way including nodes and tags.
+ # Returns the way id, a Potlatch-style array of points, a hash of tags, and the version number.
+
+ def getway(wayid) #:doc:
+ amf_handle_error_with_timeout("'getway' #{wayid}") do
+ if POTLATCH_USE_SQL then
+ points = sql_get_nodes_in_way(wayid)
+ tags = sql_get_tags_in_way(wayid)
+ version = sql_get_way_version(wayid)
+ else
+ # Ideally we would do ":include => :nodes" here but if we do that
+ # then rails only seems to return the first copy of a node when a
+ # way includes a node more than once
+ way = Way.find(:first, :conditions => { :id => wayid }, :include => { :nodes => :node_tags })
+
+ # check case where way has been deleted or doesn't exist
+ return [-4, 'way', wayid, [], {}, nil] if way.nil? or !way.visible
+
+ points = way.nodes.collect do |node|
+ nodetags=node.tags
+ nodetags.delete('created_by')
+ [node.lon, node.lat, node.id, nodetags, node.version]
+ end
+ tags = way.tags
+ version = way.version
+ end
+
+ [0, '', wayid, points, tags, version]
+ end
+ end
+
+ # Get an old version of a way, and all constituent nodes.
+ #
+ # For undelete (version<0), always uses the most recent version of each node,
+ # even if it's moved. For revert (version >= 0), uses the node in existence
+ # at the time, generating a new id if it's still visible and has been moved/
+ # retagged.
+ #
+ # Returns:
+ # 0. success code,
+ # 1. id,
+ # 2. array of points,
+ # 3. hash of tags,
+ # 4. version,
+ # 5. is this the current, visible version? (boolean)
+
+ def getway_old(id, timestamp) #:doc:
+ amf_handle_error_with_timeout("'getway_old' #{id}, #{timestamp}") do
+ if timestamp == ''
+ # undelete
+ old_way = OldWay.find(:first, :conditions => ['visible = ? AND id = ?', true, id], :order => 'version DESC')
+ points = old_way.get_nodes_undelete unless old_way.nil?
+ else
+ begin
+ # revert
+ timestamp = DateTime.strptime(timestamp.to_s, "%d %b %Y, %H:%M:%S")
+ old_way = OldWay.find(:first, :conditions => ['id = ? AND timestamp <= ?', id, timestamp], :order => 'timestamp DESC')
+ unless old_way.nil?
+ points = old_way.get_nodes_revert(timestamp)
+ if !old_way.visible
+ return [-1, "Sorry, the way was deleted at that time - please revert to a previous version.", id, [], {}, nil, false]
+ end
+ end
+ rescue ArgumentError
+ # thrown by date parsing method. leave old_way as nil for
+ # the error handler below.
+ end
+ end
+
+ if old_way.nil?
+ return [-1, "Sorry, the server could not find a way at that time.", id, [], {}, nil, false]
+ else
+ curway=Way.find(id)
+ old_way.tags['history'] = "Retrieved from v#{old_way.version}"
+ return [0, '', id, points, old_way.tags, curway.version, (curway.version==old_way.version and curway.visible)]
+ end
+ end
+ end
+
+ # Find history of a way.
+ # Returns 'way', id, and an array of previous versions:
+ # - formerly [old_way.version, old_way.timestamp.strftime("%d %b %Y, %H:%M"), old_way.visible ? 1 : 0, user, uid]
+ # - now [timestamp,user,uid]
+ #
+ # Heuristic: Find all nodes that have ever been part of the way;
+ # get a list of their revision dates; add revision dates of the way;
+ # sort and collapse list (to within 2 seconds); trim all dates before the
+ # start date of the way.
+
+ def getway_history(wayid) #:doc:
+ begin
+ # Find list of revision dates for way and all constituent nodes
+ revdates=[]
+ revusers={}
+ Way.find(wayid).old_ways.collect do |a|
+ revdates.push(a.timestamp)
+ unless revusers.has_key?(a.timestamp.to_i) then revusers[a.timestamp.to_i]=change_user(a) end
+ a.nds.each do |n|
+ Node.find(n).old_nodes.collect do |o|
+ revdates.push(o.timestamp)
+ unless revusers.has_key?(o.timestamp.to_i) then revusers[o.timestamp.to_i]=change_user(o) end
+ end
+ end
+ end
+ waycreated=revdates[0]
+ revdates.uniq!
+ revdates.sort!
+ revdates.reverse!
+
+ # Remove any dates (from nodes) before first revision date of way
+ revdates.delete_if { |d| d<waycreated }
+ # Remove any elements where 2 seconds doesn't elapse before next one
+ revdates.delete_if { |d| revdates.include?(d+1) or revdates.include?(d+2) }
+ # Collect all in one nested array
+ revdates.collect! {|d| [d.strftime("%d %b %Y, %H:%M:%S")] + revusers[d.to_i] }
+
+ return ['way', wayid, revdates]
+ rescue ActiveRecord::RecordNotFound
+ return ['way', wayid, []]
+ end
+ end
+
+ # Find history of a node. Returns 'node', id, and an array of previous versions as above.
+
+ def getnode_history(nodeid) #:doc:
+ begin
+ history = Node.find(nodeid).old_nodes.reverse.collect do |old_node|
+ [old_node.timestamp.strftime("%d %b %Y, %H:%M:%S")] + change_user(old_node)
+ end
+ return ['node', nodeid, history]
+ rescue ActiveRecord::RecordNotFound
+ return ['node', nodeid, []]
+ end
+ end
+
+ def change_user(obj)
+ user_object = obj.changeset.user
+ user = user_object.data_public? ? user_object.display_name : 'anonymous'
+ uid = user_object.data_public? ? user_object.id : 0
+ [user,uid]
+ end
+
+ # Find GPS traces with specified name/id.
+ # Returns array listing GPXs, each one comprising id, name and description.
+
+ def findgpx(searchterm, usertoken)
+ amf_handle_error_with_timeout("'findgpx'") do
+ user = getuser(usertoken)
+ if !uid then return -1,"You must be logged in to search for GPX traces.",[] end
+
+ gpxs = []
+ if searchterm.to_i>0 then
+ gpx = Trace.find(searchterm.to_i, :conditions => ["visible=? AND (public=? OR user_id=?)",true,true,user.id] )
+ if gpx then
+ gpxs.push([gpx.id, gpx.name, gpx.description])
+ end
+ else
+ Trace.find(:all, :limit => 21, :conditions => ["visible=? AND (public=? OR user_id=?) AND MATCH(name) AGAINST (?)",true,true,user.id,searchterm] ).each do |gpx|
+ gpxs.push([gpx.id, gpx.name, gpx.description])
+ end
+ end
+ [0,'',gpxs]
+ end
+ end
+
+ # Get a relation with all tags and members.
+ # Returns:
+ # 0. success code?
+ # 1. object type?
+ # 2. relation id,
+ # 3. hash of tags,
+ # 4. list of members,
+ # 5. version.
+
+ def getrelation(relid) #:doc:
+ amf_handle_error("'getrelation' #{relid}") do
+ rel = Relation.find(:first, :conditions => { :id => relid })
+
+ return [-4, 'relation', relid, {}, [], nil] if rel.nil? or !rel.visible
+ [0, '', relid, rel.tags, rel.members, rel.version]
+ end
+ end
+
+ # Find relations with specified name/id.
+ # Returns array of relations, each in same form as getrelation.
+
+ def findrelations(searchterm)
+ rels = []
+ if searchterm.to_i>0 then
+ rel = Relation.find(searchterm.to_i)
+ if rel and rel.visible then
+ rels.push([rel.id, rel.tags, rel.members, rel.version])
+ end
+ else
+ RelationTag.find(:all, :limit => 11, :conditions => ["v like ?", "%#{searchterm}%"] ).each do |t|
+ if t.relation.visible then
+ rels.push([t.relation.id, t.relation.tags, t.relation.members, t.relation.version])
+ end
+ end
+ end
+ rels
+ end
+
+ # Save a relation.
+ # Returns
+ # 0. 0 (success),
+ # 1. original relation id (unchanged),
+ # 2. new relation id,
+ # 3. version.
+
+ def putrelation(renumberednodes, renumberedways, usertoken, changeset_id, version, relid, tags, members, visible) #:doc:
+ amf_handle_error("'putrelation' #{relid}") do
+ user = getuser(usertoken)
+ if !user then return -1,"You are not logged in, so the relation could not be saved." end
+
+ relid = relid.to_i
+ visible = (visible.to_i != 0)
+
+ new_relation = nil
+ relation = nil
+ Relation.transaction do
+ # create a new relation, or find the existing one
+ if relid > 0
+ relation = Relation.find(relid)
+ end
+ # We always need a new node, based on the data that has been sent to us
+ new_relation = Relation.new
+
+ # check the members are all positive, and correctly type
+ typedmembers = []
+ members.each do |m|
+ mid = m[1].to_i
+ if mid < 0
+ mid = renumberednodes[mid] if m[0] == 'Node'
+ mid = renumberedways[mid] if m[0] == 'Way'
+ end
+ if mid
+ typedmembers << [m[0], mid, m[2]]
+ end
+ end
+
+ # assign new contents
+ new_relation.members = typedmembers
+ new_relation.tags = tags
+ new_relation.visible = visible
+ new_relation.changeset_id = changeset_id
+ new_relation.version = version
+
+ if relid <= 0
+ # We're creating the relation
+ new_relation.create_with_history(user)
+ elsif visible
+ # We're updating the relation
+ new_relation.id = relid
+ relation.update_from(new_relation, user)
+ else
+ # We're deleting the relation
+ new_relation.id = relid
+ relation.delete_with_history!(new_relation, user)
+ end
+ end # transaction
+
+ if relid <= 0
+ return [0, '', relid, new_relation.id, new_relation.version]
+ else
+ return [0, '', relid, relid, relation.version]
+ end
+ end
+ end
+
+ # Save a way to the database, including all nodes. Any nodes in the previous
+ # version and no longer used are deleted.
+ #
+ # Parameters:
+ # 0. hash of renumbered nodes (added by amf_controller)
+ # 1. current user token (for authentication)
+ # 2. current changeset
+ # 3. new way version
+ # 4. way ID
+ # 5. list of nodes in way
+ # 6. hash of way tags
+ # 7. array of nodes to change (each one is [lon,lat,id,version,tags]),
+ # 8. hash of nodes to delete (id->version).
+ #
+ # Returns:
+ # 0. '0' (code for success),
+ # 1. message,
+ # 2. original way id (unchanged),
+ # 3. new way id,
+ # 4. hash of renumbered nodes (old id=>new id),
+ # 5. way version,
+ # 6. hash of node versions (node=>version)
+
+ def putway(renumberednodes, usertoken, changeset_id, wayversion, originalway, pointlist, attributes, nodes, deletednodes) #:doc:
+ amf_handle_error("'putway' #{originalway}") do
+ # -- Initialise