[Git][noosfero/noosfero][api] 8 commits: Ads GeoRef lib
Rodrigo Souto
gitlab at gitlab.com
Tue Jun 23 10:43:32 BRT 2015
Rodrigo Souto pushed to branch api at Noosfero / noosfero
Commits:
bacb490a by Aurélio A. Heckert at 2015-06-19T14:17:41Z
Ads GeoRef lib
- - - - -
6c54d1c3 by Aurélio A. Heckert at 2015-06-19T14:17:42Z
Filter profile by location
- - - - -
f0260209 by Aurélio A. Heckert at 2015-06-19T14:19:25Z
API Filter enterprises by location
- - - - -
6b50511e by Aurélio A. Heckert at 2015-06-19T14:19:25Z
API response with error block
- - - - -
8bc5fc88 by Aurélio A. Heckert at 2015-06-19T14:20:29Z
API cant filter People by location
A note about privacy, prevent devs to commit a mistake.
- - - - -
2077d2b6 by Aurélio A. Heckert at 2015-06-19T14:23:27Z
Cosmetic changes on API code
- - - - -
662484b4 by Aurélio A. Heckert at 2015-06-19T14:23:27Z
add a description to People API
- - - - -
9efdcbd7 by Rodrigo Souto at 2015-06-23T13:43:25Z
Merge branch 'API-grape' into 'api'
Georef e cosmética
See merge request !608
- - - - -
7 changed files:
- app/models/profile.rb
- lib/noosfero/api/entity.rb
- lib/noosfero/api/helpers.rb
- lib/noosfero/api/v1/enterprises.rb
- lib/noosfero/api/v1/people.rb
- lib/noosfero/geo_ref.rb
- + test/unit/geo_ref_test.rb
Changes:
=====================================
app/models/profile.rb
=====================================
--- a/app/models/profile.rb
+++ b/app/models/profile.rb
@@ -100,6 +100,48 @@ class Profile < ActiveRecord::Base
}
scope :no_templates, {:conditions => {:is_template => false}}
+ # Returns a scoped object to select profiles in a given location or in a radius
+ # distance from the given location center.
+ # The parameter can be the `request.params` with the keys:
+ # * `country`: Country code string.
+ # * `state`: Second-level administrative country subdivisions.
+ # * `city`: City full name for center definition, or as set by users.
+ # * `lat`: The latitude to define the center of georef search.
+ # * `lng`: The longitude to define the center of georef search.
+ # * `distance`: Define the search radius in kilometers.
+ # NOTE: This method may return an exception object, to inform filter error.
+ # When chaining scopes, is hardly recommended you to add this as the last one,
+ # if you can't be sure about the provided parameters.
+ def self.by_location(params)
+ params = params.with_indifferent_access
+ if params[:distance].blank?
+ where_code = []
+ [ :city, :state, :country ].each do |place|
+ unless params[place].blank?
+ # ... So we must to find on this named location
+ # TODO: convert location attrs to a table collumn
+ where_code << "(profiles.data like '%#{place}: #{params[place]}%')"
+ end
+ end
+ self.where where_code.join(' AND ')
+ else # Filter in a georef circle
+ unless params[:lat].blank? && params[:lng].blank?
+ lat, lng = [ params[:lat].to_f, params[:lng].to_f ]
+ end
+ if !lat
+ location = [ params[:city], params[:state], params[:country] ].compact.join(', ')
+ if location.blank?
+ return Exception.new (
+ _('You must to provide `lat` and `lng`, or `city` and `country` to define the center of the search circle, defined by `distance`.')
+ )
+ end
+ lat, lng = Noosfero::GeoRef.location_to_georef location
+ end
+ dist = params[:distance].to_f
+ self.where "#{Noosfero::GeoRef.sql_dist lat, lng} <= #{dist}"
+ end
+ end
+
include TimeScopes
def members
=====================================
lib/noosfero/api/entity.rb
=====================================
--- a/lib/noosfero/api/entity.rb
+++ b/lib/noosfero/api/entity.rb
@@ -1,5 +1,26 @@
class Noosfero::API::Entity < Grape::Entity
+ def initialize(object, options = {})
+ object = nil if object.is_a? Exception
+ super object, options
+ end
+
+ def self.represent(objects, options = {})
+ if options[:is_inner_data]
+ super objects, options
+ else
+ data = super objects, options.merge(is_inner_data: true)
+ if objects.is_a? Exception
+ data.merge ok: false, error: {
+ type: objects.class.name,
+ message: objects.message
+ }
+ else
+ data.merge ok: true, error: { type: 'Success', message: '' }
+ end
+ end
+ end
+
def self.fields_condition(fields)
lambda do |object, options|
return true if options[:fields].blank?
=====================================
lib/noosfero/api/helpers.rb
=====================================
--- a/lib/noosfero/api/helpers.rb
+++ b/lib/noosfero/api/helpers.rb
@@ -2,7 +2,7 @@ module Noosfero
module API
module APIHelpers
PRIVATE_TOKEN_PARAM = :private_token
- ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type]
+ DEFAULT_ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type]
def current_user
private_token = (params[PRIVATE_TOKEN_PARAM] || headers['Private-Token']).to_s
@@ -228,7 +228,7 @@ module Noosfero
def parser_params(params)
parsed_params = {}
params.map do |k,v|
- parsed_params[k.to_sym] = v if ALLOWED_PARAMETERS.include?(k.to_sym)
+ parsed_params[k.to_sym] = v if DEFAULT_ALLOWED_PARAMETERS.include?(k.to_sym)
end
parsed_params
end
=====================================
lib/noosfero/api/v1/enterprises.rb
=====================================
--- a/lib/noosfero/api/v1/enterprises.rb
+++ b/lib/noosfero/api/v1/enterprises.rb
@@ -5,13 +5,14 @@ module Noosfero
before { authenticate! }
resource :enterprises do
-
- # Collect comments from articles
+
+ # Collect enterprises from environment
#
# Parameters:
# from - date where the search will begin. If nothing is passed the default date will be the date of the first article created
# oldest - Collect the oldest comments from reference_id comment. If nothing is passed the newest comments are collected
# limit - amount of comments returned. The default value is 20
+ # georef params - read `Profile.by_location` for more information.
#
# Example Request:
# GET /enterprises?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10
@@ -19,6 +20,7 @@ module Noosfero
get do
enterprises = select_filtered_collection_of(environment, 'enterprises', params)
enterprises = enterprises.visible_for_person(current_person)
+ enterprises = enterprises.by_location(params) # Must be the last. May return Exception obj.
present enterprises, :with => Entities::Enterprise
end
@@ -39,7 +41,7 @@ module Noosfero
get do
person = environment.people.find(params[:person_id])
enterprises = select_filtered_collection_of(person, 'enterprises', params)
- enterprises = enterprises.visible
+ enterprises = enterprises.visible.by_location(params)
present enterprises, :with => Entities::Enterprise
end
=====================================
lib/noosfero/api/v1/people.rb
=====================================
--- a/lib/noosfero/api/v1/people.rb
+++ b/lib/noosfero/api/v1/people.rb
@@ -4,9 +4,21 @@ module Noosfero
class People < Grape::API
before { authenticate! }
+ desc 'API Root'
+
resource :people do
- # Collect comments from articles
+ # -- A note about privacy --
+ # We wold find people by location, but we must test if the related
+ # fields are public. We can't do it now, with SQL, while the location
+ # data and the fields_privacy are a serialized settings.
+ # We must build a new table for profile data, where we can set meta-data
+ # like:
+ # | id | profile_id | key | value | privacy_level | source |
+ # | 1 | 99 | city | Salvador | friends | user |
+ # | 2 | 99 | lng | -38.521 | me only | automatic |
+
+ # Collect people from environment
#
# Parameters:
# from - date where the search will begin. If nothing is passed the default date will be the date of the first article created
@@ -16,6 +28,8 @@ module Noosfero
# Example Request:
# GET /people?from=2013-04-04-14:41:43&until=2014-04-04-14:41:43&limit=10
# GET /people?reference_id=10&limit=10&oldest
+
+ desc "Find environment's people"
get do
people = select_filtered_collection_of(environment, 'people', params)
people = people.visible_for_person(current_person)
=====================================
lib/noosfero/geo_ref.rb
=====================================
--- a/lib/noosfero/geo_ref.rb
+++ b/lib/noosfero/geo_ref.rb
@@ -1,6 +1,84 @@
module Noosfero::GeoRef
- KM_LAT = 111.2 # aproximate distance in km for 1 degree latitude
- KM_LNG = 85.3 # aproximate distance in km for 1 degree longitude
+ # May replace this module by http://www.postgresql.org/docs/9.3/static/earthdistance.html
+
+ EARTH_RADIUS = 6378 # aproximate in km
+
+ class << self
+
+ def dist(lat1, lng1, lat2, lng2)
+ def deg2rad(d); (d*Math::PI)/180; end
+ def c(n); Math.cos(n); end
+ def s(n); Math.sin(n); end
+ lat1 = deg2rad lat1
+ lat2 = deg2rad lat2
+ dlng = deg2rad(lng2) - deg2rad(lng1)
+ EARTH_RADIUS * Math.atan2(
+ Math.sqrt(
+ ( c(lat2) * s(dlng) )**2 +
+ ( c(lat1) * s(lat2) - s(lat1) * c(lat2) * c(dlng) )**2
+ ),
+ s(lat1) * s(lat2) + c(lat1) * c(lat2) * c(dlng)
+ )
+ end
+
+ # Write a SQL expression to return the distance from a profile to a
+ # reference point, in kilometers.
+ # http://www.plumislandmedia.net/mysql/vicenty-great-circle-distance-formula
+ def sql_dist(ref_lat, ref_lng)
+ "2*PI()*#{EARTH_RADIUS}*(
+ DEGREES(
+ ATAN2(
+ SQRT(
+ POW(COS(RADIANS(#{ref_lat}))*SIN(RADIANS(#{ref_lng}-lng)),2) +
+ POW(
+ COS(RADIANS(lat)) * SIN(RADIANS(#{ref_lat})) - (
+ SIN(RADIANS(lat)) * COS(RADIANS(#{ref_lat})) * COS(RADIANS(#{ref_lng}-lng))
+ ), 2
+ )
+ ),
+ SIN(RADIANS(lat)) * SIN(RADIANS(#{ref_lat})) +
+ COS(RADIANS(lat)) * COS(RADIANS(#{ref_lat})) * COS(RADIANS(#{ref_lng}-lng))
+ )
+ )/360
+ )"
+ end
+
+ # Asks Google for the georef of a location.
+ def location_to_georef(location)
+ key = location.downcase
+ ll = Rails.cache.read key
+ return ll + [:CACHE] if ll.kind_of? Array
+ resp = RestClient.get 'https://maps.googleapis.com/maps/api/geocode/json?' +
+ 'sensor=false&address=' + url_encode(location)
+ if resp.nil? || resp.code.to_i != 200
+ if ENV['RAILS_ENV'] == 'test'
+ print " Google Maps API fail (code #{resp ? resp.code : :nil}) "
+ else
+ Rails.logger.warn "Google Maps API request information for " +
+ "\"#{location}\" fail. (code #{resp ? resp.code : :nil})"
+ end
+ return [ 0, 0, "HTTP_FAIL_#{resp.code}".to_sym ] # do not cache failed response
+ else
+ json = JSON.parse resp.body
+ if json && (r=json['results']) && (r=r[0]) && (r=r['geometry']) &&
+ (r=r['location']) && r['lat']
+ ll = [ r['lat'], r['lng'], :SUCCESS ]
+ else
+ status = json['status'] || 'Undefined Error'
+ message = "Google Maps API cant find \"#{location}\" (#{status})"
+ if ENV['RAILS_ENV'] == 'test'
+ print " #{message} "
+ else
+ Rails.logger.warn message
+ end
+ ll = [ 0, 0, status.to_sym ]
+ end
+ Rails.cache.write key, ll
+ end
+ ll
+ end
+
+ end
end
=====================================
test/unit/geo_ref_test.rb
=====================================
--- /dev/null
+++ b/test/unit/geo_ref_test.rb
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class GeoRefTest < ActiveSupport::TestCase
+
+ ll = {
+ salvador: [-12.9, -38.5],
+ rio_de_janeiro: [-22.9, -43.1],
+ new_york: [ 40.7, -74.0],
+ tokyo: [ 35.6, 139.6]
+ }
+
+ should 'calculate the distance between lat,lng points' do
+ assert_equal 1215, Noosfero::GeoRef.dist(*(ll[:salvador]+ll[:rio_de_janeiro])).round
+ assert_equal 6998, Noosfero::GeoRef.dist(*(ll[:salvador]+ll[:new_york])).round
+ assert_equal 17503, Noosfero::GeoRef.dist(*(ll[:salvador]+ll[:tokyo])).round
+ end
+
+ should 'calculate the distance between a lat,lng points and a profile' do
+ env = fast_create Environment, name: 'SomeSite'
+ @acme = Enterprise.create! environment: env, identifier: 'acme', name: 'ACME',
+ city: 'Salvador', state: 'Bahia', country: 'BR', lat: -12.9, lng: -38.5
+ def sql_dist_to(ll)
+ ActiveRecord::Base.connection.execute(
+ "SELECT #{Noosfero::GeoRef.sql_dist ll[0], ll[1]} as dist" +
+ " FROM profiles WHERE id = #{@acme.id};"
+ ).first['dist'].to_f.round
+ end
+ assert_equal 1215, sql_dist_to(ll[:rio_de_janeiro])
+ assert_equal 6998, sql_dist_to(ll[:new_york])
+ assert_equal 17503, sql_dist_to(ll[:tokyo])
+ end
+
+ def round_ll(ll)
+ ll.map{|n| n.is_a?(Float) ? n.to_i : n }
+ end
+
+ should 'get lat/lng from address' do
+ Rails.cache.clear
+ ll = Noosfero::GeoRef.location_to_georef 'Salvador, Bahia, BR'
+ assert_equal [-12, -38, :SUCCESS], round_ll(ll)
+ end
+
+ should 'get and cache lat/lng from address' do
+ Rails.cache.clear
+ ll = Noosfero::GeoRef.location_to_georef 'Curitiba, Paraná, BR'
+ assert_equal [-25, -49, :SUCCESS], round_ll(ll)
+ ll = Noosfero::GeoRef.location_to_georef 'Curitiba, Paraná, BR'
+ assert_equal [-25, -49, :SUCCESS, :CACHE], round_ll(ll)
+ end
+
+ should 'notify a non existent address' do
+ Rails.cache.clear
+ orig_env = ENV['RAILS_ENV']
+ ENV['RAILS_ENV'] = 'X' # cancel throw for test mode on process_rest_req.
+ ll = Noosfero::GeoRef.location_to_georef 'Nowhere, Nocountry, XYZ'
+ ENV['RAILS_ENV'] = orig_env # restore value to do not mess with other tests.
+ assert_equal [0, 0, :ZERO_RESULTS], round_ll(ll)
+ end
+
+end
View it on GitLab: https://gitlab.com/noosfero/noosfero/compare/c366e6ecb18b9aa7add7bc3f2a3a93051babef28...9efdcbd7c0888f533da6deee3aafc96264732f70
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listas.softwarelivre.org/pipermail/noosfero-dev/attachments/20150623/94920701/attachment-0001.html>
More information about the Noosfero-dev
mailing list