Commit bacb490aa805c31365275b2390fa3fae238ddc15
1 parent
6b9d32eb
Exists in
api_tasks
and in
4 other branches
Ads GeoRef lib
Showing
2 changed files
with
142 additions
and
2 deletions
Show diff stats
lib/noosfero/geo_ref.rb
| 1 | 1 | module Noosfero::GeoRef |
| 2 | 2 | |
| 3 | - KM_LAT = 111.2 # aproximate distance in km for 1 degree latitude | |
| 4 | - KM_LNG = 85.3 # aproximate distance in km for 1 degree longitude | |
| 3 | + # May replace this module by http://www.postgresql.org/docs/9.3/static/earthdistance.html | |
| 4 | + | |
| 5 | + EARTH_RADIUS = 6378 # aproximate in km | |
| 6 | + | |
| 7 | + class << self | |
| 8 | + | |
| 9 | + def dist(lat1, lng1, lat2, lng2) | |
| 10 | + def deg2rad(d); (d*Math::PI)/180; end | |
| 11 | + def c(n); Math.cos(n); end | |
| 12 | + def s(n); Math.sin(n); end | |
| 13 | + lat1 = deg2rad lat1 | |
| 14 | + lat2 = deg2rad lat2 | |
| 15 | + dlng = deg2rad(lng2) - deg2rad(lng1) | |
| 16 | + EARTH_RADIUS * Math.atan2( | |
| 17 | + Math.sqrt( | |
| 18 | + ( c(lat2) * s(dlng) )**2 + | |
| 19 | + ( c(lat1) * s(lat2) - s(lat1) * c(lat2) * c(dlng) )**2 | |
| 20 | + ), | |
| 21 | + s(lat1) * s(lat2) + c(lat1) * c(lat2) * c(dlng) | |
| 22 | + ) | |
| 23 | + end | |
| 24 | + | |
| 25 | + # Write a SQL expression to return the distance from a profile to a | |
| 26 | + # reference point, in kilometers. | |
| 27 | + # http://www.plumislandmedia.net/mysql/vicenty-great-circle-distance-formula | |
| 28 | + def sql_dist(ref_lat, ref_lng) | |
| 29 | + "2*PI()*#{EARTH_RADIUS}*( | |
| 30 | + DEGREES( | |
| 31 | + ATAN2( | |
| 32 | + SQRT( | |
| 33 | + POW(COS(RADIANS(#{ref_lat}))*SIN(RADIANS(#{ref_lng}-lng)),2) + | |
| 34 | + POW( | |
| 35 | + COS(RADIANS(lat)) * SIN(RADIANS(#{ref_lat})) - ( | |
| 36 | + SIN(RADIANS(lat)) * COS(RADIANS(#{ref_lat})) * COS(RADIANS(#{ref_lng}-lng)) | |
| 37 | + ), 2 | |
| 38 | + ) | |
| 39 | + ), | |
| 40 | + SIN(RADIANS(lat)) * SIN(RADIANS(#{ref_lat})) + | |
| 41 | + COS(RADIANS(lat)) * COS(RADIANS(#{ref_lat})) * COS(RADIANS(#{ref_lng}-lng)) | |
| 42 | + ) | |
| 43 | + )/360 | |
| 44 | + )" | |
| 45 | + end | |
| 46 | + | |
| 47 | + # Asks Google for the georef of a location. | |
| 48 | + def location_to_georef(location) | |
| 49 | + key = location.downcase | |
| 50 | + ll = Rails.cache.read key | |
| 51 | + return ll + [:CACHE] if ll.kind_of? Array | |
| 52 | + resp = RestClient.get 'https://maps.googleapis.com/maps/api/geocode/json?' + | |
| 53 | + 'sensor=false&address=' + url_encode(location) | |
| 54 | + if resp.nil? || resp.code.to_i != 200 | |
| 55 | + if ENV['RAILS_ENV'] == 'test' | |
| 56 | + print " Google Maps API fail (code #{resp ? resp.code : :nil}) " | |
| 57 | + else | |
| 58 | + Rails.logger.warn "Google Maps API request information for " + | |
| 59 | + "\"#{location}\" fail. (code #{resp ? resp.code : :nil})" | |
| 60 | + end | |
| 61 | + return [ 0, 0, "HTTP_FAIL_#{resp.code}".to_sym ] # do not cache failed response | |
| 62 | + else | |
| 63 | + json = JSON.parse resp.body | |
| 64 | + if json && (r=json['results']) && (r=r[0]) && (r=r['geometry']) && | |
| 65 | + (r=r['location']) && r['lat'] | |
| 66 | + ll = [ r['lat'], r['lng'], :SUCCESS ] | |
| 67 | + else | |
| 68 | + status = json['status'] || 'Undefined Error' | |
| 69 | + message = "Google Maps API cant find \"#{location}\" (#{status})" | |
| 70 | + if ENV['RAILS_ENV'] == 'test' | |
| 71 | + print " #{message} " | |
| 72 | + else | |
| 73 | + Rails.logger.warn message | |
| 74 | + end | |
| 75 | + ll = [ 0, 0, status.to_sym ] | |
| 76 | + end | |
| 77 | + Rails.cache.write key, ll | |
| 78 | + end | |
| 79 | + ll | |
| 80 | + end | |
| 81 | + | |
| 82 | + end | |
| 5 | 83 | |
| 6 | 84 | end | ... | ... |
| ... | ... | @@ -0,0 +1,62 @@ |
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +require File.dirname(__FILE__) + '/../test_helper' | |
| 4 | + | |
| 5 | +class GeoRefTest < ActiveSupport::TestCase | |
| 6 | + | |
| 7 | + ll = { | |
| 8 | + salvador: [-12.9, -38.5], | |
| 9 | + rio_de_janeiro: [-22.9, -43.1], | |
| 10 | + new_york: [ 40.7, -74.0], | |
| 11 | + tokyo: [ 35.6, 139.6] | |
| 12 | + } | |
| 13 | + | |
| 14 | + should 'calculate the distance between lat,lng points' do | |
| 15 | + assert_equal 1215, Noosfero::GeoRef.dist(*(ll[:salvador]+ll[:rio_de_janeiro])).round | |
| 16 | + assert_equal 6998, Noosfero::GeoRef.dist(*(ll[:salvador]+ll[:new_york])).round | |
| 17 | + assert_equal 17503, Noosfero::GeoRef.dist(*(ll[:salvador]+ll[:tokyo])).round | |
| 18 | + end | |
| 19 | + | |
| 20 | + should 'calculate the distance between a lat,lng points and a profile' do | |
| 21 | + env = fast_create Environment, name: 'SomeSite' | |
| 22 | + @acme = Enterprise.create! environment: env, identifier: 'acme', name: 'ACME', | |
| 23 | + city: 'Salvador', state: 'Bahia', country: 'BR', lat: -12.9, lng: -38.5 | |
| 24 | + def sql_dist_to(ll) | |
| 25 | + ActiveRecord::Base.connection.execute( | |
| 26 | + "SELECT #{Noosfero::GeoRef.sql_dist ll[0], ll[1]} as dist" + | |
| 27 | + " FROM profiles WHERE id = #{@acme.id};" | |
| 28 | + ).first['dist'].to_f.round | |
| 29 | + end | |
| 30 | + assert_equal 1215, sql_dist_to(ll[:rio_de_janeiro]) | |
| 31 | + assert_equal 6998, sql_dist_to(ll[:new_york]) | |
| 32 | + assert_equal 17503, sql_dist_to(ll[:tokyo]) | |
| 33 | + end | |
| 34 | + | |
| 35 | + def round_ll(ll) | |
| 36 | + ll.map{|n| n.is_a?(Float) ? n.to_i : n } | |
| 37 | + end | |
| 38 | + | |
| 39 | + should 'get lat/lng from address' do | |
| 40 | + Rails.cache.clear | |
| 41 | + ll = Noosfero::GeoRef.location_to_georef 'Salvador, Bahia, BR' | |
| 42 | + assert_equal [-12, -38, :SUCCESS], round_ll(ll) | |
| 43 | + end | |
| 44 | + | |
| 45 | + should 'get and cache lat/lng from address' do | |
| 46 | + Rails.cache.clear | |
| 47 | + ll = Noosfero::GeoRef.location_to_georef 'Curitiba, Paraná, BR' | |
| 48 | + assert_equal [-25, -49, :SUCCESS], round_ll(ll) | |
| 49 | + ll = Noosfero::GeoRef.location_to_georef 'Curitiba, Paraná, BR' | |
| 50 | + assert_equal [-25, -49, :SUCCESS, :CACHE], round_ll(ll) | |
| 51 | + end | |
| 52 | + | |
| 53 | + should 'notify a non existent address' do | |
| 54 | + Rails.cache.clear | |
| 55 | + orig_env = ENV['RAILS_ENV'] | |
| 56 | + ENV['RAILS_ENV'] = 'X' # cancel throw for test mode on process_rest_req. | |
| 57 | + ll = Noosfero::GeoRef.location_to_georef 'Nowhere, Nocountry, XYZ' | |
| 58 | + ENV['RAILS_ENV'] = orig_env # restore value to do not mess with other tests. | |
| 59 | + assert_equal [0, 0, :ZERO_RESULTS], round_ll(ll) | |
| 60 | + end | |
| 61 | + | |
| 62 | +end | ... | ... |