X-Git-Url: https://git.openstreetmap.org/rails.git/blobdiff_plain/db66d47e42bc267670ef00b3b2611b4b53cb40e9..9752466c0342700ebef5a7741e9105f44957a82a:/app/controllers/amf_controller.rb diff --git a/app/controllers/amf_controller.rb b/app/controllers/amf_controller.rb index ef55ced62..febc4db68 100644 --- a/app/controllers/amf_controller.rb +++ b/app/controllers/amf_controller.rb @@ -1,20 +1,28 @@ -# AMF Controller is a semi-standalone API for Flash clients, particularly Potlatch. -# All interaction between Potlatch (as a .SWF application) and the +# amf_controller is a semi-standalone API for Flash clients, particularly +# Potlatch. All interaction between Potlatch (as a .SWF application) and the # OSM database takes place using this controller. Messages are # encoded in the Actionscript Message Format (AMF). # -# See Also Potlatch::Potlatch and Potlatch::AMF +# Helper functions are in /lib/potlatch. # -# Public domain. -# editions Systeme D / Richard Fairhurst 2004-2008 +# Author:: editions Systeme D / Richard Fairhurst 2004-2008 +# Licence:: public domain. # -# All in/out parameters are floats unless explicitly stated. +# == General structure +# +# Apart from the amf_read and amf_write methods (which distribute the requests +# from the AMF message), each method generally takes arguments in the order +# they were sent by the Potlatch SWF. Do not assume typing has been preserved. +# Methods all return an array to the SWF. +# +# == Debugging +# +# Any method that returns a status code (0 for ok) can also send: +# return(-1,"message") <-- just puts up a dialogue +# return(-2,"message") <-- also asks the user to e-mail me # -# to trap errors (getway_old,putway,putpoi,deleteway only): -# return(-1,"message") <-- just puts up a dialogue -# return(-2,"message") <-- also asks the user to e-mail me -# to log: -# RAILS_DEFAULT_LOGGER.error("Args: #{args[0]}, #{args[1]}, #{args[2]}, #{args[3]}") +# To write to the Rails log, use RAILS_DEFAULT_LOGGER.info("message"). + class AmfController < ApplicationController require 'stringio' @@ -23,98 +31,104 @@ class AmfController < ApplicationController session :off before_filter :check_write_availability - # Main AMF handler. Tha talk method takes in AMF, figures out what to do and dispatched to the appropriate private method - def talk - req=StringIO.new(request.raw_post+0.chr) # Get POST data as request - # (cf http://www.ruby-forum.com/topic/122163) - req.read(2) # Skip version indicator and client ID - results={} # Results of each body - renumberednodes={} # Shared across repeated putways - renumberedways={} # Shared across repeated putways - - # ------------- - # Parse request - - headers=AMF.getint(req) # Read number of headers - - headers.times do # Read each header - name=AMF.getstring(req) # | - req.getc # | skip boolean - value=AMF.getvalue(req) # | - header["name"]=value # | - end - - bodies=AMF.getint(req) # Read number of bodies - bodies.times do # Read each body - message=AMF.getstring(req) # | get message name - index=AMF.getstring(req) # | get index in response sequence - bytes=AMF.getlong(req) # | get total size in bytes - args=AMF.getvalue(req) # | get response (probably an array) - - case message - when 'getpresets'; results[index]=AMF.putdata(index,getpresets) - when 'whichways'; results[index]=AMF.putdata(index,whichways(args)) - when 'whichways_deleted'; results[index]=AMF.putdata(index,whichways_deleted(args)) - when 'getway'; results[index]=AMF.putdata(index,getway(args)) - when 'getrelation'; results[index]=AMF.putdata(index,getrelation(args)) - when 'getway_old'; results[index]=AMF.putdata(index,getway_old(args)) - when 'getway_history'; results[index]=AMF.putdata(index,getway_history(args)) - when 'putway'; r=putway(args,renumberednodes) - renumberednodes=r[3] - if r[1] != r[2] - renumberedways[r[1]] = r[2] - end - results[index]=AMF.putdata(index,r) - when 'putrelation'; results[index]=AMF.putdata(index,putrelation(args, renumberednodes, renumberedways)) - when 'deleteway'; results[index]=AMF.putdata(index,deleteway(args)) - when 'putpoi'; results[index]=AMF.putdata(index,putpoi(args)) - when 'getpoi'; results[index]=AMF.putdata(index,getpoi(args)) - end - end - - # ------------------ - # Write out response - - RAILS_DEFAULT_LOGGER.info(" Response: start") - a,b=results.length.divmod(256) - render :content_type => "application/x-amf", :text => proc { |response, output| - output.write 0.chr+0.chr+0.chr+0.chr+a.chr+b.chr - results.each do |k,v| - output.write(v) - end - } - RAILS_DEFAULT_LOGGER.info(" Response: end") + # Main AMF handlers: process the raw AMF string (using AMF library) and + # calls each action (private method) accordingly. + # ** FIXME: refactor to reduce duplication of code across read/write + + def amf_read + req=StringIO.new(request.raw_post+0.chr)# Get POST data as request + # (cf http://www.ruby-forum.com/topic/122163) + req.read(2) # Skip version indicator and client ID + results={} # Results of each body + + # Parse request + + headers=AMF.getint(req) # Read number of headers + + headers.times do # Read each header + name=AMF.getstring(req) # | + req.getc # | skip boolean + value=AMF.getvalue(req) # | + header["name"]=value # | + end + + bodies=AMF.getint(req) # Read number of bodies + bodies.times do # Read each body + message=AMF.getstring(req) # | get message name + index=AMF.getstring(req) # | get index in response sequence + bytes=AMF.getlong(req) # | get total size in bytes + args=AMF.getvalue(req) # | get response (probably an array) + + case message + when 'getpresets'; results[index]=AMF.putdata(index,getpresets()) + when 'whichways'; results[index]=AMF.putdata(index,whichways(*args)) + when 'whichways_deleted'; results[index]=AMF.putdata(index,whichways_deleted(*args)) + when 'getway'; results[index]=AMF.putdata(index,getway(args[0].to_i)) + when 'getrelation'; results[index]=AMF.putdata(index,getrelation(args[0].to_i)) + when 'getway_old'; results[index]=AMF.putdata(index,getway_old(args[0].to_i,args[1].to_i)) + when 'getway_history'; results[index]=AMF.putdata(index,getway_history(args[0].to_i)) + when 'getnode_history'; results[index]=AMF.putdata(index,getnode_history(args[0].to_i)) + when 'findrelations'; results[index]=AMF.putdata(index,findrelations(*args)) + when 'getpoi'; results[index]=AMF.putdata(index,getpoi(*args)) + end + end + sendresponse(results) + end + + def amf_write + req=StringIO.new(request.raw_post+0.chr) + req.read(2) + results={} + renumberednodes={} # Shared across repeated putways + renumberedways={} # Shared across repeated putways + + headers=AMF.getint(req) # Read number of headers + headers.times do # Read each header + name=AMF.getstring(req) # | + req.getc # | skip boolean + value=AMF.getvalue(req) # | + header["name"]=value # | + end + + bodies=AMF.getint(req) # Read number of bodies + bodies.times do # Read each body + message=AMF.getstring(req) # | get message name + index=AMF.getstring(req) # | get index in response sequence + bytes=AMF.getlong(req) # | get total size in bytes + args=AMF.getvalue(req) # | get response (probably an array) + + case message + when 'putway'; r=putway(renumberednodes,*args) + renumberednodes=r[3] + if r[1] != r[2] + renumberedways[r[1]] = r[2] + end + results[index]=AMF.putdata(index,r) + when 'putrelation'; results[index]=AMF.putdata(index,putrelation(renumberednodes, renumberedways, *args)) + when 'deleteway'; results[index]=AMF.putdata(index,deleteway(args[0],args[1].to_i)) + when 'putpoi'; results[index]=AMF.putdata(index,putpoi(*args)) + end + end + sendresponse(results) end private - # Return presets (default tags and crap) to potlatch. - # Uses POTLATCH_PRESETS global, set up in OSM::Potlatch - def getpresets #:doc: - return POTLATCH_PRESETS + # Return presets (default tags, localisation etc.): + # uses POTLATCH_PRESETS global, set up in OSM::Potlatch. + + def getpresets() #:doc: + return POTLATCH_PRESETS end - # ----- whichways - # Find all the way ids and nodes (including tags and projected lat/lng) which aren't part of those ways in an are - # - # The argument is an array containing the following, in order: - # 0. minimum longitude - # 1. minimum latitude - # 2. maximum longitude - # 3. maximum latitude - # 4. baselong, 5. basey, 6. masterscale as above - def whichways(args) #:doc: - xmin = args[0].to_f-0.01 - ymin = args[1].to_f-0.01 - xmax = args[2].to_f+0.01 - ymax = args[3].to_f+0.01 - baselong = args[4] - basey = args[5] - masterscale = args[6] + # 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. def whichways(xmin, ymin, xmax, ymax) #:doc: - xmin -= 0.01; ymin -= 0.01 - xmax += 0.01; ymax += 0.01 + enlarge = [(xmax-xmin)/8,0.01].min + xmin -= enlarge; ymin -= enlarge + xmax += enlarge; ymax += enlarge if POTLATCH_USE_SQL then way_ids = sql_find_way_ids_in_area(xmin, ymin, xmax, ymax) @@ -125,9 +139,9 @@ class AmfController < ApplicationController nodes_in_area = Node.find_by_area(ymin, xmin, ymax, xmax, :conditions => "current_nodes.visible = 1", :include => :ways) way_ids = nodes_in_area.collect { |node| node.way_ids }.flatten.uniq - # 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_potlatch(baselong,masterscale), n.lat_potlatch(basey,masterscale), n.tags_as_hash] } + # 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_as_hash] } # find the relations used by those nodes and ways relations = Relation.find_for_nodes(nodes_in_area.collect { |n| n.id }, :conditions => "visible = 1") + @@ -138,19 +152,8 @@ class AmfController < ApplicationController [way_ids, points, relation_ids] end - # ----- whichways_deleted - # return array of deleted ways in current bounding box - # in: as whichways - # does: finds all deleted ways with a deleted node in bounding box - # out: [0] array of way ids - def whichways_deleted(args) #:doc: - xmin = args[0].to_f-0.01 - ymin = args[1].to_f-0.01 - xmax = args[2].to_f+0.01 - ymax = args[3].to_f+0.01 - baselong = args[4] - basey = args[5] - masterscale = args[6] + # 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: xmin -= 0.01; ymin -= 0.01 @@ -162,23 +165,8 @@ class AmfController < ApplicationController [way_ids] end - # ----- getway - # Get a way with all of it's nodes and tags - # The input is an array with the following components, in order: - # 0. wayid - the ID of the way to get - # 1. baselong - origin of SWF map (longitude) - # 2. basey - origin of SWF map (latitude) - # 3. masterscale - SWF map scale - # - # The output is an array which contains all the nodes (with projected - # latitude and longitude) and tags for a way (and all the nodes tags). - # It also has the way's unprojected (WGS84) bbox. - # - # FIXME: The server really shouldn't be figuring out a ways bounding box and doing projection for potlatch - # FIXME: the argument splitting should be done in the 'talk' method, not here - def getway(args) #:doc: - wayid,baselong,basey,masterscale = args - wayid = wayid.to_i + # Get a way including nodes and tags. + # Returns 0 (success), a Potlatch-style array of points, and a hash of tags. def getway(wayid) #:doc: if POTLATCH_USE_SQL then @@ -190,7 +178,9 @@ class AmfController < ApplicationController # way includes a node more than once way = Way.find(wayid) points = way.nodes.collect do |node| - [node.lon, node.lat, node.id, nil, node.tags_as_hash] + nodetags=node.tags_as_hash + nodetags.delete('created_by') + [node.lon, node.lat, node.id, nodetags] end tags = way.tags end @@ -198,23 +188,12 @@ class AmfController < ApplicationController [wayid, points, tags] end - # ----- getway_old - # returns old version of way - # in: [0] way id, - # [1] way version to get (or -1 for "last deleted version") - # [2] baselong, [3] basey, [4] masterscale - # does: gets old version of way and all constituent nodes - # for undelete, always uses the most recent version of each node - # (even if it's moved) - # for revert, uses the historic version of each node, but if that node is - # still visible and has been changed since, generates a new node id - # out: [0] 0 (code for success), [1] SWF object name, - # [2] array of points (as getway _except_ [3] is node.visible?, 0 or 1), - # [4] xmin, [5] xmax, [6] ymin, [7] ymax (unprojected bbox), - # [8] way version - def getway_old(args) #:doc: - RAILS_DEFAULT_LOGGER.info(" Message: getway_old (server is #{SERVER_URL})") - # if SERVER_URL=="www.openstreetmap.org" then return -1,"Revert is not currently enabled on the OpenStreetMap server." 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=1+), uses the node in existence + # at the time, generating a new id if it's still visible and has been moved/ + # retagged. def getway_old(id, version) #:doc: if version < 0 @@ -229,14 +208,31 @@ class AmfController < ApplicationController [0, id, points, old_way.tags, old_way.version] end + + # Find history of a way. Returns 'way', id, and + # an array of previous versions. def getway_history(wayid) #:doc: - history = Way.find(wayid).old_ways.collect do |old_way| + history = Way.find(wayid).old_ways.reverse.collect do |old_way| user = old_way.user.data_public? ? old_way.user.display_name : 'anonymous' - [old_way.version, old_way.timestamp.strftime("%d %b %Y, %H:%M"), old_way.visible ? 1 : 0, user] + uid = old_way.user.data_public? ? old_way.user.id : 0 + [old_way.version, old_way.timestamp.strftime("%d %b %Y, %H:%M"), old_way.visible ? 1 : 0, user, uid] + end + + ['way',wayid,history] + end + + # Find history of a node. Returns 'node', id, and + # an array of previous versions. + + def getnode_history(nodeid) #:doc: + history = Node.find(nodeid).old_nodes.reverse.collect do |old_node| + user = old_node.user.data_public? ? old_node.user.display_name : 'anonymous' + uid = old_node.user.data_public? ? old_node.user.id : 0 + [old_node.timestamp.to_i, old_node.timestamp.strftime("%d %b %Y, %H:%M"), old_node.visible ? 1 : 0, user, uid] end - [history] + ['node',nodeid,history] end # Get a relation with all tags and members. @@ -251,18 +247,31 @@ class AmfController < ApplicationController [relid, rel.tags, rel.members] end - # ----- getrelation - # save relation to the database - # in: [0] user token (string), - # [1] original relation id (may be negative), - # [2] hash of tags, [3] list of members, - # [4] visible - # out: [0] 0 (success), [1] original relation id (unchanged), - # [2] new relation id - def putrelation(args, renumberednodes, renumberedways) #:doc: - usertoken,relid,tags,members,visible=args - uid=getuserid(usertoken) - if !uid then return -1,"You are not logged in, so the point could not be saved." 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]) + end + else + RelationTag.find(:all, :limit => 11, :conditions => ["match(v) against (?)", searchterm] ).each do |t| + if t.relation.visible then + rels.push([t.relation.id, t.relation.tags, t.relation.members]) + end + end + end + rels + end + + # Save a relation. + # Returns + # 0. 0 (success), + # 1. original relation id (unchanged), + # 2. new relation id. def putrelation(renumberednodes, renumberedways, usertoken, relid, tags, members, visible) #:doc: uid = getuserid(usertoken) @@ -272,25 +281,24 @@ class AmfController < ApplicationController visible = visible.to_i # create a new relation, or find the existing one - if relid <= 0 - rel = Relation.new - else - rel = Relation.find(relid) - end - - # 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' - if mid < 0 - return -2, "Negative ID unresolved" - end - end - typedmembers << [m[0], mid, m[2]] - end + if relid <= 0 + rel = Relation.new + else + rel = Relation.find(relid) + end + + # 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 rel.members = typedmembers @@ -298,38 +306,27 @@ class AmfController < ApplicationController rel.visible = visible rel.user_id = uid - # check it then save it - # BUG: the following is commented out because it always fails on my - # install. I think it's a Rails bug. + # check it then save it + # BUG: the following is commented out because it always fails on my + # install. I think it's a Rails bug. - #if !rel.preconditions_ok? - # return -2, "Relation preconditions failed" - #else - rel.save_with_history! - #end + #if !rel.preconditions_ok? + # return -2, "Relation preconditions failed" + #else + rel.save_with_history! + #end [0, relid, rel.id] end - # ----- putway - # saves a way to the database - # in: [0] user token (string), - # [1] original way id (may be negative), - # [2] array of points (as getway/getway_old), - # [3] hash of way tags, - # [4] original way version (0 if not a reverted/undeleted way), - # [5] baselong, [6] basey, [7] masterscale - # does: saves way to the database - # all constituent nodes are created/updated as necessary - # (or deleted if they were in the old version and are otherwise unused) - # out: [0] 0 (code for success), [1] original way id (unchanged), - # [2] new way id, [3] hash of renumbered nodes (old id=>new id), - # [4] xmin, [5] xmax, [6] ymin, [7] ymax (unprojected bbox) - def putway(args,renumberednodes) #:doc: - RAILS_DEFAULT_LOGGER.info(" putway started") - usertoken,originalway,points,attributes,oldversion,baselong,basey,masterscale=args - uid=getuserid(usertoken) - if !uid then return -1,"You are not logged in, so the way could not be saved." end + # Save a way to the database, including all nodes. Any nodes in the previous + # version and no longer used are deleted. + # + # Returns: + # 0. '0' (code for success), + # 1. original way id (unchanged), + # 2. new way id, + # 3. hash of renumbered nodes (old id=>new id) def putway(renumberednodes, usertoken, originalway, points, attributes) #:doc: @@ -347,7 +344,7 @@ class AmfController < ApplicationController if points.length < 2 then return -2,"Server error - way is only #{points.length} points long." end - # -- 3. read original way into memory + # -- Get unique nodes if originalway < 0 way = Way.new @@ -357,7 +354,7 @@ class AmfController < ApplicationController uniques = way.unshared_node_ids end - # -- 4. get version by inserting new row into ways + # -- Compare nodes and save changes to any that have changed nodes = [] @@ -375,8 +372,10 @@ class AmfController < ApplicationController savenode = true else node = Node.find(id) + nodetags=node.tags_as_hash + nodetags.delete('created_by') if !fpcomp(lat, node.lat) or !fpcomp(lon, node.lon) or - Tags.join(n[4]) != node.tags or !node.visible? + n[4] != nodetags or !node.visible? savenode = true end end @@ -399,6 +398,14 @@ class AmfController < ApplicationController nodes.push(id) end + # -- Save revised way + + way.tags = attributes + way.nds = nodes + way.user_id = uid + way.visible = true + way.save_with_history! + # -- Delete any unique nodes uniques.each do |n| @@ -410,42 +417,15 @@ class AmfController < ApplicationController node.save_with_history! end - points.each_index do |i| - xs=coord2long(points[i][0],masterscale,baselong) - ys=coord2lat(points[i][1],masterscale,basey) - xmin=[xs,xmin].min; xmax=[xs,xmax].max - ymin=[ys,ymin].min; ymax=[ys,ymax].max - node=points[i][2].to_i - tagstr=array2tag(points[i][4]) - tagsql="'"+sqlescape(tagstr)+"'" - lat=(ys * 10000000).round - long=(xs * 10000000).round - tile=QuadTile.tile_for_point(ys, xs) - - way.tags = attributes - way.nds = nodes - way.user_id = uid - way.visible = true - way.save_with_history! - [0, originalway, way.id, renumberednodes] end - # ----- putpoi - # save POI to the database - # in: [0] user token (string), - # [1] original node id (may be negative), - # [2] projected longitude, [3] projected latitude, - # [4] hash of tags, [5] visible (0 to delete, 1 otherwise), - # [6] baselong, [7] basey, [8] masterscale - # does: saves POI node to the database - # refuses save if the node has since become part of a way - # out: [0] 0 (success), [1] original node id (unchanged), - # [2] new node id - def putpoi(args) #:doc: - usertoken,id,x,y,tags,visible,baselong,basey,masterscale=args - uid=getuserid(usertoken) - if !uid then return -1,"You are not logged in, so the point could not be saved." end + # Save POI to the database. + # Refuses save if the node has since become part of a way. + # Returns: + # 0. 0 (success), + # 1. original node id (unchanged), + # 2. new node id. def putpoi(usertoken, id, lon, lat, tags, visible) #:doc: uid = getuserid(usertoken) @@ -475,26 +455,17 @@ class AmfController < ApplicationController [0, id, node.id] end - # ----- getpoi - # read POI from database - # (only called on revert: POIs are usually read by whichways) - # in: [0] node id, [1] baselong, [2] basey, [3] masterscale - # does: reads POI - # out: [0] id (unchanged), [1] projected long, [2] projected lat, - # [3] hash of tags - def getpoi(args) #:doc: - id,baselong,basey,masterscale = args - - n = Node.find(id.to_i) - if n - return [n.id, n.lon_potlatch(baselong,masterscale), n.lat_potlatch(basey,masterscale), n.tags_as_hash] - else - return [nil,nil,nil,''] - end - end + # Read POI from database + # (only called on revert: POIs are usually read by whichways). + # + # Returns array of id, long, lat, hash of tags. - def getpoi(id) #:doc: - n = Node.find(id) + def getpoi(id,timestamp) #:doc: + if timestamp>0 then + n = OldNode.find(id, :conditions=>['UNIX_TIMESTAMP(timestamp)=?',timestamp]) + else + n = Node.find(id) + end if n return [n.id, n.lon, n.lat, n.tags_as_hash] @@ -503,6 +474,8 @@ class AmfController < ApplicationController end end + # Delete way and all constituent nodes. Also removes from any relations. + # Returns 0 (success), unchanged way id. def deleteway(usertoken, way_id) #:doc: uid = getuserid(usertoken) @@ -515,45 +488,18 @@ class AmfController < ApplicationController way.unshared_node_ids.each do |n| deleteitemrelations(n, 'node') end + deleteitemrelations(way_id, 'way') way.delete_with_relations_and_nodes_and_history(user) [0, way_id] end - def createuniquenodes(way,uqn_name,nodelist) #:doc: - # Find nodes which appear in this way but no others - sql=<<-EOF - CREATE TEMPORARY TABLE #{uqn_name} - SELECT a.node_id - FROM (SELECT DISTINCT node_id FROM current_way_nodes - WHERE id=#{way}) a - LEFT JOIN current_way_nodes b - ON b.node_id=a.node_id - AND b.id!=#{way} - WHERE b.node_id IS NULL - EOF - unless nodelist.empty? then - sql+="AND a.node_id NOT IN ("+nodelist.join(',')+")" - end - ActiveRecord::Base.connection.execute(sql) - end - - # ==================================================================== - # Relations handling - # deleteuniquenoderelations(uqn_name,uid,db_now) - # deleteitemrelations(way|node,'way'|'node',uid,db_now) - - def deleteuniquenoderelations(uqn_name,uid,db_now) #:doc: - sql=<<-EOF - SELECT node_id,cr.id FROM #{uqn_name},current_relation_members crm,current_relations cr - WHERE crm.member_id=node_id - AND crm.member_type='node' - AND crm.id=cr.id - AND cr.visible=1 - EOF + # Support functions + + # Remove a node or way from all relations def deleteitemrelations(objid, type) #:doc: relations = RelationMember.find(:all, @@ -566,100 +512,124 @@ class AmfController < ApplicationController end end - def deleteitemrelations(objid,type,uid,db_now) #:doc: - sql=<<-EOF - SELECT cr.id FROM current_relation_members crm,current_relations cr - WHERE crm.member_id=#{objid} - AND crm.member_type='#{type}' - AND crm.id=cr.id - AND cr.visible=1 - EOF - - relways=ActiveRecord::Base.connection.select_all(sql) - relways.each do |a| - removefromrelation(objid,type,a['id'],uid,db_now) - end - end - - def removefromrelation(objid,type,relation,uid,db_now) #:doc: - rver=ActiveRecord::Base.connection.insert("INSERT INTO relations (id,user_id,timestamp,visible) VALUES (#{relation},#{uid},#{db_now},1)") - - tagsql=<<-EOF - INSERT INTO relation_tags (id,k,v,version) - SELECT id,k,v,#{rver} FROM current_relation_tags - WHERE id=#{relation} - EOF - ActiveRecord::Base.connection.insert(tagsql) - - membersql=<<-EOF - INSERT INTO relation_members (id,member_type,member_id,member_role,version) - SELECT id,member_type,member_id,member_role,#{rver} FROM current_relation_members - WHERE id=#{relation} - AND (member_id!=#{objid} OR member_type!='#{type}') - EOF - ActiveRecord::Base.connection.insert(membersql) - - ActiveRecord::Base.connection.update("UPDATE current_relations SET user_id=#{uid},timestamp=#{db_now} WHERE id=#{relation}") - ActiveRecord::Base.connection.execute("DELETE FROM current_relation_members WHERE id=#{relation} AND member_type='#{type}' AND member_id=#{objid}") - end - - def sqlescape(a) #:doc: - a.gsub(/[\000-\037]/,"").gsub("'","''").gsub(92.chr) {92.chr+92.chr} - end + # Break out node tags into a hash + # (should become obsolete as of API 0.6) - def tag2array(a) #:doc: - tags={} - Tags.split(a) do |k, v| - tags[k.gsub(':','|')]=v - end - tags + def tagstring_to_hash(a) #:doc: + tags={} + Tags.split(a) do |k, v| + tags[k]=v + end + tags end - def array2tag(a) #:doc: - tags = [] - a.each do |k,v| - if v=='' then next end - if v[0,6]=='(type ' then next end - tags << [k.gsub('|',':'), v] - end - return Tags.join(tags) - end + # Authenticate token + # (could be removed if no-one uses the username+password form) def getuserid(token) #:doc: - if (token =~ /^(.+)\+(.+)$/) then - user = User.authenticate(:username => $1, :password => $2) - else - user = User.authenticate(:token => token) - end + if (token =~ /^(.+)\+(.+)$/) then + user = User.authenticate(:username => $1, :password => $2) + else + user = User.authenticate(:token => token) + end - return user ? user.id : nil; + return user ? user.id : nil; end - # ==================================================================== - # Co-ordinate conversion + # Compare two floating-point numbers to within 0.0000001 - def lat2coord(a,basey,masterscale) #:doc: - -(lat2y(a)-basey)*masterscale + def fpcomp(a,b) #:doc: + return ((a/0.0000001).round==(b/0.0000001).round) end - def long2coord(a,baselong,masterscale) #:doc: - (a-baselong)*masterscale + # Send AMF response + + def sendresponse(results) + a,b=results.length.divmod(256) + render :content_type => "application/x-amf", :text => proc { |response, output| + output.write 0.chr+0.chr+0.chr+0.chr+a.chr+b.chr + results.each do |k,v| + output.write(v) + end + } end - def lat2y(a) #:doc: - 180/Math::PI * Math.log(Math.tan(Math::PI/4+a*(Math::PI/180)/2)) - end - def coord2lat(a,masterscale,basey) #:doc: - y2lat(a/-masterscale+basey) + # ==================================================================== + # Alternative SQL queries for getway/whichways + + def sql_find_way_ids_in_area(xmin,ymin,xmax,ymax) + sql=<<-EOF + SELECT DISTINCT current_way_nodes.id AS wayid + FROM current_way_nodes + INNER JOIN current_nodes ON current_nodes.id=current_way_nodes.node_id + INNER JOIN current_ways ON current_ways.id =current_way_nodes.id + WHERE current_nodes.visible=1 + AND current_ways.visible=1 + AND #{OSM.sql_for_area(ymin, xmin, ymax, xmax, "current_nodes.")} + EOF + return ActiveRecord::Base.connection.select_all(sql).collect { |a| a['wayid'].to_i } end - - def coord2long(a,masterscale,baselong) #:doc: - a/masterscale+baselong + + def sql_find_pois_in_area(xmin,ymin,xmax,ymax) + sql=<<-EOF + SELECT current_nodes.id,current_nodes.latitude*0.0000001 AS lat,current_nodes.longitude*0.0000001 AS lon,current_nodes.tags + FROM current_nodes + LEFT OUTER JOIN current_way_nodes cwn ON cwn.node_id=current_nodes.id + WHERE current_nodes.visible=1 + AND cwn.id IS NULL + AND #{OSM.sql_for_area(ymin, xmin, ymax, xmax, "current_nodes.")} + EOF + return ActiveRecord::Base.connection.select_all(sql).collect { |n| [n['id'].to_i,n['lon'].to_f,n['lat'].to_f,tagstring_to_hash(n['tags'])] } end - - def y2lat(a) - 180/Math::PI * (2*Math.atan(Math.exp(a*Math::PI/180))-Math::PI/2) + + def sql_find_relations_in_area_and_ways(xmin,ymin,xmax,ymax,way_ids) + # ** It would be more Potlatchy to get relations for nodes within ways + # during 'getway', not here + sql=<<-EOF + SELECT DISTINCT cr.id AS relid + FROM current_relations cr + INNER JOIN current_relation_members crm ON crm.id=cr.id + INNER JOIN current_nodes cn ON crm.member_id=cn.id AND crm.member_type='node' + WHERE #{OSM.sql_for_area(ymin, xmin, ymax, xmax, "cn.")} + EOF + unless way_ids.empty? + sql+=<<-EOF + UNION + SELECT DISTINCT cr.id AS relid + FROM current_relations cr + INNER JOIN current_relation_members crm ON crm.id=cr.id + WHERE crm.member_type='way' + AND crm.member_id IN (#{way_ids.join(',')}) + EOF + end + return ActiveRecord::Base.connection.select_all(sql).collect { |a| a['relid'].to_i }.uniq + end + + def sql_get_nodes_in_way(wayid) + points=[] + sql=<<-EOF + SELECT latitude*0.0000001 AS lat,longitude*0.0000001 AS lon,current_nodes.id,tags + FROM current_way_nodes,current_nodes + WHERE current_way_nodes.id=#{wayid.to_i} + AND current_way_nodes.node_id=current_nodes.id + AND current_nodes.visible=1 + ORDER BY sequence_id + EOF + ActiveRecord::Base.connection.select_all(sql).each do |row| + nodetags=tagstring_to_hash(row['tags']) + nodetags.delete('created_by') + points << [row['lon'].to_f,row['lat'].to_f,row['id'].to_i,nodetags] + end + points + end + + def sql_get_tags_in_way(wayid) + tags={} + ActiveRecord::Base.connection.select_all("SELECT k,v FROM current_way_tags WHERE id=#{wayid.to_i}").each do |row| + tags[row['k']]=row['v'] + end + tags end end