1 | module OSM |
---|
2 | |
---|
3 | # This piece of magic reads a GPX with SAX and spits out |
---|
4 | # lat/lng and stuff |
---|
5 | # |
---|
6 | # This would print every latitude value: |
---|
7 | # |
---|
8 | # gpx = OSM::GPXImporter.new('somefile.gpx') |
---|
9 | # gpx.points {|p| puts p['latitude']} |
---|
10 | |
---|
11 | require 'time' |
---|
12 | require 'rexml/parsers/sax2parser' |
---|
13 | require 'rexml/text' |
---|
14 | require 'xml/libxml' |
---|
15 | require 'digest/md5' |
---|
16 | require 'RMagick' |
---|
17 | |
---|
18 | class Mercator |
---|
19 | include Math |
---|
20 | |
---|
21 | def initialize(lat, lon, degrees_per_pixel, width, height) |
---|
22 | #init me with your centre lat/lon, the number of degrees per pixel and the size of your image |
---|
23 | @clat = lat |
---|
24 | @clon = lon |
---|
25 | @degrees_per_pixel = degrees_per_pixel |
---|
26 | @degrees_per_pixel = 0.0000000001 if @degrees_per_pixel < 0.0000000001 |
---|
27 | @width = width |
---|
28 | @height = height |
---|
29 | @dlon = width / 2 * @degrees_per_pixel |
---|
30 | @dlat = height / 2 * @degrees_per_pixel * cos(@clat * PI / 180) |
---|
31 | |
---|
32 | @tx = xsheet(@clon - @dlon) |
---|
33 | @ty = ysheet(@clat - @dlat) |
---|
34 | |
---|
35 | @bx = xsheet(@clon + @dlon) |
---|
36 | @by = ysheet(@clat + @dlat) |
---|
37 | |
---|
38 | end |
---|
39 | |
---|
40 | #the following two functions will give you the x/y on the entire sheet |
---|
41 | |
---|
42 | def kilometerinpixels |
---|
43 | return 40008.0 / 360.0 * @degrees_per_pixel |
---|
44 | end |
---|
45 | |
---|
46 | def ysheet(lat) |
---|
47 | log(tan(PI / 4 + (lat * PI / 180 / 2))) |
---|
48 | end |
---|
49 | |
---|
50 | def xsheet(lon) |
---|
51 | lon |
---|
52 | end |
---|
53 | |
---|
54 | #and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME |
---|
55 | |
---|
56 | def y(lat) |
---|
57 | return @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height) |
---|
58 | end |
---|
59 | |
---|
60 | def x(lon) |
---|
61 | return ((xsheet(lon) - @tx) / (@bx - @tx) * @width) |
---|
62 | end |
---|
63 | end |
---|
64 | |
---|
65 | |
---|
66 | class GPXImporter |
---|
67 | # FIXME swap REXML for libXML |
---|
68 | attr_reader :possible_points |
---|
69 | attr_reader :actual_points |
---|
70 | attr_reader :tracksegs |
---|
71 | |
---|
72 | def initialize(filename) |
---|
73 | @filename = filename |
---|
74 | end |
---|
75 | |
---|
76 | def points |
---|
77 | @possible_points = 0 |
---|
78 | @actual_points = 0 |
---|
79 | @tracksegs = 0 |
---|
80 | |
---|
81 | lat = -1 |
---|
82 | lon = -1 |
---|
83 | ele = -1 |
---|
84 | date = DateTime.now(); |
---|
85 | gotlatlon = false |
---|
86 | gotele = false |
---|
87 | gotdate = false |
---|
88 | |
---|
89 | parser = REXML::Parsers::SAX2Parser.new(File.new(@filename)) |
---|
90 | |
---|
91 | parser.listen( :start_element, %w{ trkpt }) do |uri,localname,qname,attributes| |
---|
92 | lat = attributes['lat'].to_f |
---|
93 | lon = attributes['lon'].to_f |
---|
94 | gotlatlon = true |
---|
95 | @possible_points += 1 |
---|
96 | end |
---|
97 | |
---|
98 | parser.listen( :characters, %w{ ele } ) do |text| |
---|
99 | ele = text |
---|
100 | gotele = true |
---|
101 | end |
---|
102 | |
---|
103 | parser.listen( :characters, %w{ time } ) do |text| |
---|
104 | if text && text != '' |
---|
105 | begin |
---|
106 | date = DateTime.parse(text) |
---|
107 | gotdate = true |
---|
108 | rescue |
---|
109 | end |
---|
110 | end |
---|
111 | end |
---|
112 | |
---|
113 | parser.listen( :end_element, %w{ trkseg } ) do |uri, localname, qname| |
---|
114 | @tracksegs += 1 |
---|
115 | end |
---|
116 | |
---|
117 | parser.listen( :end_element, %w{ trkpt } ) do |uri,localname,qname| |
---|
118 | if gotlatlon && gotdate |
---|
119 | ele = '0' unless gotele |
---|
120 | if lat < 90 && lat > -90 && lon > -180 && lon < 180 |
---|
121 | @actual_points += 1 |
---|
122 | yield Hash['latitude' => lat, 'longitude' => lon, 'timestamp' => date, 'altitude' => ele, 'segment' => @tracksegs] |
---|
123 | end |
---|
124 | end |
---|
125 | gotlatlon = false |
---|
126 | gotele = false |
---|
127 | gotdate = false |
---|
128 | end |
---|
129 | |
---|
130 | parser.parse |
---|
131 | end |
---|
132 | |
---|
133 | def get_picture(min_lat, min_lon, max_lat, max_lon, num_points) |
---|
134 | #puts "getting picfor bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}" |
---|
135 | frames = 10 |
---|
136 | width = 250 |
---|
137 | height = 250 |
---|
138 | rat= Math.cos( ((max_lat + min_lat)/2.0) / 180.0 * 3.141592) |
---|
139 | proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height) |
---|
140 | |
---|
141 | images = [] |
---|
142 | |
---|
143 | frames.times do |
---|
144 | gc = Magick::Draw.new |
---|
145 | gc.stroke_linejoin('miter') |
---|
146 | gc.stroke('#FFFFFF') |
---|
147 | gc.fill('#FFFFFF') |
---|
148 | gc.rectangle(0,0,width,height) |
---|
149 | gc.stroke_width(1) |
---|
150 | images << gc |
---|
151 | end |
---|
152 | |
---|
153 | oldpx = 0.0 |
---|
154 | oldpy = 0.0 |
---|
155 | |
---|
156 | first = true |
---|
157 | |
---|
158 | m = 0 |
---|
159 | mm = 0 |
---|
160 | points do |p| |
---|
161 | px = proj.x(p['longitude']) |
---|
162 | py = proj.y(p['latitude']) |
---|
163 | frames.times do |n| |
---|
164 | images[n].stroke_width(1) |
---|
165 | images[n].stroke('#BBBBBB') |
---|
166 | images[n].fill('#BBBBBB') |
---|
167 | # puts "A #{px},#{py} - #{oldpx},#{oldpy}" |
---|
168 | images[n].line(px, py, oldpx, oldpy ) unless first |
---|
169 | end |
---|
170 | images[mm].stroke_width(3) |
---|
171 | images[mm].stroke('#000000') |
---|
172 | images[mm].fill('#000000') |
---|
173 | images[mm].line(px, py, oldpx, oldpy ) unless first |
---|
174 | # puts "B #{px},#{py} - #{oldpx},#{oldpy}" |
---|
175 | m +=1 |
---|
176 | if m > num_points.to_f / frames.to_f * (mm+1) |
---|
177 | mm += 1 |
---|
178 | end |
---|
179 | first = false |
---|
180 | oldpy = py |
---|
181 | oldpx = px |
---|
182 | end |
---|
183 | |
---|
184 | il = Magick::ImageList.new |
---|
185 | |
---|
186 | frames.times do |n| |
---|
187 | canvas = Magick::Image.new(width, height) { |
---|
188 | self.background_color = 'white' |
---|
189 | } |
---|
190 | begin |
---|
191 | images[n].draw(canvas) |
---|
192 | rescue ArgumentError |
---|
193 | end |
---|
194 | canvas.format = 'GIF' |
---|
195 | il << canvas |
---|
196 | end |
---|
197 | |
---|
198 | il.delay = 50 |
---|
199 | il.format = 'GIF' |
---|
200 | return il.to_blob |
---|
201 | end |
---|
202 | |
---|
203 | def get_icon(min_lat, min_lon, max_lat, max_lon) |
---|
204 | #puts "getting icon for bbox #{min_lat},#{min_lon} - #{max_lat},#{max_lon}" |
---|
205 | width = 50 |
---|
206 | height = 50 |
---|
207 | rat= Math.cos( ((max_lat + min_lat)/2.0) / 180.0 * 3.141592) |
---|
208 | proj = OSM::Mercator.new((min_lat + max_lat) / 2, (max_lon + min_lon) / 2, (max_lat - min_lat) / width / rat, width, height) |
---|
209 | |
---|
210 | images = [] |
---|
211 | |
---|
212 | gc = Magick::Draw.new |
---|
213 | gc.stroke_linejoin('miter') |
---|
214 | |
---|
215 | oldpx = 0.0 |
---|
216 | oldpy = 0.0 |
---|
217 | |
---|
218 | first = true |
---|
219 | |
---|
220 | gc.stroke_width(1) |
---|
221 | gc.stroke('#000000') |
---|
222 | gc.fill('#000000') |
---|
223 | |
---|
224 | points do |p| |
---|
225 | px = proj.x(p['longitude']) |
---|
226 | py = proj.y(p['latitude']) |
---|
227 | gc.line(px, py, oldpx, oldpy ) unless first |
---|
228 | # puts "C #{px},#{py} - #{oldpx},#{oldpy}" |
---|
229 | first = false |
---|
230 | oldpy = py |
---|
231 | oldpx = px |
---|
232 | end |
---|
233 | |
---|
234 | canvas = Magick::Image.new(width, height) { |
---|
235 | self.background_color = 'white' |
---|
236 | } |
---|
237 | begin |
---|
238 | gc.draw(canvas) |
---|
239 | rescue ArgumentError |
---|
240 | end |
---|
241 | canvas.format = 'GIF' |
---|
242 | return canvas.to_blob |
---|
243 | end |
---|
244 | |
---|
245 | end |
---|
246 | |
---|
247 | class GreatCircle |
---|
248 | include Math |
---|
249 | |
---|
250 | # initialise with a base position |
---|
251 | def initialize(lat, lon) |
---|
252 | @lat = lat * PI / 180 |
---|
253 | @lon = lon * PI / 180 |
---|
254 | end |
---|
255 | |
---|
256 | # get the distance from the base position to a given position |
---|
257 | def distance(lat, lon) |
---|
258 | lat = lat * PI / 180 |
---|
259 | lon = lon * PI / 180 |
---|
260 | return 6372.795 * 2 * asin(sqrt(sin((lat - @lat) / 2) ** 2 + cos(@lat) * cos(lat) * sin((lon - @lon)/2) ** 2)) |
---|
261 | end |
---|
262 | |
---|
263 | # get the worst case bounds for a given radius from the base position |
---|
264 | def bounds(radius) |
---|
265 | latradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2)) |
---|
266 | lonradius = 2 * asin(sqrt(sin(radius / 6372.795 / 2) ** 2 / cos(@lat) ** 2)) |
---|
267 | minlat = (@lat - latradius) * 180 / PI |
---|
268 | maxlat = (@lat + latradius) * 180 / PI |
---|
269 | minlon = (@lon - lonradius) * 180 / PI |
---|
270 | maxlon = (@lon + lonradius) * 180 / PI |
---|
271 | return { :minlat => minlat, :maxlat => maxlat, :minlon => minlon, :maxlon => maxlon } |
---|
272 | end |
---|
273 | end |
---|
274 | |
---|
275 | class GeoRSS |
---|
276 | def initialize(feed_title='OpenStreetMap GPS Traces', feed_description='OpenStreetMap GPS Traces', feed_url='http://www.openstreetmap.org/traces/') |
---|
277 | @doc = XML::Document.new |
---|
278 | @doc.encoding = 'UTF-8' |
---|
279 | |
---|
280 | rss = XML::Node.new 'rss' |
---|
281 | @doc.root = rss |
---|
282 | rss['version'] = "2.0" |
---|
283 | rss['xmlns:geo'] = "http://www.w3.org/2003/01/geo/wgs84_pos#" |
---|
284 | @channel = XML::Node.new 'channel' |
---|
285 | rss << @channel |
---|
286 | title = XML::Node.new 'title' |
---|
287 | title << feed_title |
---|
288 | @channel << title |
---|
289 | description_el = XML::Node.new 'description' |
---|
290 | @channel << description_el |
---|
291 | |
---|
292 | description_el << feed_description |
---|
293 | link = XML::Node.new 'link' |
---|
294 | link << feed_url |
---|
295 | @channel << link |
---|
296 | image = XML::Node.new 'image' |
---|
297 | @channel << image |
---|
298 | url = XML::Node.new 'url' |
---|
299 | url << 'http://www.openstreetmap.org/feeds/mag_map-rss2.0.png' |
---|
300 | image << url |
---|
301 | title = XML::Node.new 'title' |
---|
302 | title << "OpenStreetMap" |
---|
303 | image << title |
---|
304 | width = XML::Node.new 'width' |
---|
305 | width << '100' |
---|
306 | image << width |
---|
307 | height = XML::Node.new 'height' |
---|
308 | height << '100' |
---|
309 | image << height |
---|
310 | link = XML::Node.new 'link' |
---|
311 | link << feed_url |
---|
312 | image << link |
---|
313 | end |
---|
314 | |
---|
315 | def add(latitude=0, longitude=0, title_text='dummy title', author_text='anonymous', url='http://www.example.com/', description_text='dummy description', timestamp=DateTime.now) |
---|
316 | item = XML::Node.new 'item' |
---|
317 | |
---|
318 | title = XML::Node.new 'title' |
---|
319 | item << title |
---|
320 | title << title_text |
---|
321 | link = XML::Node.new 'link' |
---|
322 | link << url |
---|
323 | item << link |
---|
324 | |
---|
325 | guid = XML::Node.new 'guid' |
---|
326 | guid << url |
---|
327 | item << guid |
---|
328 | |
---|
329 | description = XML::Node.new 'description' |
---|
330 | description << description_text |
---|
331 | item << description |
---|
332 | |
---|
333 | author = XML::Node.new 'author' |
---|
334 | author << author_text |
---|
335 | item << author |
---|
336 | |
---|
337 | pubDate = XML::Node.new 'pubDate' |
---|
338 | pubDate << timestamp.to_s(:rfc822) |
---|
339 | item << pubDate |
---|
340 | |
---|
341 | if latitude |
---|
342 | lat_el = XML::Node.new 'geo:lat' |
---|
343 | lat_el << latitude.to_s |
---|
344 | item << lat_el |
---|
345 | end |
---|
346 | |
---|
347 | if longitude |
---|
348 | lon_el = XML::Node.new 'geo:long' |
---|
349 | lon_el << longitude.to_s |
---|
350 | item << lon_el |
---|
351 | end |
---|
352 | |
---|
353 | @channel << item |
---|
354 | end |
---|
355 | |
---|
356 | def to_s |
---|
357 | return @doc.to_s |
---|
358 | end |
---|
359 | end |
---|
360 | |
---|
361 | class API |
---|
362 | def get_xml_doc |
---|
363 | doc = XML::Document.new |
---|
364 | doc.encoding = 'UTF-8' |
---|
365 | root = XML::Node.new 'osm' |
---|
366 | root['version'] = API_VERSION |
---|
367 | root['generator'] = 'OpenStreetMap server' |
---|
368 | doc.root = root |
---|
369 | return doc |
---|
370 | end |
---|
371 | end |
---|
372 | |
---|
373 | def self.IPLocation(ip_address) |
---|
374 | Timeout::timeout(4) do |
---|
375 | Net::HTTP.start('api.hostip.info') do |http| |
---|
376 | country = http.get("/country.php?ip=#{ip_address}").body |
---|
377 | country = "GB" if country = "UK" |
---|
378 | Net::HTTP.start('ws.geonames.org') do |http| |
---|
379 | xml = REXML::Document.new(http.get("/countryInfo?country=#{country}").body) |
---|
380 | xml.elements.each("geonames/country") do |ele| |
---|
381 | minlon = ele.get_text("bBoxWest").to_s |
---|
382 | minlat = ele.get_text("bBoxSouth").to_s |
---|
383 | maxlon = ele.get_text("bBoxEast").to_s |
---|
384 | maxlat = ele.get_text("bBoxNorth").to_s |
---|
385 | return { :minlon => minlon, :minlat => minlat, :maxlon => maxlon, :maxlat => maxlat } |
---|
386 | end |
---|
387 | end |
---|
388 | end |
---|
389 | end |
---|
390 | |
---|
391 | return nil |
---|
392 | rescue Exception |
---|
393 | return nil |
---|
394 | end |
---|
395 | |
---|
396 | # Construct a random token of a given length |
---|
397 | def self.make_token(length = 30) |
---|
398 | chars = 'abcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' |
---|
399 | token = '' |
---|
400 | |
---|
401 | length.times do |
---|
402 | token += chars[(rand * chars.length).to_i].chr |
---|
403 | end |
---|
404 | |
---|
405 | return token |
---|
406 | end |
---|
407 | |
---|
408 | # Return an encrypted version of a password |
---|
409 | def self.encrypt_password(password, salt) |
---|
410 | return Digest::MD5.hexdigest(password) if salt.nil? |
---|
411 | return Digest::MD5.hexdigest(salt + password) |
---|
412 | end |
---|
413 | end |
---|