Potlatch whichways and getway. These aren't really Rails, they're just SQL grafted...
[rails.git] / app / controllers / amf_controller.rb
1 class AmfController < ApplicationController
2   require 'stringio'
3
4   # Still to do:
5   # - all db interaction
6   # - user authentication
7   # - (also pass lat/lon through from view tab to edit tab)
8
9 # to log:
10 # RAILS_DEFAULT_LOGGER.error("Args: #{args[0]}, #{args[1]}, #{args[2]}, #{args[3]}")
11
12
13   # ====================================================================
14   # Main AMF handler
15   
16   # ---- talk   process AMF request
17
18   def talk
19         req=StringIO.new(request.raw_post)      # Get POST data as request
20         req.read(2)                                                     # Skip version indicator and client ID
21         results={}                                                      # Results of each body
22
23         # -------------
24         # Parse request
25
26         headers=getint(req)                                     # Read number of headers
27         for i in (1..headers)                           # Read each header
28                 name=getstring(req)                             #  |
29                 req.getc                                                #  | skip boolean
30                 value=getvalue(req)                             #  |
31                 header["name"]=value                    #  |
32         end
33
34         bodies=getint(req)                                      # Read number of bodies
35         for i in (1..bodies)                            # Read each body
36                 message=getstring(req)                  #  | get message name
37                 index=getstring(req)                    #  | get index in response sequence
38                 bytes=getlong(req)                              #  | get total size in bytes
39                 args=getvalue(req)                              #  | get response (probably an array)
40         
41                 case message
42                         when 'getpresets';      results[index]=putdata(index,getpresets)
43                         when 'whichways';       results[index]=putdata(index,whichways(args))
44                         when 'getway';          results[index]=putdata(index,getway(args))
45                         when 'putway';          results[index]=putdata(index,putway(args))
46                         when 'deleteway';       results[index]=putdata(index,deleteway(args))
47                 end
48         end
49
50         # ------------------
51         # Write out response
52
53         response.headers["Content-Type"]="application/x-amf"
54         a,b=results.length.divmod(256)
55         ans=0.chr+0.chr+0.chr+0.chr+a.chr+b.chr
56         results.each do |k,v|
57                 ans+=v
58         end
59         render :text=>ans
60
61   end
62
63
64         # ====================================================================
65         # Remote calls
66
67         def getpresets
68                 presets={}
69                 presetmenus={}; presetmenus['point']=[]; presetmenus['way']=[]
70                 presetnames={}; presetnames['point']={}; presetnames['way']={}
71                 presettype=''
72                 presetcategory=''
73                 
74                 File.open("config/potlatch/presets.txt") do |file|
75                         file.each_line {|line|
76                                 t=line.chomp
77                                 if (t=~/(\w+)\/(\w+)/) then
78                                         presettype=$1
79                                         presetcategory=$2
80                                         presetmenus[presettype].push(presetcategory)
81                                         presetnames[presettype][presetcategory]=["(no preset)"]
82                                 elsif (t=~/^(.+):\s?(.+)$/) then
83                                         pre=$1; kv=$2
84                                         presetnames[presettype][presetcategory].push(pre)
85                                         presets[pre]={}
86                                         kv.split(',').each {|a|
87                                                 if (a=~/^(.+)=(.*)$/) then presets[pre][$1]=$2 end
88                                         }
89                                 end
90                         }
91                 end
92                 [presets,presetmenus,presetnames]
93         end
94
95         def whichways(args)
96                 waylist=WaySegment.find_by_sql("SELECT DISTINCT current_way_segments.id AS wayid"+
97                          "  FROM current_way_segments,current_segments,current_nodes "+
98                          " WHERE segment_id=current_segments.id "+
99                          "   AND node_a=current_nodes.id "+
100                          "   AND (latitude  BETWEEN "+(args[1].to_f-0.01).to_s+" AND "+(args[3].to_f+0.01).to_s+") "+
101                          "   AND (longitude BETWEEN "+(args[0].to_f-0.01).to_s+" AND "+(args[2].to_f+0.01).to_s+")")
102                 ways=[]
103                 waylist.each {|a|
104                         ways<<a.wayid.to_i
105 RAILS_DEFAULT_LOGGER.error("Found #{a.wayid.to_i}")
106                 }
107                 ways
108         end
109         
110         def getway(args)
111                 objname,wayid,$baselong,$basey,$masterscale=args
112                 wayid=wayid.to_i
113                 points=[]
114                 lastid=-1
115                 xmin=999999; xmax=-999999
116                 ymin=999999; ymax=-999999
117
118 RAILS_DEFAULT_LOGGER.error("Looking for way #{wayid}")
119                 nodelist=ActiveRecord::Base.connection.select_all "SELECT n1.latitude AS lat1,n1.longitude AS long1,n1.id AS id1,n1.tags as tags1, "+
120                         "                 n2.latitude AS lat2,n2.longitude AS long2,n2.id AS id2,n2.tags as tags2,segment_id "+
121                         "    FROM current_way_segments,current_segments,current_nodes AS n1,current_nodes AS n2 "+
122                         "   WHERE current_way_segments.id=#{wayid} "+
123                         "     AND segment_id=current_segments.id "+
124                         "     AND n1.id=node_a and n2.id=node_b "+
125                         "   ORDER BY sequence_id"
126                 nodelist.each {|row|
127                         xs1=long2coord(row['long1'].to_f); ys1=lat2coord(row['lat1'].to_f)
128                         xs2=long2coord(row['long2'].to_f); ys2=lat2coord(row['lat2'].to_f)
129                         if (row['id1'].to_i!=lastid)
130                                 points<<[xs1,ys1,row['id1'].to_i,0,tag2array(row['tags1']),0]
131                         end
132                         lastid=row['id2'].to_i
133                         points<<[xs2,ys2,row['id2'].to_i,1,tag2array(row['tags2']),row['segment_id'].to_i]
134                         xmin=[xmin,row['long1'].to_f,row['long2'].to_f].min
135                         xmax=[xmax,row['long1'].to_f,row['long2'].to_f].max
136                         ymin=[ymin,row['lat1'].to_f,row['lat2'].to_f].min
137                         ymax=[ymax,row['lat1'].to_f,row['lat2'].to_f].max
138                 }
139                         
140                 attributes={}
141                 attrlist=ActiveRecord::Base.connection.select_all "SELECT k,v FROM current_way_tags WHERE id=#{wayid}"
142                 attrlist.each {|a| attributes[a['k']]=a['v'] }
143
144 RAILS_DEFAULT_LOGGER.error("Way #{wayid} #{xmin},#{xmax},#{ymin},#{ymax}")
145                 [objname,points,attributes,xmin,xmax,ymin,ymax]
146         end
147         
148         def putway(args)
149                 # to do
150         end
151         
152         def deleteway(args)
153                 # to do
154         end
155         
156         # need support functions here too:
157         #       database support functions (readwayquery, createuniquesegments)
158         #       tag2array, array2tag
159         #       getuserid
160
161         def tag2array(a)
162                 tags={}
163                 a.gsub('\;','#%').split(';').each do |b|
164                         b.gsub!('#%',';')
165                         k,v=b.split('=')
166                         tags[k]=v
167                 end
168                 tags
169         end
170         
171
172         # ====================================================================
173         # AMF read subroutines
174         
175         # ----- getint          return two-byte integer
176         # ----- getlong         return four-byte long
177         # ----- getstring       return string with two-byte length
178         # ----- getdouble       return eight-byte double-precision float
179         # ----- getobject       return object/hash
180         # ----- getarray        return numeric array
181         
182         def getint(s)
183                 s.getc*256+s.getc
184         end
185         
186         def getlong(s)
187                 ((s.getc*256+s.getc)*256+s.getc)*256+s.getc
188         end
189         
190         def getstring(s)
191                 len=s.getc*256+s.getc
192                 s.read(len)
193         end
194         
195         def getdouble(s)
196                 a=s.read(8).unpack('G')                 # G big-endian, E little-endian
197                 a[0]
198         end
199         
200         def getarray(s)
201                 len=getlong(s)
202                 arr=[]
203                 for i in (0..len-1)
204                         arr[i]=getvalue(s)
205                 end
206                 arr
207         end
208         
209         def getobject(s)
210                 arr={}
211                 while (key=getstring(s))
212                         if (key=='') then break end
213                         arr[key]=getvalue(s)
214                 end
215                 s.getc          # skip the 9 'end of object' value
216                 arr
217         end
218         
219         # ----- getvalue        parse and get value
220         
221         def getvalue(s)
222                 case s.getc
223                         when 0; return getdouble(s)                     # number
224                         when 1; return s.getc                           # boolean
225                         when 2; return getstring(s)                     # string
226                         when 3; return getobject(s)                     # object/hash
227                         when 5; return nil                                      # null
228                         when 6; return nil                                      # undefined
229                         when 8; s.read(4)                                       # mixedArray
230                                         return getobject(s)                     #  |
231                         when 10;return getarray(s)                      # array
232                         else;   return nil                                      # error
233                 end
234         end
235
236         # ====================================================================
237         # AMF write subroutines
238         
239         # ----- putdata         envelope data into AMF writeable form
240         # ----- encodevalue     pack variables as AMF
241         
242         def putdata(index,n)
243                 d =encodestring(index+"/onResult")
244                 d+=encodestring("null")
245                 d+=[-1].pack("N")
246                 d+=encodevalue(n)
247         end
248         
249         def encodevalue(n)
250                 case n.class.to_s
251                         when 'Array'
252                                 a=10.chr+encodelong(n.length)
253                                 n.each do |b|
254                                         a+=encodevalue(b)
255                                 end
256                                 a
257                         when 'Hash'
258                                 a=3.chr
259                                 n.each do |k,v|
260                                         a+=encodestring(k)+encodevalue(v)
261                                 end
262                                 a+0.chr+0.chr+9.chr
263                         when 'String'
264                                 2.chr+encodestring(n)
265                         when 'Bignum','Fixnum','Float'
266                                 0.chr+encodedouble(n)
267                         when 'NilClass'
268                                 5.chr
269                         else
270                                 RAILS_DEFAULT_LOGGER.error("Unexpected Ruby type for AMF conversion: "+n.class.to_s)
271                 end
272         end
273         
274         # ----- encodestring    encode string with two-byte length
275         # ----- encodedouble    encode number as eight-byte double precision float
276         # ----- encodelong              encode number as four-byte long
277         
278         def encodestring(n)
279                 a,b=n.size.divmod(256)
280                 a.chr+b.chr+n
281         end
282         
283         def encodedouble(n)
284                 [n].pack('G')
285         end
286         
287         def encodelong(n)
288                 [n].pack('N')
289         end
290         
291         # ====================================================================
292         # Co-ordinate conversion
293         
294         def lat2coord(a)
295                 -(lat2y(a)-$basey)*$masterscale+250
296         end
297         
298         def long2coord(a)
299                 (a-$baselong)*$masterscale+350
300         end
301         
302         def lat2y(a)
303                 180/Math::PI * Math.log(Math.tan(Math::PI/4+a*(Math::PI/180)/2))
304         end
305         
306         def coord2lat(a)
307                 y2lat((a-250)/-$masterscale+$basey)
308         end
309         
310         def coord2long(a)
311                 (a-350)/$masterscale+$baselong
312         end
313         
314         def y2lat(a)
315                 180/Math::PI * (2*Math.atan(Math.exp(a*Math::PI/180))-Math::PI/2)
316         end
317
318
319 end