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