]> git.openstreetmap.org Git - rails.git/blobdiff - app/controllers/amf_controller.rb
preliminary commit 0.5 API with relations / untested after entity-relation rename...
[rails.git] / app / controllers / amf_controller.rb
index 9564a1bdf79ff974d2e7b073ea411d699b34309c..d30043e59d10518d11548bba222bc067ff544d68 100644 (file)
@@ -1,6 +1,9 @@
 class AmfController < ApplicationController
   require 'stringio'
 
+  session :off
+  before_filter :check_availability
+
   # to log:
   # RAILS_DEFAULT_LOGGER.error("Args: #{args[0]}, #{args[1]}, #{args[2]}, #{args[3]}")
 
@@ -34,24 +37,27 @@ class AmfController < ApplicationController
       args=getvalue(req)                               #  | get response (probably an array)
 
       case message
-      when 'getpresets';       results[index]=putdata(index,getpresets)
-      when 'whichways';                results[index]=putdata(index,whichways(args))
-      when 'getway';           results[index]=putdata(index,getway(args))
-      when 'putway';           results[index]=putdata(index,putway(args))
-      when 'deleteway';                results[index]=putdata(index,deleteway(args))
+                 when 'getpresets';    results[index]=putdata(index,getpresets)
+                 when 'whichways';             results[index]=putdata(index,whichways(args))
+                 when 'getway';                results[index]=putdata(index,getway(args))
+                 when 'putway';                results[index]=putdata(index,putway(args))
+                 when 'deleteway';             results[index]=putdata(index,deleteway(args))
+                 when 'makeway';               results[index]=putdata(index,makeway(args))
       end
     end
 
     # ------------------
     # Write out response
 
-    response.headers["Content-Type"]="application/x-amf"
+    RAILS_DEFAULT_LOGGER.info("  Response: start")
     a,b=results.length.divmod(256)
-    ans=0.chr+0.chr+0.chr+0.chr+a.chr+b.chr
-    results.each do |k,v|
-      ans+=v
-    end
-    render :text => ans
+       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")
 
   end
 
@@ -60,8 +66,8 @@ class AmfController < ApplicationController
   # ====================================================================
   # Remote calls
 
-  # -----      getpresets
-  #            return presets,presetmenus and presetnames arrays
+  # ----- getpresets
+  #          return presets,presetmenus and presetnames arrays
 
   def getpresets
     presets={}
@@ -70,6 +76,8 @@ class AmfController < ApplicationController
     presettype=''
     presetcategory=''
 
+    RAILS_DEFAULT_LOGGER.info("  Message: getpresets")
+
     #          File.open("config/potlatch/presets.txt") do |file|
 
     # Temporary patch to get around filepath problem
@@ -115,6 +123,15 @@ preserved railway: railway=preserved
 disused railway tracks: railway=disused
 course of old railway: railway=abandoned
 
+way/natural
+forest: natural=wood,landuse=forest
+woodland: natural=wood,landuse=
+reservoir: natural=water,landuse=reservoir
+lake: natural=water,landuse=
+marsh: natural=marsh
+beach: natural=beach
+coastline: natural=coastline
+
 point/road
 mini roundabout: highway=mini_roundabout
 traffic lights: highway=traffic_signals
@@ -139,6 +156,9 @@ point/railway
 station: railway=station
 viaduct: railway=viaduct
 level crossing: railway=crossing
+
+point/natural
+peak: natural=peak
 EOF
 
     StringIO.open(txt) do |file|
@@ -162,21 +182,28 @@ EOF
     return [presets,presetmenus,presetnames]
   end
 
-  # -----      whichways(left,bottom,right,top)
-  #            return array of ways in current bounding box
-  #            at present, instead of using correct (=more complex) SQL to find
-  #            corner-crossing ways, it simply enlarges the bounding box by +/- 0.01
+  # ----- whichways(left,bottom,right,top)
+  #              return array of ways in current bounding box
+  #              at present, instead of using correct (=more complex) SQL to find
+  #              corner-crossing ways, it simply enlarges the bounding box by +/- 0.01
 
   def whichways(args)
-    waylist=WaySegment.find_by_sql("SELECT DISTINCT current_way_segments.id AS wayid"+
-       "  FROM current_way_segments,current_segments,current_nodes,current_ways "+
+    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
+
+    RAILS_DEFAULT_LOGGER.info("  Message: whichways, bbox=#{xmin},#{ymin},#{xmax},#{ymax}")
+
+    waylist=WayNode.find_by_sql("SELECT DISTINCT current_way_nodes.id AS wayid"+
+       "  FROM current_way_nodes,current_segments,current_nodes,current_ways "+
        " WHERE segment_id=current_segments.id "+
        "   AND current_segments.visible=1 "+
        "   AND node_a=current_nodes.id "+
-          "   AND current_ways.id=current_way_segments.id "+
+          "   AND current_ways.id=current_way_nodes.id "+
           "   AND current_ways.visible=1 "+
-       "   AND (latitude  BETWEEN "+(args[1].to_f-0.01).to_s+" AND "+(args[3].to_f+0.01).to_s+") "+
-       "   AND (longitude BETWEEN "+(args[0].to_f-0.01).to_s+" AND "+(args[2].to_f+0.01).to_s+")")
+       "   AND (latitude  BETWEEN "+ymin.to_s+" AND "+ymax.to_s+") "+
+       "   AND (longitude BETWEEN "+xmin.to_s+" AND "+xmax.to_s+")")
 
        ways = waylist.collect {|a| a.wayid.to_i } # get an array of way id's
 
@@ -184,8 +211,8 @@ EOF
        "  FROM current_nodes "+
        "  LEFT OUTER JOIN current_segments cs1 ON cs1.node_a=current_nodes.id "+
        "  LEFT OUTER JOIN current_segments cs2 ON cs2.node_b=current_nodes.id "+
-       " WHERE (latitude  BETWEEN "+(args[1].to_f-0.01).to_s+" AND "+(args[3].to_f+0.01).to_s+") "+
-       "   AND (longitude BETWEEN "+(args[0].to_f-0.01).to_s+" AND "+(args[2].to_f-0.01).to_s+") "+
+       " WHERE (latitude  BETWEEN "+ymin.to_s+" AND "+ymax.to_s+") "+
+       "   AND (longitude BETWEEN "+xmin.to_s+" AND "+xmax.to_s+") "+
        "   AND cs1.id IS NULL AND cs2.id IS NULL "+
        "   AND current_nodes.visible=1")
 
@@ -194,9 +221,9 @@ EOF
     return [ways,points]
   end
 
-  # -----      getway (objectname, way, baselong, basey, masterscale)
-  #                    returns objectname, array of co-ordinates, attributes,
-  #                                    xmin,xmax,ymin,ymax
+  # ----- getway (objectname, way, baselong, basey, masterscale)
+  #              returns objectname, array of co-ordinates, attributes,
+  #                              xmin,xmax,ymin,ymax
 
   def getway(args)
     objname,wayid,baselong,basey,masterscale=args
@@ -206,6 +233,8 @@ EOF
     xmin = ymin = 999999
     xmax = ymax = -999999
 
+    RAILS_DEFAULT_LOGGER.info("  Message: getway, id=#{wayid}")
+
     readwayquery(wayid).each {|row|
       xs1=long2coord(row['long1'].to_f,baselong,masterscale); ys1=lat2coord(row['lat1'].to_f,basey,masterscale)
       xs2=long2coord(row['long2'].to_f,baselong,masterscale); ys2=lat2coord(row['lat2'].to_f,basey,masterscale)
@@ -240,6 +269,8 @@ EOF
     ActiveRecord::Base.connection.execute("SET #{db_now}=NOW()")
     originalway=originalway.to_i
 
+    RAILS_DEFAULT_LOGGER.info("  Message: putway, id=#{originalway}")
+
     # -- 3.    read original way into memory
 
     xc={}; yc={}; tagc={}; seg={}
@@ -273,15 +304,20 @@ EOF
       ymin=[ys,ymin].min; ymax=[ys,ymax].max
       node=points[i][2].to_i
       tagstr=array2tag(points[i][4])
+         tagstr=tagstr.gsub(/[\000-\037]/,"")
       tagsql="'"+sqlescape(tagstr)+"'"
 
       # compare node
       if node<0
         # new node - create
-        newnode=ActiveRecord::Base.connection.insert("INSERT INTO current_nodes (   latitude,longitude,timestamp,user_id,visible,tags) VALUES (           #{ys},#{xs},#{db_now},#{uid},1,#{tagsql})")
-                       ActiveRecord::Base.connection.insert("INSERT INTO nodes         (id,latitude,longitude,timestamp,user_id,visible,tags) VALUES (#{newnode},#{ys},#{xs},#{db_now},#{uid},1,#{tagsql})")
-        points[i][2]=newnode
-        renumberednodes[node.to_s]=newnode.to_s
+               if renumberednodes[node.to_s].nil?
+                       newnode=ActiveRecord::Base.connection.insert("INSERT INTO current_nodes (   latitude,longitude,timestamp,user_id,visible,tags) VALUES (           #{ys},#{xs},#{db_now},#{uid},1,#{tagsql})")
+                                       ActiveRecord::Base.connection.insert("INSERT INTO nodes         (id,latitude,longitude,timestamp,user_id,visible,tags) VALUES (#{newnode},#{ys},#{xs},#{db_now},#{uid},1,#{tagsql})")
+                       points[i][2]=newnode
+                       renumberednodes[node.to_s]=newnode.to_s
+               else
+                       points[i][2]=renumberednodes[node.to_s].to_i
+               end
 
       elsif xc.has_key?(node)
         # old node from original way - update
@@ -306,17 +342,22 @@ EOF
       from =points[i  ][2].to_i
       to   =points[i+1][2].to_i
       if seg.has_key?(segid)
+               # if segment exists, check it still refers to the same nodes
         if seg[segid]=="#{from}-#{to}" then 
           if (seglist!='') then seglist+=',' end; seglist+=segid.to_s
           next
         end
+         elsif segid>0
+               # not in previous version of way, but supplied, so assume
+               # that it's come from makeway (i.e. unwayed segments)
+               if (seglist!='') then seglist+=',' end; seglist+=segid.to_s
+               next
       end
       segid=ActiveRecord::Base.connection.insert("INSERT INTO current_segments (   node_a,node_b,timestamp,user_id,visible,tags) VALUES (         #{from},#{to},#{db_now},#{uid},1,'')")
                ActiveRecord::Base.connection.insert("INSERT INTO segments         (id,node_a,node_b,timestamp,user_id,visible,tags) VALUES (#{segid},#{from},#{to},#{db_now},#{uid},1,'')")
       points[i+1][5]=segid
       numberedsegments[(i+1).to_s]=segid.to_s
     end
-    # numberedsegments.each{|a,b| RAILS_DEFAULT_LOGGER.error("Sending back: seg no. #{a} -> id #{b}") }
 
 
     # -- 6.ii insert new way segments
@@ -362,7 +403,7 @@ EOF
     ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqs}")
     ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqn}")
 
-    #          insert new version of route into way_segments
+    #          insert new version of route into way_nodes
 
     insertsql =''
     currentsql=''
@@ -376,19 +417,21 @@ EOF
       sequence  +=1
     end
 
-    ActiveRecord::Base.connection.execute("DELETE FROM current_way_segments WHERE id=#{way}");
-    ActiveRecord::Base.connection.insert("INSERT INTO         way_segments (id,segment_id,version    ) VALUES #{insertsql}");
-    ActiveRecord::Base.connection.insert("INSERT INTO current_way_segments (id,segment_id,sequence_id) VALUES #{currentsql}");
+    ActiveRecord::Base.connection.execute("DELETE FROM current_way_nodes WHERE id=#{way}");
+    ActiveRecord::Base.connection.insert("INSERT INTO         way_nodes (id,segment_id,version    ) VALUES #{insertsql}");
+    ActiveRecord::Base.connection.insert("INSERT INTO current_way_nodes (id,segment_id,sequence_id) VALUES #{currentsql}");
 
     # -- 7. insert new way tags
 
     insertsql =''
     currentsql=''
     attributes.each do |k,v|
-      if v=='' then next end
+      if v=='' or v.nil? then next end
       if v[0,6]=='(type ' then next end
       if insertsql !='' then insertsql +=',' end
       if currentsql!='' then currentsql+=',' end
+         k=k.gsub(/[\000-\037]/,"")
+         v=v.gsub(/[\000-\037]/,"")
       insertsql +="(#{way},'"+sqlescape(k)+"','"+sqlescape(v)+"',#{version})"
       currentsql+="(#{way},'"+sqlescape(k)+"','"+sqlescape(v)+"')"
     end
@@ -405,6 +448,9 @@ EOF
 
   def deleteway(args)
     usertoken,way=args
+
+    RAILS_DEFAULT_LOGGER.info("  Message: deleteway, id=#{way}")
+
     uid=getuserid(usertoken); if !uid then return end
        way=way.to_i
 
@@ -457,20 +503,168 @@ EOF
        
        ActiveRecord::Base.connection.insert("INSERT INTO ways (id,user_id,timestamp,visible) VALUES (#{way},#{uid},#{db_now},0)")
        ActiveRecord::Base.connection.update("UPDATE current_ways SET user_id=#{uid},timestamp=#{db_now},visible=0 WHERE id=#{way}")
-       ActiveRecord::Base.connection.execute("DELETE FROM current_way_segments WHERE id=#{way}")
+       ActiveRecord::Base.connection.execute("DELETE FROM current_way_nodes WHERE id=#{way}")
        ActiveRecord::Base.connection.execute("DELETE FROM current_way_tags WHERE id=#{way}")
        
        way
 end
 
+# ----- makeway(x,y,baselong,basey,masterscale)
+#              returns way made from unwayed segments
+
+def makeway(args)
+       usertoken,x,y,baselong,basey,masterscale=args
+    uid=getuserid(usertoken)
+    return if !uid
+
+       points=[]
+       toreverse=[]                            # segments to reverse
+       nodesused={}                            # so we don't go over the same node twice
+
+       # - find start point near x
+       
+       xc=coord2long(x,masterscale,baselong)
+       yc=coord2lat(y,masterscale,basey)
+
+        RAILS_DEFAULT_LOGGER.info("  Message: makeway, xc=#{xc}, y=#{yc}")
+
+       xs1=xc-0.001; xs2=xc+0.001
+       ys1=yc-0.001; ys2=yc+0.001
+       
+       sql=<<-EOF
+               SELECT cn1.latitude AS lat1,cn1.longitude AS lon1,cn1.id AS id1,
+                      cn2.latitude AS lat2,cn2.longitude AS lon2,cn2.id AS id2, cs.id AS segid
+                 FROM current_nodes AS cn1,
+                      current_nodes AS cn2,
+                      current_segments AS cs 
+                      LEFT OUTER JOIN current_way_nodes ON segment_id=cs.id 
+                WHERE (cn1.longitude BETWEEN #{xs1} AND #{xs2}) 
+                  AND (cn1.latitude  BETWEEN #{ys1} AND #{ys2}) 
+                  AND segment_id IS NULL 
+                   AND cs.visible=1
+                  AND cn1.id=node_a AND cn1.visible=1 
+                  AND cn2.id=node_b AND cn2.visible=1 
+             ORDER BY SQRT(POW(cn1.longitude-#{xc},2)+
+                               POW(cn1.latitude -#{yc},2)) 
+        LIMIT 1
+       EOF
+       row=ActiveRecord::Base.connection.select_one sql
+       if row.nil? then return [0,0,0,0,0] end
+       xs1=long2coord(row['lon1'].to_f,baselong,masterscale); ys1=lat2coord(row['lat1'].to_f,basey,masterscale)
+       xs2=long2coord(row['lon2'].to_f,baselong,masterscale); ys2=lat2coord(row['lat2'].to_f,basey,masterscale)
+       xmin=[xs1,xs2].min; xmax=[xs1,xs2].max
+       ymin=[ys1,ys2].min; ymax=[ys1,ys2].max
+       nodesused[row['id1'].to_i]=true
+       nodesused[row['id2'].to_i]=true
+       points<<[xs1,ys1,row['id1'].to_i,1,{},0]
+       points<<[xs2,ys2,row['id2'].to_i,1,{},row['segid'].to_i]
+       
+       # - extend at start, then end
+       while (a,point,nodesused,toreverse=findconnect(points[0][2],nodesused,'b',toreverse,baselong,basey,masterscale))[0]
+               points[0][5]=point[5]; point[5]=0       # segment leads to next node
+               points.unshift(point)
+               xmin=[point[0],xmin].min; xmax=[point[0],xmax].max
+               ymin=[point[1],ymin].min; ymax=[point[1],ymax].max
+       end
+       while (a,point,nodesused,toreverse=findconnect(points[-1][2],nodesused,'a',toreverse,baselong,basey,masterscale))[0]
+               points.push(point)
+               xmin=[point[0],xmin].min; xmax=[point[0],xmax].max
+               ymin=[point[1],ymin].min; ymax=[point[1],ymax].max
+       end
+       points[0][3]=0  # start with a move
+
+       # reverse segments in toreverse
+       if toreverse.length>0
+               sql=<<-EOF
+                       UPDATE current_segments c1, current_segments c2 
+                          SET c1.node_a=c2.node_b,c1.node_b=c2.node_a,
+                              c1.timestamp=NOW(),c1.user_id=#{uid} 
+                        WHERE c1.id=c2.id 
+                          AND c1.id IN (#{toreverse.join(',')})
+               EOF
+               ActiveRecord::Base.connection.update sql
+               sql=<<-EOF
+                       INSERT INTO segments 
+                  (SELECT * FROM current_segments 
+                    WHERE id IN (#{toreverse.join(',')}))
+               EOF
+               ActiveRecord::Base.connection.insert sql
+       end
+
+       [points,xmin,xmax,ymin,ymax]
+end
+
+def findconnect(id,nodesused,lookfor,toreverse,baselong,basey,masterscale)
+       # get all segments with 'id' as a point
+       # (to look for both node_a and node_b, UNION is faster than node_a=id OR node_b=id)!
+       sql=<<-EOF
+               SELECT cn1.latitude AS lat1,cn1.longitude AS lon1,cn1.id AS id1,
+                      cn2.latitude AS lat2,cn2.longitude AS lon2,cn2.id AS id2, cs.id AS segid
+                 FROM current_nodes AS cn1,
+                      current_nodes AS cn2,
+                      current_segments AS cs 
+                      LEFT OUTER JOIN current_way_nodes ON segment_id=cs.id 
+                WHERE segment_id IS NULL 
+                   AND cs.visible=1
+                  AND cn1.id=node_a AND cn1.visible=1 
+                  AND cn2.id=node_b AND cn2.visible=1 
+                  AND node_a=#{id}
+       UNION
+               SELECT cn1.latitude AS lat1,cn1.longitude AS lon1,cn1.id AS id1,
+                      cn2.latitude AS lat2,cn2.longitude AS lon2,cn2.id AS id2, cs.id AS segid
+                 FROM current_nodes AS cn1,
+                      current_nodes AS cn2,
+                      current_segments AS cs 
+                      LEFT OUTER JOIN current_way_nodes ON segment_id=cs.id 
+                WHERE segment_id IS NULL 
+                   AND cs.visible=1
+                  AND cn1.id=node_a AND cn1.visible=1 
+                  AND cn2.id=node_b AND cn2.visible=1 
+                  AND node_b=#{id}
+       EOF
+       connectlist=ActiveRecord::Base.connection.select_all sql
+       
+       if lookfor=='b' then tocol='id1'; tolat='lat1'; tolon='lon1'; fromcol='id2'; fromlat='lat2'; fromlon='lon2'
+                                       else tocol='id2'; tolat='lat2'; tolon='lon2'; fromcol='id1'; fromlat='lat1'; fromlon='lon1'
+       end
+       
+       # eliminate those already in the hash
+       connex=0
+       point=nil
+       connectlist.each { |row|
+               tonode=row[tocol].to_i
+               fromnode=row[fromcol].to_i
+               if id==tonode and !nodesused.has_key?(fromnode)
+                       # wrong way round; add, then add to 'segments to reverse' list
+                       connex+=1
+                       nodesused[fromnode]=true
+                       point=[long2coord(row[fromlon].to_f,baselong,masterscale),lat2coord(row[fromlat].to_f,basey,masterscale),fromnode,1,{},row['segid'].to_i]
+                       toreverse.push(row['segid'].to_i)
+               elsif id==fromnode and !nodesused.has_key?(tonode)
+                       # right way round; just add
+                       connex+=1
+                       point=[long2coord(row[tolon].to_f,baselong,masterscale),lat2coord(row[tolat].to_f,basey,masterscale),tonode,1,{},row['segid'].to_i]
+                       nodesused[tonode]=true
+               end
+       }
+       
+       # if only one left, then add it; otherwise return false
+       if connex!=1 or point.nil? then
+               return [false,[],nodesused,toreverse]
+       else
+               return [true,point,nodesused,toreverse]
+       end
+end
+
+
 # ====================================================================
 # Support functions for remote calls
 
 def readwayquery(id)
   ActiveRecord::Base.connection.select_all "SELECT n1.latitude AS lat1,n1.longitude AS long1,n1.id AS id1,n1.tags as tags1, "+
       "                  n2.latitude AS lat2,n2.longitude AS long2,n2.id AS id2,n2.tags as tags2,segment_id "+
-      "    FROM current_way_segments,current_segments,current_nodes AS n1,current_nodes AS n2 "+
-      "   WHERE current_way_segments.id=#{id} "+
+      "    FROM current_way_nodes,current_segments,current_nodes AS n1,current_nodes AS n2 "+
+      "   WHERE current_way_nodes.id=#{id} "+
       "     AND segment_id=current_segments.id "+
          "     AND current_segments.visible=1 "+
       "     AND n1.id=node_a and n2.id=node_b "+
@@ -483,9 +677,9 @@ def createuniquesegments(way,uqs_name,seglist)
   sql=<<-EOF
       CREATE TEMPORARY TABLE #{uqs_name}
               SELECT a.segment_id
-                FROM (SELECT DISTINCT segment_id FROM current_way_segment
+                FROM (SELECT DISTINCT segment_id FROM current_way_node
                   WHERE id = #{way}) a
-             LEFT JOIN current_way_segments b 
+             LEFT JOIN current_way_nodes b 
                 ON b.segment_id = a.segment_id
                  AND b.id != #{way}
                WHERE b.segment_id IS NULL
@@ -542,12 +736,13 @@ def array2tag(a)
 end
 
 def getuserid(token)
-  token=sqlescape(token)
-  if (token=~/^(.+)\+(.+)$/) then
-    return ActiveRecord::Base.connection.select_value("SELECT id FROM users WHERE active=1 AND email='#{$1}' AND pass_crypt=MD5('#{$2}')")
+  if (token =~ /^(.+)\+(.+)$/) then
+    user = User.authenticate(:username => $1, :password => $2)
   else
-    return ActiveRecord::Base.connection.select_value("SELECT id FROM users WHERE active=1 AND token='#{token}'")
+    user = User.authenticate(:token => token)
   end
+
+  return user ? user.id : nil;
 end
 
 
@@ -610,9 +805,9 @@ def getvalue(s)
   when 5;      return nil                                      # null
   when 6;      return nil                                      # undefined
   when 8;      s.read(4)                                       # mixedArray
-    return getobject(s)                        #  |
-  when 10;return getarray(s)                   # array
-  else;        return nil                                      # error
+                   return getobject(s)                 #  |
+  when 10;     return getarray(s)                      # array
+  else;                return nil                                      # error
   end
 end