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