Potlatch 0.4 (hopefully)
[rails.git] / app / controllers / amf_controller.rb
1 class AmfController < ApplicationController
2   require 'stringio'
3
4   session :off
5   before_filter :check_write_availability
6
7   # to log:
8   # RAILS_DEFAULT_LOGGER.error("Args: #{args[0]}, #{args[1]}, #{args[2]}, #{args[3]}")
9
10   # ====================================================================
11   # Main AMF handler
12
13   # ---- talk   process AMF request
14
15   def talk
16     req=StringIO.new(request.raw_post+0.chr)    # Get POST data as request
17                                                                                         # (cf http://www.ruby-forum.com/topic/122163)
18     req.read(2)                                                                 # Skip version indicator and client ID
19     results={}                                                                  # Results of each body
20
21     # -------------
22     # Parse request
23
24     headers=getint(req)                                 # Read number of headers
25
26     headers.times do                                # Read each header
27       name=getstring(req)                               #  |
28       req.getc                                  #  | skip boolean
29       value=getvalue(req)                               #  |
30       header["name"]=value                              #  |
31     end
32
33     bodies=getint(req)                                  # Read number of bodies
34     bodies.times do                                     # Read each body
35       message=getstring(req)                    #  | get message name
36       index=getstring(req)                              #  | get index in response sequence
37       bytes=getlong(req)                                #  | get total size in bytes
38       args=getvalue(req)                                #  | get response (probably an array)
39
40       case message
41                   when 'getpresets';    results[index]=putdata(index,getpresets)
42                   when 'whichways';             results[index]=putdata(index,whichways(args))
43                   when 'getway';                results[index]=putdata(index,getway(args))
44                   when 'putway';                results[index]=putdata(index,putway(args))
45                   when 'deleteway';             results[index]=putdata(index,deleteway(args))
46                   when 'putpoi';                results[index]=putdata(index,putpoi(args))
47                   when 'getpoi';                results[index]=putdata(index,getpoi(args))
48       end
49     end
50
51     # ------------------
52     # Write out response
53
54     RAILS_DEFAULT_LOGGER.info("  Response: start")
55     a,b=results.length.divmod(256)
56         render :content_type => "application/x-amf", :text => proc { |response, output| 
57         output.write 0.chr+0.chr+0.chr+0.chr+a.chr+b.chr
58                 results.each do |k,v|
59                   output.write(v)
60                 end
61         }
62     RAILS_DEFAULT_LOGGER.info("  Response: end")
63
64   end
65
66   private
67
68   # ====================================================================
69   # Remote calls
70
71   # ----- getpresets
72   #           return presets,presetmenus and presetnames arrays
73
74   def getpresets
75     presets={}
76     presetmenus={}; presetmenus['point']=[]; presetmenus['way']=[]; presetmenus['POI']=[]
77     presetnames={}; presetnames['point']={}; presetnames['way']={}; presetnames['POI']={}
78     presettype=''
79     presetcategory=''
80
81     RAILS_DEFAULT_LOGGER.info("  Message: getpresets")
82
83     #           File.open("config/potlatch/presets.txt") do |file|
84
85     # Temporary patch to get around filepath problem
86     # To remove this patch and make the code nice again:
87     # 1. uncomment above line
88     # 2. fix the path in the above line
89     # 3. delete this here document, and the following line (StringIO....)
90
91     txt=<<-EOF
92 way/road
93 motorway: highway=motorway,ref=(type road number)
94 trunk road: highway=trunk,ref=(type road number),name=(type road name)
95 primary road: highway=primary,ref=(type road number),name=(type road name)
96 secondary road: highway=secondary,ref=(type road number),name=(type road name)
97 tertiary road: highway=tertiary,ref=,name=(type road name)
98 residential road: highway=residential,ref=,name=(type road name)
99 unclassified road: highway=unclassified,ref=,name=(type road name)
100
101 way/footway
102 footpath: highway=footway,foot=yes
103 bridleway: highway=bridleway,foot=yes
104 byway: highway=unsurfaced,foot=yes
105 permissive path: highway=footway,foot=permissive
106
107 way/cycleway
108 cycle lane: highway=cycleway,cycleway=lane,ncn_ref=
109 cycle track: highway=cycleway,cycleway=track,ncn_ref=
110 cycle lane (NCN): highway=cycleway,cycleway=lane,name=(type name here),ncn_ref=(type route number)
111 cycle track (NCN): highway=cycleway,cycleway=track,name=(type name here),ncn_ref=(type route number)
112
113 way/waterway
114 canal: waterway=canal,name=(type name here)
115 navigable river: waterway=river,boat=yes,name=(type name here)
116 navigable drain: waterway=drain,boat=yes,name=(type name here)
117 derelict canal: waterway=derelict_canal,name=(type name here)
118 unnavigable river: waterway=river,boat=no,name=(type name here)
119 unnavigable drain: waterway=drain,boat=no,name=(type name here)
120
121 way/railway
122 railway: railway=rail
123 tramway: railway=tram
124 light railway: railway=light_rail
125 preserved railway: railway=preserved
126 disused railway tracks: railway=disused
127 course of old railway: railway=abandoned
128
129 way/natural
130 lake: natural=water,landuse=
131 forest: landuse=forest,natural=
132
133 point/road
134 mini roundabout: highway=mini_roundabout
135 traffic lights: highway=traffic_signals
136
137 point/footway
138 bridge: highway=bridge
139 gate: highway=gate
140 stile: highway=stile
141 cattle grid: highway=cattle_grid
142
143 point/cycleway
144 gate: highway=gate
145
146 point/waterway
147 lock gate: waterway=lock_gate
148 weir: waterway=weir
149 aqueduct: waterway=aqueduct
150 winding hole: waterway=turning_point
151 mooring: waterway=mooring
152
153 point/railway
154 station: railway=station
155 viaduct: railway=viaduct
156 level crossing: railway=crossing
157
158 point/natural
159 peak: natural=peak
160
161 POI/road
162 car park: amenity=parking
163 petrol station: amenity=fuel
164
165 POI/cycleway
166 bike park: amenity=bicycle_parking
167
168 POI/place
169 city: place=city,name=(type name here),is_in=(type region or county)
170 town: place=town,name=(type name here),is_in=(type region or county)
171 suburb: place=suburb,name=(type name here),is_in=(type region or county)
172 village: place=village,name=(type name here),is_in=(type region or county)
173 hamlet: place=hamlet,name=(type name here),is_in=(type region or county)
174
175 POI/tourism
176 attraction: tourism=attraction,amenity=,religion=,denomination=
177 church: tourism=,amenity=place_of_worship,name=(type name here),religion=christian,denomination=(type denomination here)
178 hotel: tourism=hotel,amenity=,religion=,denomination=
179 other religious: tourism=,amenity=place_of_worship,name=(type name here),religion=(type religion),denomination=
180 post box: amenity=post_box,tourism=,name=,religion=,denomination=
181 post office: amenity=post_office,tourism=,name=,religion=,denomination=
182 pub: tourism=,amenity=pub,name=(type name here),religion=,denomination=
183
184 POI/natural
185 peak: point=peak
186 EOF
187
188     StringIO.open(txt) do |file|
189       file.each_line {|line|
190         t=line.chomp
191         if (t=~/(\w+)\/(\w+)/) then
192           presettype=$1
193           presetcategory=$2
194           presetmenus[presettype].push(presetcategory)
195           presetnames[presettype][presetcategory]=["(no preset)"]
196         elsif (t=~/^(.+):\s?(.+)$/) then
197           pre=$1; kv=$2
198           presetnames[presettype][presetcategory].push(pre)
199           presets[pre]={}
200           kv.split(',').each {|a|
201             if (a=~/^(.+)=(.*)$/) then presets[pre][$1]=$2 end
202           }
203         end
204       }
205     end
206     return [presets,presetmenus,presetnames]
207   end
208
209   # ----- whichways(left,bottom,right,top)
210   #               return array of ways in current bounding box
211   #               at present, instead of using correct (=more complex) SQL to find
212   #               corner-crossing ways, it simply enlarges the bounding box by +/- 0.01
213
214   def whichways(args)
215     xmin = args[0].to_f-0.01
216     ymin = args[1].to_f-0.01
217     xmax = args[2].to_f+0.01
218     ymax = args[3].to_f+0.01
219     baselong    = args[4]
220     basey       = args[5]
221     masterscale = args[6]
222
223     RAILS_DEFAULT_LOGGER.info("  Message: whichways, bbox=#{xmin},#{ymin},#{xmax},#{ymax}")
224
225     waylist = ActiveRecord::Base.connection.select_all("SELECT DISTINCT current_way_nodes.id AS wayid"+
226        "  FROM current_way_nodes,current_nodes,current_ways "+
227        " WHERE current_nodes.id=current_way_nodes.node_id "+
228        "   AND current_nodes.visible=1 "+
229        "   AND current_ways.id=current_way_nodes.id "+
230        "   AND current_ways.visible=1 "+
231        "   AND "+OSM.sql_for_area(ymin, xmin, ymax, xmax, "current_nodes."))
232
233     ways = waylist.collect {|a| a['wayid'].to_i } # get an array of way id's
234
235     pointlist = ActiveRecord::Base.connection.select_all("SELECT current_nodes.id,current_nodes.latitude*0.0000001 AS lat,current_nodes.longitude*0.0000001 AS lng,current_nodes.tags "+
236        "  FROM current_nodes "+
237        "  LEFT OUTER JOIN current_way_nodes cwn ON cwn.node_id=current_nodes.id "+
238        " WHERE "+OSM.sql_for_area(ymin, xmin, ymax, xmax, "current_nodes.")+
239        "   AND cwn.id IS NULL "+
240        "   AND current_nodes.visible=1")
241
242     points = pointlist.collect {|a| [a['id'],long2coord(a['lng'].to_f,baselong,masterscale),lat2coord(a['lat'].to_f,basey,masterscale),tag2array(a['tags'])]    } # get a list of node ids and their tags
243
244     return [ways,points]
245   end
246
247   # ----- getway (objectname, way, baselong, basey, masterscale)
248   #               returns objectname, array of co-ordinates, attributes,
249   #                               xmin,xmax,ymin,ymax
250
251   def getway(args)
252     objname,wayid,baselong,basey,masterscale=args
253     wayid = wayid.to_i
254     points = []
255     lastid = -1
256     xmin = ymin = 999999
257     xmax = ymax = -999999
258
259     RAILS_DEFAULT_LOGGER.info("  Message: getway, id=#{wayid}")
260
261     readwayquery(wayid).each {|row|
262       points<<[long2coord(row['longitude'].to_f,baselong,masterscale),lat2coord(row['latitude'].to_f,basey,masterscale),row['id'].to_i,nil,tag2array(row['tags'])]
263       xmin = [xmin,row['longitude'].to_f].min
264       xmax = [xmax,row['longitude'].to_f].max
265       ymin = [ymin,row['latitude'].to_f].min
266       ymax = [ymax,row['latitude'].to_f].max
267     }
268
269     attributes={}
270     attrlist=ActiveRecord::Base.connection.select_all "SELECT k,v FROM current_way_tags WHERE id=#{wayid}"
271     attrlist.each {|a| attributes[a['k'].gsub(':','|')]=a['v'] }
272
273     [objname,points,attributes,xmin,xmax,ymin,ymax]
274   end
275
276   # -----       putway (user token, way, array of co-ordinates, array of attributes,
277   #                                     baselong, basey, masterscale)
278   #                     returns current way ID, new way ID, hash of renumbered nodes,
279   #                                     xmin,xmax,ymin,ymax
280
281   def putway(args)
282     RAILS_DEFAULT_LOGGER.info("  putway started")
283     usertoken,originalway,points,attributes,baselong,basey,masterscale=args
284     uid=getuserid(usertoken)
285     return if !uid
286     RAILS_DEFAULT_LOGGER.info("  putway authenticated happily")
287     db_uqn='unin'+uid.to_s+originalway.to_i.abs.to_s+Time.new.to_i.to_s # temp uniquenodes table name, typically 51 chars
288     db_now='@now'+uid.to_s+originalway.to_i.abs.to_s+Time.new.to_i.to_s # 'now' variable name, typically 51 chars
289     ActiveRecord::Base.connection.execute("SET #{db_now}=NOW()")
290     originalway=originalway.to_i
291
292     RAILS_DEFAULT_LOGGER.info("  Message: putway, id=#{originalway}")
293
294     # -- 3.     read original way into memory
295
296     xc={}; yc={}; tagc={}
297     if originalway>0
298       way=originalway
299       readwayquery(way).each { |row|
300         id=row['id'].to_i
301         xc[id]=row['longitude'].to_f
302         yc[id]=row['latitude' ].to_f
303         tagc[id]=row['tags']
304       }
305       ActiveRecord::Base.connection.update("UPDATE current_ways SET timestamp=#{db_now},user_id=#{uid},visible=1 WHERE id=#{way}")
306     else
307       way=ActiveRecord::Base.connection.insert("INSERT INTO current_ways (user_id,timestamp,visible) VALUES (#{uid},#{db_now},1)")
308     end
309
310     # -- 4.     get version by inserting new row into ways
311
312     version=ActiveRecord::Base.connection.insert("INSERT INTO ways (id,user_id,timestamp,visible) VALUES (#{way},#{uid},#{db_now},1)")
313
314     # -- 5. compare nodes and update xmin,xmax,ymin,ymax
315
316     xmin=ymin= 999999
317     xmax=ymax=-999999
318     insertsql=''
319     renumberednodes={}
320         nodelist=[]
321
322     points.each_index do |i|
323       xs=coord2long(points[i][0],masterscale,baselong)
324       ys=coord2lat(points[i][1],masterscale,basey)
325       xmin=[xs,xmin].min; xmax=[xs,xmax].max
326       ymin=[ys,ymin].min; ymax=[ys,ymax].max
327       node=points[i][2].to_i
328           tagstr=array2tag(points[i][4])
329       tagsql="'"+sqlescape(tagstr)+"'"
330       lat=(ys * 10000000).round
331       long=(xs * 10000000).round
332       tile=QuadTile.tile_for_point(ys, xs)
333
334       # compare node
335       if node<0
336         # new node - create
337                 if renumberednodes[node.to_s].nil?
338           newnode=ActiveRecord::Base.connection.insert("INSERT INTO current_nodes (   latitude,longitude,timestamp,user_id,visible,tags,tile) VALUES (           #{lat},#{long},#{db_now},#{uid},1,#{tagsql},#{tile})")
339                   ActiveRecord::Base.connection.insert("INSERT INTO nodes         (id,latitude,longitude,timestamp,user_id,visible,tags,tile) VALUES (#{newnode},#{lat},#{long},#{db_now},#{uid},1,#{tagsql},#{tile})")
340           points[i][2]=newnode
341           nodelist.push(newnode)
342           renumberednodes[node.to_s]=newnode.to_s
343                 else
344           points[i][2]=renumberednodes[node.to_s].to_i
345                 end
346
347       elsif xc.has_key?(node)
348                 nodelist.push(node)
349         # old node from original way - update
350         if (xs!=xc[node] or (ys/0.0000001).round!=(yc[node]/0.0000001).round or tagstr!=tagc[node])
351           ActiveRecord::Base.connection.insert("INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible,tags,tile) VALUES (#{node},#{lat},#{long},#{db_now},#{uid},1,#{tagsql},#{tile})")
352           ActiveRecord::Base.connection.update("UPDATE current_nodes SET latitude=#{lat},longitude=#{long},timestamp=#{db_now},user_id=#{uid},tags=#{tagsql},visible=1,tile=#{tile} WHERE id=#{node}")
353         end
354       else
355         # old node, created in another way and now added to this way
356       end
357     end
358
359
360         # -- 6a. delete any nodes not in modified way
361
362     createuniquenodes(way,db_uqn,nodelist)      # nodes which appear in this way but no other
363
364     sql=<<-EOF
365         INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible,tile)  
366         SELECT DISTINCT cn.id,cn.latitude,cn.longitude,#{db_now},#{uid},0,cn.tile
367           FROM current_nodes AS cn,#{db_uqn}
368          WHERE cn.id=node_id
369     EOF
370     ActiveRecord::Base.connection.insert(sql)
371
372     sql=<<-EOF
373       UPDATE current_nodes AS cn, #{db_uqn}
374          SET cn.timestamp=#{db_now},cn.visible=0,cn.user_id=#{uid} 
375        WHERE cn.id=node_id
376     EOF
377     ActiveRecord::Base.connection.update(sql)
378
379         deleteuniquenoderelations(db_uqn,uid,db_now)
380     ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqn}")
381
382         #       6b. insert new version of route into way_nodes
383
384     insertsql =''
385     currentsql=''
386     sequence  =1
387     points.each do |p|
388       if insertsql !='' then insertsql +=',' end
389       if currentsql!='' then currentsql+=',' end
390       insertsql +="(#{way},#{p[2]},#{sequence},#{version})"
391       currentsql+="(#{way},#{p[2]},#{sequence})"
392       sequence  +=1
393     end
394
395     ActiveRecord::Base.connection.execute("DELETE FROM current_way_nodes WHERE id=#{way}");
396     ActiveRecord::Base.connection.insert( "INSERT INTO         way_nodes (id,node_id,sequence_id,version) VALUES #{insertsql}");
397     ActiveRecord::Base.connection.insert( "INSERT INTO current_way_nodes (id,node_id,sequence_id        ) VALUES #{currentsql}");
398
399     # -- 7. insert new way tags
400
401     insertsql =''
402     currentsql=''
403     attributes.each do |k,v|
404       if v=='' or v.nil? then next end
405       if v[0,6]=='(type ' then next end
406       if insertsql !='' then insertsql +=',' end
407       if currentsql!='' then currentsql+=',' end
408       insertsql +="(#{way},'"+sqlescape(k.gsub('|',':'))+"','"+sqlescape(v)+"',#{version})"
409       currentsql+="(#{way},'"+sqlescape(k.gsub('|',':'))+"','"+sqlescape(v)+"')"
410     end
411
412     ActiveRecord::Base.connection.execute("DELETE FROM current_way_tags WHERE id=#{way}")
413     if (insertsql !='') then ActiveRecord::Base.connection.insert("INSERT INTO way_tags (id,k,v,version) VALUES #{insertsql}" ) end
414     if (currentsql!='') then ActiveRecord::Base.connection.insert("INSERT INTO current_way_tags (id,k,v) VALUES #{currentsql}") end
415
416     [originalway,way,renumberednodes,xmin,xmax,ymin,ymax]
417   end
418
419   # -----       putpoi (user token, id, x,y,tag array,visible,baselong,basey,masterscale)
420   #                     returns current id, new id
421   #                     if new: add new row to current_nodes and nodes
422   #                     if old: add new row to nodes, update current_nodes
423
424   def putpoi(args)
425     usertoken,id,x,y,tags,visible,baselong,basey,masterscale=args
426     uid=getuserid(usertoken)
427     return if !uid
428     db_now='@now'+uid.to_s+id.to_i.abs.to_s+Time.new.to_i.to_s  # 'now' variable name, typically 51 chars
429     ActiveRecord::Base.connection.execute("SET #{db_now}=NOW()")
430
431     id=id.to_i
432     visible=visible.to_i
433         if visible==0 then
434                 # if deleting, check node hasn't become part of a way 
435                 inway=ActiveRecord::Base.connection.select_one("SELECT cw.id FROM current_ways cw,current_way_nodes cwn WHERE cw.id=cwn.id AND cw.visible=1 AND cwn.node_id=#{id} LIMIT 1")
436                 unless inway.nil? then return [id,id] end       # should really return an error
437                 deleteitemrelations(id,'node',uid,db_now)
438         end
439
440     x=coord2long(x.to_f,masterscale,baselong)
441     y=coord2lat(y.to_f,masterscale,basey)
442     tagsql="'"+sqlescape(array2tag(tags))+"'"
443     lat=(y * 10000000).round
444     long=(x * 10000000).round
445     tile=QuadTile.tile_for_point(y, x)
446         
447     if (id>0) then
448         ActiveRecord::Base.connection.insert("INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible,tags,tile) VALUES (#{id},#{lat},#{long},#{db_now},#{uid},#{visible},#{tagsql},#{tile})");
449         ActiveRecord::Base.connection.update("UPDATE current_nodes SET latitude=#{lat},longitude=#{long},timestamp=#{db_now},user_id=#{uid},visible=#{visible},tags=#{tagsql},tile=#{tile} WHERE id=#{id}");
450         newid=id
451     else
452         newid=ActiveRecord::Base.connection.insert("INSERT INTO current_nodes (latitude,longitude,timestamp,user_id,visible,tags,tile) VALUES (#{lat},#{long},#{db_now},#{uid},#{visible},#{tagsql},#{tile})");
453               ActiveRecord::Base.connection.update("INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible,tags,tile) VALUES (#{newid},#{lat},#{long},#{db_now},#{uid},#{visible},#{tagsql},#{tile})");
454     end
455     [id,newid]
456   end
457
458   # -----       getpoi (id,baselong,basey,masterscale)
459   #                     returns id,x,y,tag array
460   
461   def getpoi(args)
462         id,baselong,basey,masterscale=args; id=id.to_i
463         poi=ActiveRecord::Base.connection.select_one("SELECT latitude*0.0000001 AS lat,longitude*0.0000001 AS lng,tags "+
464                 "FROM current_nodes WHERE visible=1 AND id=#{id}")
465         if poi.nil? then return [nil,nil,nil,''] end
466         [id,
467          long2coord(poi['lng'].to_f,baselong,masterscale),
468          lat2coord(poi['lat'].to_f,basey,masterscale),
469          tag2array(poi['tags'])]
470   end
471
472   # -----       deleteway (user token, way, nodes to keep)
473   #                     returns way ID only
474
475   def deleteway(args)
476     usertoken,way,preserve=args
477
478     RAILS_DEFAULT_LOGGER.info("  Message: deleteway, id=#{way}")
479
480     uid=getuserid(usertoken); if !uid then return end
481     way=way.to_i
482
483     db_uqn='unin'+uid.to_s+way.to_i.abs.to_s+Time.new.to_i.to_s # temp uniquenodes table name, typically 51 chars
484     db_now='@now'+uid.to_s+way.to_i.abs.to_s+Time.new.to_i.to_s # 'now' variable name, typically 51 chars
485     ActiveRecord::Base.connection.execute("SET #{db_now}=NOW()")
486
487     # - delete any otherwise unused nodes
488   
489     createuniquenodes(way,db_uqn,[])
490
491         unless (preserve.empty?) then
492                 ActiveRecord::Base.connection.execute("DELETE FROM #{db_uqn} WHERE node_id IN ("+preserve.join(',')+")")
493         end
494
495     sql=<<-EOF
496         INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible,tile)
497         SELECT DISTINCT cn.id,cn.latitude,cn.longitude,#{db_now},#{uid},0,cn.tile
498           FROM current_nodes AS cn,#{db_uqn}
499          WHERE cn.id=node_id
500     EOF
501     ActiveRecord::Base.connection.insert(sql)
502
503     sql=<<-EOF
504       UPDATE current_nodes AS cn, #{db_uqn}
505          SET cn.timestamp=#{db_now},cn.visible=0,cn.user_id=#{uid} 
506        WHERE cn.id=node_id
507     EOF
508     ActiveRecord::Base.connection.update(sql)
509
510         deleteuniquenoderelations(db_uqn,uid,db_now)
511     ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqn}")
512
513     # - delete way
514         
515     ActiveRecord::Base.connection.insert("INSERT INTO ways (id,user_id,timestamp,visible) VALUES (#{way},#{uid},#{db_now},0)")
516     ActiveRecord::Base.connection.update("UPDATE current_ways SET user_id=#{uid},timestamp=#{db_now},visible=0 WHERE id=#{way}")
517     ActiveRecord::Base.connection.execute("DELETE FROM current_way_nodes WHERE id=#{way}")
518     ActiveRecord::Base.connection.execute("DELETE FROM current_way_tags WHERE id=#{way}")
519         deleteitemrelations(way,'way',uid,db_now)
520     way
521 end
522
523
524
525 # ====================================================================
526 # Support functions for remote calls
527
528 def readwayquery(id)
529   ActiveRecord::Base.connection.select_all "SELECT latitude*0.0000001 AS latitude,longitude*0.0000001 AS longitude,current_nodes.id,tags "+
530       "    FROM current_way_nodes,current_nodes "+
531       "   WHERE current_way_nodes.id=#{id} "+
532       "     AND current_way_nodes.node_id=current_nodes.id "+
533       "     AND current_nodes.visible=1 "+
534       "   ORDER BY sequence_id"
535 end
536
537 def createuniquenodes(way,uqn_name,nodelist)
538         # Find nodes which appear in this way but no others
539         sql=<<-EOF
540         CREATE TEMPORARY TABLE #{uqn_name}
541                                         SELECT a.node_id
542                                           FROM (SELECT DISTINCT node_id FROM current_way_nodes
543                                                         WHERE id=#{way}) a
544                                  LEFT JOIN current_way_nodes b
545                                                 ON b.node_id=a.node_id
546                                            AND b.id!=#{way}
547                                          WHERE b.node_id IS NULL
548         EOF
549         unless nodelist.empty? then
550           sql+="AND a.node_id NOT IN ("+nodelist.join(',')+")"
551         end
552         ActiveRecord::Base.connection.execute(sql)
553 end
554
555
556
557 # ====================================================================
558 # Relations handling
559 # deleteuniquenoderelations(uqn_name,uid,db_now)
560 # deleteitemrelations(way|node,'way'|'node',uid,db_now)
561
562 def deleteuniquenoderelations(uqn_name,uid,db_now)
563         sql=<<-EOF
564         SELECT node_id,cr.id FROM #{uqn_name},current_relation_members crm,current_relations cr 
565          WHERE crm.member_id=node_id 
566            AND crm.member_type='node' 
567            AND crm.id=cr.id 
568            AND cr.visible=1
569         EOF
570
571         relnodes=ActiveRecord::Base.connection.select_all(sql)
572         relnodes.each do |a|
573                 removefromrelation(a['node_id'],'node',a['id'],uid,db_now)
574         end
575 end
576
577 def deleteitemrelations(objid,type,uid,db_now)
578         sql=<<-EOF
579         SELECT cr.id FROM current_relation_members crm,current_relations cr 
580          WHERE crm.member_id=#{objid} 
581            AND crm.member_type='#{type}' 
582            AND crm.id=cr.id 
583            AND cr.visible=1
584         EOF
585         
586         relways=ActiveRecord::Base.connection.select_all(sql)
587         relways.each do |a|
588                 removefromrelation(objid,type,a['id'],uid,db_now)
589         end
590 end
591
592 def removefromrelation(objid,type,relation,uid,db_now)
593         rver=ActiveRecord::Base.connection.insert("INSERT INTO relations (id,user_id,timestamp,visible) VALUES (#{relation},#{uid},#{db_now},1)")
594
595         tagsql=<<-EOF
596         INSERT INTO relation_tags (id,k,v,version) 
597         SELECT id,k,v,#{rver} FROM current_relation_tags 
598          WHERE id=#{relation} 
599         EOF
600         ActiveRecord::Base.connection.insert(tagsql)
601
602         membersql=<<-EOF
603         INSERT INTO relation_members (id,member_type,member_id,member_role,version) 
604         SELECT id,member_type,member_id,member_role,#{rver} FROM current_relation_members 
605          WHERE id=#{relation} 
606            AND (member_id!=#{objid} OR member_type!='#{type}')
607         EOF
608         ActiveRecord::Base.connection.insert(membersql)
609         
610         ActiveRecord::Base.connection.update("UPDATE current_relations SET user_id=#{uid},timestamp=#{db_now} WHERE id=#{relation}")
611         ActiveRecord::Base.connection.execute("DELETE FROM current_relation_members WHERE id=#{relation} AND member_type='#{type}' AND member_id=#{objid}")
612 end
613
614
615 def sqlescape(a)
616   a.gsub(/[\000-\037]/,"").gsub("'","''").gsub(92.chr,92.chr+92.chr)
617 end
618
619 def tag2array(a)
620   tags={}
621   a.gsub(';;;','#%').split(';').each do |b|
622     b.gsub!('#%',';;;')
623     b.gsub!('===','#%')
624     k,v=b.split('=')
625     if k.nil? then k='' end
626     if v.nil? then v='' end
627     tags[k.gsub('#%','=').gsub(':','|')]=v.gsub('#%','=')
628   end
629   tags
630 end
631
632 def array2tag(a)
633   str=''
634   a.each do |k,v|
635     if v=='' then next end
636     if v[0,6]=='(type ' then next end
637     if str!='' then str+=';' end
638     str+=k.gsub(';',';;;').gsub('=','===').gsub('|',':')+'='+v.gsub(';',';;;').gsub('=','===')
639   end
640   str
641 end
642
643 def getuserid(token)
644   if (token =~ /^(.+)\+(.+)$/) then
645     user = User.authenticate(:username => $1, :password => $2)
646   else
647     user = User.authenticate(:token => token)
648   end
649
650   return user ? user.id : nil;
651 end
652
653
654
655 # ====================================================================
656 # AMF read subroutines
657
658 # ----- getint          return two-byte integer
659 # ----- getlong         return four-byte long
660 # ----- getstring       return string with two-byte length
661 # ----- getdouble       return eight-byte double-precision float
662 # ----- getobject       return object/hash
663 # ----- getarray        return numeric array
664
665 def getint(s)
666   s.getc*256+s.getc
667 end
668
669 def getlong(s)
670   ((s.getc*256+s.getc)*256+s.getc)*256+s.getc
671 end
672
673 def getstring(s)
674   len=s.getc*256+s.getc
675   s.read(len)
676 end
677
678 def getdouble(s)
679   a=s.read(8).unpack('G')                       # G big-endian, E little-endian
680   a[0]
681 end
682
683 def getarray(s)
684   len=getlong(s)
685   arr=[]
686   for i in (0..len-1)
687     arr[i]=getvalue(s)
688   end
689   arr
690 end
691
692 def getobject(s)
693   arr={}
694   while (key=getstring(s))
695     if (key=='') then break end
696     arr[key]=getvalue(s)
697   end
698   s.getc                # skip the 9 'end of object' value
699   arr
700 end
701
702 # ----- getvalue        parse and get value
703
704 def getvalue(s)
705   case s.getc
706         when 0; return getdouble(s)                     # number
707         when 1; return s.getc                           # boolean
708         when 2; return getstring(s)                     # string
709         when 3; return getobject(s)                     # object/hash
710         when 5; return nil                                      # null
711         when 6; return nil                                      # undefined
712         when 8; s.read(4)                                       # mixedArray
713                         return getobject(s)                     #  |
714         when 10;return getarray(s)                      # array
715         else;   return nil                                      # error
716   end
717 end
718
719 # ====================================================================
720 # AMF write subroutines
721
722 # ----- putdata         envelope data into AMF writeable form
723 # ----- encodevalue     pack variables as AMF
724
725 def putdata(index,n)
726   d =encodestring(index+"/onResult")
727   d+=encodestring("null")
728   d+=[-1].pack("N")
729   d+=encodevalue(n)
730 end
731
732 def encodevalue(n)
733   case n.class.to_s
734   when 'Array'
735     a=10.chr+encodelong(n.length)
736     n.each do |b|
737       a+=encodevalue(b)
738     end
739     a
740   when 'Hash'
741     a=3.chr
742     n.each do |k,v|
743       a+=encodestring(k)+encodevalue(v)
744     end
745     a+0.chr+0.chr+9.chr
746   when 'String'
747     2.chr+encodestring(n)
748   when 'Bignum','Fixnum','Float'
749     0.chr+encodedouble(n)
750   when 'NilClass'
751     5.chr
752   else
753     RAILS_DEFAULT_LOGGER.error("Unexpected Ruby type for AMF conversion: "+n.class.to_s)
754   end
755 end
756
757 # ----- encodestring    encode string with two-byte length
758 # ----- encodedouble    encode number as eight-byte double precision float
759 # ----- encodelong              encode number as four-byte long
760
761 def encodestring(n)
762   a,b=n.size.divmod(256)
763   a.chr+b.chr+n
764 end
765
766 def encodedouble(n)
767   [n].pack('G')
768 end
769
770 def encodelong(n)
771   [n].pack('N')
772 end
773
774 # ====================================================================
775 # Co-ordinate conversion
776
777 def lat2coord(a,basey,masterscale)
778   -(lat2y(a)-basey)*masterscale+250
779 end
780
781 def long2coord(a,baselong,masterscale)
782   (a-baselong)*masterscale+350
783 end
784
785 def lat2y(a)
786   180/Math::PI * Math.log(Math.tan(Math::PI/4+a*(Math::PI/180)/2))
787 end
788
789 def coord2lat(a,masterscale,basey)
790   y2lat((a-250)/-masterscale+basey)
791 end
792
793 def coord2long(a,masterscale,baselong)
794   (a-350)/masterscale+baselong
795 end
796
797 def y2lat(a)
798   180/Math::PI * (2*Math.atan(Math.exp(a*Math::PI/180))-Math::PI/2)
799 end
800
801 end