Potlatch on Rails, first working version
[rails.git] / app / controllers / amf_controller.rb
1 class AmfController < ApplicationController
2   require 'stringio'
3
4 # to log:
5 # RAILS_DEFAULT_LOGGER.error("Args: #{args[0]}, #{args[1]}, #{args[2]}, #{args[3]}")
6
7         # doesn't appear to set old versions of ways to invisible
8         # not sure about segments, either...
9
10
11   # ====================================================================
12   # Main AMF handler
13   
14   # ---- talk   process AMF request
15
16   def talk
17         req=StringIO.new(request.raw_post)      # Get POST data as request
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         for i in (1..headers)                           # Read each header
26                 name=getstring(req)                             #  |
27                 req.getc                                                #  | skip boolean
28                 value=getvalue(req)                             #  |
29                 header["name"]=value                    #  |
30         end
31
32         bodies=getint(req)                                      # Read number of bodies
33         for i in (1..bodies)                            # Read each body
34                 message=getstring(req)                  #  | get message name
35                 index=getstring(req)                    #  | get index in response sequence
36                 bytes=getlong(req)                              #  | get total size in bytes
37                 args=getvalue(req)                              #  | get response (probably an array)
38         
39                 case message
40                         when 'getpresets';      results[index]=putdata(index,getpresets)
41                         when 'whichways';       results[index]=putdata(index,whichways(args))
42                         when 'getway';          results[index]=putdata(index,getway(args))
43                         when 'putway';          results[index]=putdata(index,putway(args))
44                         when 'deleteway';       results[index]=putdata(index,deleteway(args))
45                 end
46         end
47
48         # ------------------
49         # Write out response
50
51         response.headers["Content-Type"]="application/x-amf"
52         a,b=results.length.divmod(256)
53         ans=0.chr+0.chr+0.chr+0.chr+a.chr+b.chr
54         results.each do |k,v|
55                 ans+=v
56         end
57         render :text=>ans
58
59   end
60
61
62         # ====================================================================
63         # Remote calls
64
65         # ----- getpresets
66         #               return presets,presetmenus and presetnames arrays
67
68         def getpresets
69                 presets={}
70                 presetmenus={}; presetmenus['point']=[]; presetmenus['way']=[]
71                 presetnames={}; presetnames['point']={}; presetnames['way']={}
72                 presettype=''
73                 presetcategory=''
74                 
75                 File.open("config/potlatch/presets.txt") do |file|
76                         file.each_line {|line|
77                                 t=line.chomp
78                                 if (t=~/(\w+)\/(\w+)/) then
79                                         presettype=$1
80                                         presetcategory=$2
81                                         presetmenus[presettype].push(presetcategory)
82                                         presetnames[presettype][presetcategory]=["(no preset)"]
83                                 elsif (t=~/^(.+):\s?(.+)$/) then
84                                         pre=$1; kv=$2
85                                         presetnames[presettype][presetcategory].push(pre)
86                                         presets[pre]={}
87                                         kv.split(',').each {|a|
88                                                 if (a=~/^(.+)=(.*)$/) then presets[pre][$1]=$2 end
89                                         }
90                                 end
91                         }
92                 end
93                 [presets,presetmenus,presetnames]
94         end
95
96         # ----- whichways(left,bottom,right,top)
97         #               return array of ways in current bounding box
98         #               at present, instead of using correct (=more complex) SQL to find
99         #               corner-crossing ways, it simply enlarges the bounding box by +/- 0.01
100         
101         def whichways(args)
102                 waylist=WaySegment.find_by_sql("SELECT DISTINCT current_way_segments.id AS wayid"+
103                          "  FROM current_way_segments,current_segments,current_nodes "+
104                          " WHERE segment_id=current_segments.id "+
105                          "   AND node_a=current_nodes.id "+
106                          "   AND (latitude  BETWEEN "+(args[1].to_f-0.01).to_s+" AND "+(args[3].to_f+0.01).to_s+") "+
107                          "   AND (longitude BETWEEN "+(args[0].to_f-0.01).to_s+" AND "+(args[2].to_f+0.01).to_s+")")
108                 ways=[]
109                 waylist.each {|a|
110                         ways<<a.wayid.to_i
111                 }
112                 ways
113         end
114
115         # ----- getway (objectname, way, baselong, basey, masterscale)
116         #               returns objectname, array of co-ordinates, attributes,
117         #                               xmin,xmax,ymin,ymax
118         
119         def getway(args)
120                 objname,wayid,baselong,basey,masterscale=args
121                 wayid=wayid.to_i
122                 points=[]
123                 lastid=-1
124                 xmin=999999; xmax=-999999
125                 ymin=999999; ymax=-999999
126
127                 readwayquery(wayid).each {|row|
128                         xs1=long2coord(row['long1'].to_f,baselong,masterscale); ys1=lat2coord(row['lat1'].to_f,basey,masterscale)
129                         xs2=long2coord(row['long2'].to_f,baselong,masterscale); ys2=lat2coord(row['lat2'].to_f,basey,masterscale)
130                         if (row['id1'].to_i!=lastid)
131                                 points<<[xs1,ys1,row['id1'].to_i,0,tag2array(row['tags1']),0]
132                         end
133                         lastid=row['id2'].to_i
134                         points<<[xs2,ys2,row['id2'].to_i,1,tag2array(row['tags2']),row['segment_id'].to_i]
135                         xmin=[xmin,row['long1'].to_f,row['long2'].to_f].min
136                         xmax=[xmax,row['long1'].to_f,row['long2'].to_f].max
137                         ymin=[ymin,row['lat1'].to_f,row['lat2'].to_f].min
138                         ymax=[ymax,row['lat1'].to_f,row['lat2'].to_f].max
139                 }
140
141                 attributes={}
142                 attrlist=ActiveRecord::Base.connection.select_all "SELECT k,v FROM current_way_tags WHERE id=#{wayid}"
143                 attrlist.each {|a| attributes[a['k']]=a['v'] }
144
145                 [objname,points,attributes,xmin,xmax,ymin,ymax]
146         end
147         
148         # ----- putway (user token, way, array of co-ordinates, array of attributes,
149         #                               baselong, basey, masterscale)
150         #               returns current way ID, new way ID, hash of renumbered nodes,
151         #                               xmin,xmax,ymin,ymax
152
153         def putway(args)
154                 usertoken,originalway,points,attributes,baselong,basey,masterscale=args
155                 uid=getuserid(usertoken); if !uid then return end
156                 db_uqs='uniq'+usertoken+originalway.to_i.abs.to_s+Time.new.to_i.to_s    # temp uniquesegments table name, typically 51 chars
157                 db_now='@now'+usertoken+originalway.to_i.abs.to_s+Time.new.to_i.to_s    # 'now' variable name, typically 51 chars
158                 ActiveRecord::Base.connection.execute("SET #{db_now}=NOW()")
159                 originalway=originalway.to_i
160                 
161                 # -- 3. read original way into memory
162         
163                 xc={}; yc={}; tagc={}; seg={}
164                 if (originalway>0)
165                         way=originalway
166                         readwayquery(way).each { |row|
167                                 id1=row['id1'].to_i; xc[id1]=row['long1'].to_f; yc[id1]=row['lat1'].to_f; tagc[id1]=row['tags1']
168                                 id2=row['id2'].to_i; xc[id2]=row['long2'].to_f; yc[id2]=row['lat2'].to_f; tagc[id2]=row['tags2']
169                                 seg[row['segment_id'].to_i]=id1.to_s+'-'+id2.to_s
170                         }
171                 else
172                         way=ActiveRecord::Base.connection.insert("INSERT INTO current_ways (user_id,timestamp,visible) VALUES (#{uid},#{db_now},1)")
173                 end
174
175                 # -- 4. get version by inserting new row into ways
176
177                 version=ActiveRecord::Base.connection.insert("INSERT INTO ways (id,user_id,timestamp,visible) VALUES (#{way},#{uid},#{db_now},1)")
178
179                 # -- 5. compare nodes and update xmin,xmax,ymin,ymax
180
181                 xmin=999999; xmax=-999999
182                 ymin=999999; ymax=-999999
183                 insertsql=''
184                 nodelist=''
185                 renumberednodes={}
186         
187                 points.each_index do |i|
188                         xs=coord2long(points[i][0],masterscale,baselong)
189                         ys=coord2lat(points[i][1],masterscale,basey)
190                         xmin=[xs,xmin].min; xmax=[xs,xmax].max
191                         ymin=[ys,ymin].min; ymax=[ys,ymax].max
192                         node=points[i][2].to_i
193                         tagstr=array2tag(points[i][4])
194                         tagsql="'"+sqlescape(tagstr)+"'"
195         
196                         # compare node
197                         if node<0
198                                 # new node - create
199                                 newnode=ActiveRecord::Base.connection.insert("INSERT INTO current_nodes (   latitude,longitude,timestamp,user_id,visible,tags) VALUES (           #{ys},#{xs},#{db_now},#{uid},1,#{tagsql})")
200                                                 ActiveRecord::Base.connection.insert("INSERT INTO nodes         (id,latitude,longitude,timestamp,user_id,visible,tags) VALUES (#{newnode},#{ys},#{xs},#{db_now},#{uid},1,#{tagsql})")
201                                 points[i][2]=newnode
202                                 renumberednodes[node.to_s]=newnode.to_s
203                                 
204                         elsif xc.has_key?(node)
205                                 # old node from original way - update
206                                 if (xs!=xc[node] or (ys/0.0000001).round!=(yc[node]/0.0000001).round or tagstr!=tagc[node])
207                                         ActiveRecord::Base.connection.insert("INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible,tags) VALUES (#{node},#{ys},#{xs},#{db_now},#{uid},1,#{tagsql})")
208                                         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}")
209                                 else
210                                         if (nodelist!='') then nodelist+=',' end; nodelist+=node.to_s
211                                 end
212                         else
213                                 # old node, created in another way and now added to this way
214                                 if (nodelist!='') then nodelist+=',' end; nodelist+=node.to_s
215                         end
216         
217                 end
218
219                 if nodelist!='' then
220                         ActiveRecord::Base.connection.update("UPDATE current_nodes SET timestamp=#{db_now},user_id=#{uid},visible=1 WHERE id IN (#{nodelist})")
221                 end
222
223                 # -- 6.i compare segments
224         
225                 numberedsegments={}
226                 seglist=''
227                 for i in (0..(points.length-2))
228                         if (points[i+1][3].to_i==0) then next end
229                         segid=points[i+1][5].to_i
230                         from =points[i  ][2].to_i
231                         to   =points[i+1][2].to_i
232                         if seg.has_key?(segid)
233                                 if seg[segid]=="#{from}-#{to}" then 
234                                         if (seglist!='') then seglist+=',' end; seglist+=segid.to_s
235                                         next
236                                 end
237                         end
238                         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,'')")
239                                   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,'')")
240                         points[i+1][5]=segid
241                         numberedsegments[(i+1).to_s]=segid.to_s
242                 end
243                 # numberedsegments.each{|a,b| RAILS_DEFAULT_LOGGER.error("Sending back: seg no. #{a} -> id #{b}") }
244
245                 if seglist!='' then
246                         ActiveRecord::Base.connection.update("UPDATE current_segments SET timestamp=#{db_now},user_id=#{uid},visible=1 WHERE id IN (#{seglist})")
247                 end
248
249
250                 # -- 6.ii insert new way segments
251
252                 createuniquesegments(way,db_uqs)
253
254                 # a=''
255                 # ActiveRecord::Base.connection.select_values("SELECT segment_id FROM #{db_uqs}").each {|b| a+=b+',' }
256                 # RAILS_DEFAULT_LOGGER.error("Unique segments are #{a}")
257                 # a=ActiveRecord::Base.connection.select_value("SELECT #{db_now}")
258                 # RAILS_DEFAULT_LOGGER.error("Timestamp of this edit is #{a}")
259                 # RAILS_DEFAULT_LOGGER.error("Userid of this edit is #{uid}")
260
261                 #               delete nodes from uniquesegments (and not in modified way)
262         
263                 sql=<<-EOF
264                         INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible)  
265                         SELECT DISTINCT cn.id,cn.latitude,cn.longitude,#{db_now},#{uid},0 
266                           FROM current_nodes AS cn, 
267                                    current_segments AS cs,
268                                    #{db_uqs} AS us 
269                          WHERE(cn.id=cs.node_a OR cn.id=cs.node_b) 
270                            AND cs.id=us.segment_id AND cs.visible=1 
271                            AND (cn.timestamp!=#{db_now} OR cn.user_id!=#{uid})
272                 EOF
273                 ActiveRecord::Base.connection.insert(sql)
274
275                 sql=<<-EOF
276                         UPDATE current_nodes AS cn, 
277                                    current_segments AS cs, 
278                                    #{db_uqs} AS us 
279                            SET cn.timestamp=#{db_now},cn.visible=0,cn.user_id=#{uid} 
280                          WHERE (cn.id=cs.node_a OR cn.id=cs.node_b) 
281                            AND cs.id=us.segment_id AND cs.visible=1 
282                            AND (cn.timestamp!=#{db_now} OR cn.user_id!=#{uid})
283                 EOF
284                 ActiveRecord::Base.connection.update(sql)
285         
286                 #               delete segments from uniquesegments (and not in modified way)
287         
288                 sql=<<-EOF
289                         INSERT INTO segments (id,node_a,node_b,timestamp,user_id,visible) 
290                         SELECT DISTINCT segment_id,node_a,node_b,#{db_now},#{uid},0
291                           FROM current_segments AS cs, #{db_uqs} AS us
292                          WHERE cs.id=us.segment_id AND cs.visible=1 
293                            AND (cs.timestamp!=#{db_now} OR cs.user_id!=#{uid})
294                 EOF
295                 ActiveRecord::Base.connection.insert(sql)
296                 
297                 sql=<<-EOF
298                            UPDATE current_segments AS cs, #{db_uqs} AS us
299                                   SET cs.timestamp=#{db_now},cs.visible=0,cs.user_id=#{uid} 
300                                 WHERE cs.id=us.segment_id AND cs.visible=1 
301                                   AND (cs.timestamp!=#{db_now} OR cs.user_id!=#{uid})
302                 EOF
303                 ActiveRecord::Base.connection.update(sql)
304                 ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqs}")
305
306                 #               insert new version of route into way_segments
307         
308                 insertsql =''
309                 currentsql=''
310                 sequence  =1
311                 for i in (0..(points.length-2))
312                         if (points[i+1][3].to_i==0) then next end
313                         if insertsql !='' then insertsql +=',' end
314                         if currentsql!='' then currentsql+=',' end
315                         insertsql +="(#{way},#{points[i+1][5]},#{version})"
316                         currentsql+="(#{way},#{points[i+1][5]},#{sequence})"
317                         sequence  +=1
318                 end
319                 
320                 ActiveRecord::Base.connection.execute("DELETE FROM current_way_segments WHERE id=#{way}");
321                 ActiveRecord::Base.connection.insert("INSERT INTO         way_segments (id,segment_id,version    ) VALUES #{insertsql}");
322                 ActiveRecord::Base.connection.insert("INSERT INTO current_way_segments (id,segment_id,sequence_id) VALUES #{currentsql}");
323         
324                 # -- 7. insert new way tags
325         
326                 insertsql =''
327                 currentsql=''
328                 attributes.each do |k,v|
329                         if v=='' then next end
330                         if v[0,6]=='(type ' then next end
331                         if insertsql !='' then insertsql +=',' end
332                         if currentsql!='' then currentsql+=',' end
333                         insertsql +="(#{way},'"+sqlescape(k)+"','"+sqlescape(v)+"',version)"
334                         currentsql+="(#{way},'"+sqlescape(k)+"','"+sqlescape(v)+"')"
335                 end
336                 
337                 ActiveRecord::Base.connection.execute("DELETE FROM current_way_tags WHERE id=#{way}")
338                 if (insertsql !='') then ActiveRecord::Base.connection.insert("INSERT INTO way_tags (id,k,v,version) VALUES #{insertsql}" ) end
339                 if (currentsql!='') then ActiveRecord::Base.connection.insert("INSERT INTO current_way_tags (id,k,v) VALUES #{currentsql}") end
340         
341                 [originalway,way,renumberednodes,numberedsegments,xmin,xmax,ymin,ymax]
342         end
343         
344         # ----- deleteway (user token, way)
345         #               returns way ID only
346         
347         def deleteway(args)
348                 usertoken,way=args
349                 uid=getuserid(usertoken); if !uid then return end
350                 way=way.to_i
351
352                 db_uqs='uniq'+usertoken+way.to_i.abs.to_s+Time.new.to_i.to_s    # temp uniquesegments table name, typically 51 chars
353                 db_now='@now'+usertoken+way.to_i.abs.to_s+Time.new.to_i.to_s    # 'now' variable name, typically 51 chars
354                 ActiveRecord::Base.connection.execute("SET #{db_now}=NOW()")
355                 createuniquesegments(way,db_uqs)
356         
357                 sql=<<-EOF
358                         INSERT INTO nodes (id,latitude,longitude,timestamp,user_id,visible) 
359                         SELECT DISTINCT cn.id,cn.latitude,cn.longitude,#{db_now},#{uid},0 
360                           FROM current_nodes AS cn, 
361                                    current_segments AS cs, 
362                                    #{db_uqs} AS us
363                          WHERE (cn.id=cs.node_a OR cn.id=cs.node_b) 
364                            AND cs.id=us.segment_id
365                 EOF
366                 ActiveRecord::Base.connection.insert(sql)
367         
368                 sql=<<-EOF
369                         UPDATE current_nodes AS cn, 
370                                    current_segments AS cs, 
371                                    #{db_uqs} AS us
372                            SET cn.timestamp=#{db_now},cn.visible=0,cn.user_id=#{uid} 
373                          WHERE (cn.id=cs.node_a OR cn.id=cs.node_b) 
374                            AND cs.id=us.segment_id
375                 EOF
376                 ActiveRecord::Base.connection.update(sql)
377         
378                 # -     delete any otherwise unused segments
379                                 
380                 sql=<<-EOF
381                         INSERT INTO segments (id,node_a,node_b,timestamp,user_id,visible) 
382                         SELECT DISTINCT segment_id,node_a,node_b,#{db_now},#{uid},0 
383                           FROM current_segments AS cs, #{db_uqs} AS us
384                          WHERE cs.id=us.segment_id
385                 EOF
386                 ActiveRecord::Base.connection.insert(sql)
387                                 
388                 sql=<<-EOF
389                         UPDATE current_segments AS cs, #{db_uqs} AS us
390                            SET cs.timestamp=#{db_now},cs.visible=0,cs.user_id=#{uid} 
391                          WHERE cs.id=us.segment_id
392                 EOF
393                 ActiveRecord::Base.connection.update(sql)
394                 ActiveRecord::Base.connection.execute("DROP TABLE #{db_uqs}")
395         
396                 # - delete way
397                 
398                 ActiveRecord::Base.connection.insert("INSERT INTO ways (id,user_id,timestamp,visible) VALUES (#{way},#{uid},#{db_now},0)")
399                 ActiveRecord::Base.connection.update("UPDATE current_ways SET user_id=#{uid},timestamp=#{db_now},visible=0 WHERE id=#{way}")
400                 ActiveRecord::Base.connection.execute("DELETE FROM current_way_segments WHERE id=#{way}")
401                 ActiveRecord::Base.connection.execute("DELETE FROM current_way_tags WHERE id=#{way}")
402         
403                 way
404         end
405         
406         # ====================================================================
407         # Support functions for remote calls
408
409         def readwayquery(id)
410                 ActiveRecord::Base.connection.select_all "SELECT n1.latitude AS lat1,n1.longitude AS long1,n1.id AS id1,n1.tags as tags1, "+
411                         "                 n2.latitude AS lat2,n2.longitude AS long2,n2.id AS id2,n2.tags as tags2,segment_id "+
412                         "    FROM current_way_segments,current_segments,current_nodes AS n1,current_nodes AS n2 "+
413                         "   WHERE current_way_segments.id=#{id} "+
414                         "     AND segment_id=current_segments.id "+
415                         "     AND n1.id=node_a and n2.id=node_b "+
416                         "   ORDER BY sequence_id"
417         end
418
419         def createuniquesegments(way,uqs_name)
420                 sql=<<-EOF
421                         CREATE TEMPORARY TABLE #{uqs_name}
422                                                         SELECT a.segment_id,COUNT(a.segment_id) AS ct
423                                                           FROM current_way_segments AS a, current_way_segments AS b
424                                                          WHERE a.segment_id=b.segment_id 
425                                                            AND a.id=#{way} 
426                                                   GROUP BY a.segment_id
427                                                         HAVING ct=1
428                 EOF
429                 ActiveRecord::Base.connection.execute(sql)
430         end
431         
432
433         def sqlescape(a)
434                 a.gsub("'","''").gsub(92.chr,92.chr+92.chr)
435         end
436
437         def tag2array(a)
438                 tags={}
439                 a.gsub(';;;','#%').split(';').each do |b|
440                         b.gsub!('#%',';;;')
441                         b.gsub!('===','#%')
442                         k,v=b.split('=')
443                         tags[k.gsub('#%','=')]=v.gsub('#%','=')
444                 end
445                 tags
446         end
447
448         def array2tag(a)
449                 str=''
450                 a.each do |k,v|
451                         if v=='' then next end
452                         if v[0,6]=='(type ' then next end
453                         if str!='' then str+=';' end
454                         str+=k.gsub(';',';;;').gsub('=','===')+'='+v.gsub(';',';;;').gsub('=','===')
455                 end
456                 str
457         end
458         
459         def getuserid(token)
460                 token=sqlescape(token)
461                 ActiveRecord::Base.connection.select_value("SELECT id FROM users WHERE token='#{token}' AND active=1 AND timeout>NOW()")
462         end
463         
464
465
466         # ====================================================================
467         # AMF read subroutines
468         
469         # ----- getint          return two-byte integer
470         # ----- getlong         return four-byte long
471         # ----- getstring       return string with two-byte length
472         # ----- getdouble       return eight-byte double-precision float
473         # ----- getobject       return object/hash
474         # ----- getarray        return numeric array
475         
476         def getint(s)
477                 s.getc*256+s.getc
478         end
479         
480         def getlong(s)
481                 ((s.getc*256+s.getc)*256+s.getc)*256+s.getc
482         end
483         
484         def getstring(s)
485                 len=s.getc*256+s.getc
486                 s.read(len)
487         end
488         
489         def getdouble(s)
490                 a=s.read(8).unpack('G')                 # G big-endian, E little-endian
491                 a[0]
492         end
493         
494         def getarray(s)
495                 len=getlong(s)
496                 arr=[]
497                 for i in (0..len-1)
498                         arr[i]=getvalue(s)
499                 end
500                 arr
501         end
502         
503         def getobject(s)
504                 arr={}
505                 while (key=getstring(s))
506                         if (key=='') then break end
507                         arr[key]=getvalue(s)
508                 end
509                 s.getc          # skip the 9 'end of object' value
510                 arr
511         end
512         
513         # ----- getvalue        parse and get value
514         
515         def getvalue(s)
516                 case s.getc
517                         when 0; return getdouble(s)                     # number
518                         when 1; return s.getc                           # boolean
519                         when 2; return getstring(s)                     # string
520                         when 3; return getobject(s)                     # object/hash
521                         when 5; return nil                                      # null
522                         when 6; return nil                                      # undefined
523                         when 8; s.read(4)                                       # mixedArray
524                                         return getobject(s)                     #  |
525                         when 10;return getarray(s)                      # array
526                         else;   return nil                                      # error
527                 end
528         end
529
530         # ====================================================================
531         # AMF write subroutines
532         
533         # ----- putdata         envelope data into AMF writeable form
534         # ----- encodevalue     pack variables as AMF
535         
536         def putdata(index,n)
537                 d =encodestring(index+"/onResult")
538                 d+=encodestring("null")
539                 d+=[-1].pack("N")
540                 d+=encodevalue(n)
541         end
542         
543         def encodevalue(n)
544                 case n.class.to_s
545                         when 'Array'
546                                 a=10.chr+encodelong(n.length)
547                                 n.each do |b|
548                                         a+=encodevalue(b)
549                                 end
550                                 a
551                         when 'Hash'
552                                 a=3.chr
553                                 n.each do |k,v|
554                                         a+=encodestring(k)+encodevalue(v)
555                                 end
556                                 a+0.chr+0.chr+9.chr
557                         when 'String'
558                                 2.chr+encodestring(n)
559                         when 'Bignum','Fixnum','Float'
560                                 0.chr+encodedouble(n)
561                         when 'NilClass'
562                                 5.chr
563                         else
564                                 RAILS_DEFAULT_LOGGER.error("Unexpected Ruby type for AMF conversion: "+n.class.to_s)
565                 end
566         end
567         
568         # ----- encodestring    encode string with two-byte length
569         # ----- encodedouble    encode number as eight-byte double precision float
570         # ----- encodelong              encode number as four-byte long
571         
572         def encodestring(n)
573                 a,b=n.size.divmod(256)
574                 a.chr+b.chr+n
575         end
576         
577         def encodedouble(n)
578                 [n].pack('G')
579         end
580         
581         def encodelong(n)
582                 [n].pack('N')
583         end
584         
585         # ====================================================================
586         # Co-ordinate conversion
587         
588         def lat2coord(a,basey,masterscale)
589                 -(lat2y(a)-basey)*masterscale+250
590         end
591         
592         def long2coord(a,baselong,masterscale)
593                 (a-baselong)*masterscale+350
594         end
595         
596         def lat2y(a)
597                 180/Math::PI * Math.log(Math.tan(Math::PI/4+a*(Math::PI/180)/2))
598         end
599         
600         def coord2lat(a,masterscale,basey)
601                 y2lat((a-250)/-masterscale+basey)
602         end
603         
604         def coord2long(a,masterscale,baselong)
605                 (a-350)/masterscale+baselong
606         end
607         
608         def y2lat(a)
609                 180/Math::PI * (2*Math.atan(Math.exp(a*Math::PI/180))-Math::PI/2)
610         end
611
612
613 end