Fix bogus SQL.
[rails.git] / app / controllers / api_controller.rb
1 class ApiController < ApplicationController
2
3   before_filter :authorize
4   after_filter :compress_output
5
6   helper :user
7   model :user
8
9   #COUNT is the number of map requests to allow before exiting and starting a new process
10   @@count = COUNT
11
12   # The maximum area you're allowed to request, in square degrees
13   MAX_REQUEST_AREA = 0.25
14
15
16   # Number of GPS trace/trackpoints returned per-page
17   TRACEPOINTS_PER_PAGE = 5000
18   
19   def trackpoints
20     @@count+=1
21     response.headers["Content-Type"] = 'text/xml'
22     #retrieve the page number
23     page = params['page'].to_i
24     unless page
25         page = 0;
26     end
27
28     unless page >= 0
29         report_error("Page number must be greater than or equal to 0")
30         return
31     end
32
33     offset = page * TRACEPOINTS_PER_PAGE
34
35     # Figure out the bbox
36     bbox = params['bbox']
37     unless bbox and bbox.count(',') == 3
38       report_error("The parameter bbox is required, and must be of the form min_lon,min_lat,max_lon,max_lat")
39       return
40     end
41
42     bbox = bbox.split(',')
43
44     min_lon = bbox[0].to_f
45     min_lat = bbox[1].to_f
46     max_lon = bbox[2].to_f
47     max_lat = bbox[3].to_f
48
49     # check the bbox is sane
50     unless min_lon <= max_lon
51       report_error("The minimum longitude must be less than the maximum longitude, but it wasn't")
52       return
53     end
54     unless min_lat <= max_lat
55       report_error("The minimum latitude must be less than the maximum latitude, but it wasn't")
56       return
57     end
58     unless min_lon >= -180 && min_lat >= -90 && max_lon <= 180 && max_lat <= 90
59       report_error("The latitudes must be between -90 and 90, and longitudes between -180 and 180")
60       return
61     end
62
63     # check the bbox isn't too large
64     requested_area = (max_lat-min_lat)*(max_lon-min_lon)
65     if requested_area > MAX_REQUEST_AREA
66       report_error("The maximum bbox size is " + MAX_REQUEST_AREA.to_s + ", and your request was too large. Either request a smaller area, or use planet.osm")
67       return
68     end
69
70     # integerise
71     min_lat = min_lat * 1000000
72     max_lat = max_lat * 1000000
73     min_lon = min_lon * 1000000
74     max_lon = max_lon * 1000000
75     # get all the points
76     points = Tracepoint.find(:all, :conditions => ['latitude BETWEEN ? AND ? AND longitude BETWEEN ? AND ?', min_lat.to_i, max_lat.to_i, min_lon.to_i, max_lon.to_i], :select => "DISTINCT *", :offset => offset, :limit => TRACEPOINTS_PER_PAGE, :order => "timestamp DESC" )
77
78     doc = XML::Document.new
79     doc.encoding = 'UTF-8'
80     root = XML::Node.new 'gpx'
81     root['version'] = '1.0'
82     root['creator'] = 'OpenStreetMap.org'
83     root['xmlns'] = "http://www.topografix.com/GPX/1/0/"
84     
85     doc.root = root
86
87     track = XML::Node.new 'trk'
88     doc.root << track
89
90     trkseg = XML::Node.new 'trkseg'
91     track << trkseg
92
93     points.each do |point|
94       trkseg << point.to_xml_node()
95     end
96
97     #exit when we have too many requests
98     if @@count > MAX_COUNT
99       render :text => doc.to_s
100       @@count = COUNT
101       exit!
102     end
103
104     render :text => doc.to_s
105
106   end
107
108   def map
109     GC.start
110     @@count+=1
111
112     response.headers["Content-Type"] = 'text/xml'
113     # Figure out the bbox
114     bbox = params['bbox']
115     unless bbox and bbox.count(',') == 3
116       report_error("The parameter bbox is required, and must be of the form min_lon,min_lat,max_lon,max_lat")
117       return
118     end
119
120     bbox = bbox.split(',')
121
122     min_lon = bbox[0].to_f
123     min_lat = bbox[1].to_f
124     max_lon = bbox[2].to_f
125     max_lat = bbox[3].to_f
126
127     # check the bbox is sane
128     unless min_lon <= max_lon
129       report_error("The minimum longitude must be less than the maximum longitude, but it wasn't")
130       return
131     end
132     unless min_lat <= max_lat
133       report_error("The minimum latitude must be less than the maximum latitude, but it wasn't")
134       return
135     end
136     unless min_lon >= -180 && min_lat >= -90 && max_lon <= 180 && max_lat <= 90
137       report_error("The latitudes must be between -90 and 90, and longitudes between -180 and 180")
138       return
139     end
140
141     # check the bbox isn't too large
142     requested_area = (max_lat-min_lat)*(max_lon-min_lon)
143     if requested_area > MAX_REQUEST_AREA
144       report_error("The maximum bbox size is " + MAX_REQUEST_AREA.to_s + ", and your request was too large. Either request a smaller area, or use planet.osm")
145       return
146     end
147
148     # get all the nodes
149     nodes = Node.find(:all, :conditions => ['latitude > ? AND longitude > ? AND latitude < ? AND longitude < ? AND visible = 1', min_lat, min_lon, max_lat, max_lon])
150
151     node_ids = nodes.collect {|node| node.id }
152
153     if node_ids.length > 50_000
154       report_error("You requested too many nodes (limit is 50,000). Either request a smaller area, or use planet.osm")
155     end
156
157     if node_ids.length == 0
158       render :text => "<osm version='0.4'></osm>"
159       return
160     end
161
162     # grab the segments
163     segments = Array.new
164     if node_ids.length > 0
165       node_ids_sql = "(#{node_ids.join(',')})"
166       # get the referenced segments
167       segments = Segment.find_by_sql "select * from current_segments where visible = 1 and (node_a in #{node_ids_sql} or node_b in #{node_ids_sql})"
168     end
169     # see if we have any missing nodes
170     segments_nodes = segments.collect {|segment| segment.node_a }
171     segments_nodes += segments.collect {|segment| segment.node_b }
172
173     segments_nodes.uniq!
174
175     missing_nodes = segments_nodes - node_ids
176
177     # get missing nodes if there are any
178     nodes += Node.find(missing_nodes) if missing_nodes.length > 0
179
180     doc = OSM::API.new.get_xml_doc
181
182     # get ways
183     # find which ways are needed
184     segment_ids = segments.collect {|segment| segment.id }
185     ways = Array.new
186     if segment_ids.length > 0
187       way_segments = WaySegment.find_all_by_segment_id(segment_ids)
188       way_ids = way_segments.collect {|way_segment| way_segment.id }
189       ways = Way.find(way_ids) # NB: doesn't pick up segments, tags from db until accessed via way.way_segments etc.
190
191       # seg_ids = way_segments.collect {|way_segment| way_segment.segment_id }
192
193       list_of_way_segs = ways.collect {|way| way.way_segments}
194       list_of_way_segs.flatten!
195
196       list_of_way_segments = list_of_way_segs.collect { |way_seg| way_seg.segment_id }
197
198         else
199           list_of_way_segments = Array.new
200     end
201
202     # - [0] in case some thing links to segment 0 which doesn't exist. Shouldn't actually ever happen but it does. FIXME: file a ticket for this
203     segments_to_fetch = (list_of_way_segments.uniq - segment_ids) - [0]
204
205     if segments_to_fetch.length > 0
206       segments += Segment.find(segments_to_fetch)
207     end
208
209     # get more nodes
210     #
211
212     segments_nodes = segments.collect {|segment| segment.node_a }
213     segments_nodes += segments.collect {|segment| segment.node_b }
214
215     node_ids_a = nodes.collect {|node| node.id }
216
217     nodes_to_get = segments_nodes - node_ids_a
218     nodes += Node.find(nodes_to_get) if nodes_to_get.length > 0
219
220     visible_nodes = {}
221     user_display_name_cache = {}
222
223     nodes.each do |node|
224       if node.visible?
225         doc.root << node.to_xml_node(user_display_name_cache)
226         visible_nodes[node.id] = node
227       end
228     end
229
230     visible_segments = {}
231
232     segments.each do |segment|
233       if visible_nodes[segment.node_a] and visible_nodes[segment.node_b] and segment.visible?
234         doc.root << segment.to_xml_node(user_display_name_cache) 
235         visible_segments[segment.id] = segment
236       end
237     end
238
239     ways.each do |way|
240       doc.root << way.to_xml_node(visible_segments, user_display_name_cache) if way.visible?
241     end 
242
243     render :text => doc.to_s
244     
245     #exit when we have too many requests
246     if @@count > MAX_COUNT
247       @@count = COUNT
248       
249       exit!
250     end
251
252   end
253 end