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