# kristianmandrup/geo_magic

better unit and distance handling

1 parent 1976bb9 commit 609b232c0f0ca330bc95e7f01dee117f5d8e48c7 committed Feb 24, 2011
1 lib/geo_magic.rb
 @@ -11,5 +11,6 @@ module GeoMagic require 'geo_magic/calculate' require 'geo_magic/distance' require 'geo_magic/radius' +require 'geo_magic/vector' require 'geo_magic/meta' require 'geo_magic/geocoder'
20 lib/geo_magic/core_ext.rb
 @@ -58,12 +58,24 @@ def radians_ratio class Array def is_point? + return true if self.first.kind_of? GeoMagic::Point (0..1).all? {|n| self[n].is_a?(Numeric) } end def to_point raise "For an array to be converted to a point, it must consist of two numbers, was: #{self}" if !is_point? - GeoMagic::Point.new self[0], self[1] + point_args = if self.first.kind_of? GeoMagic::Point + self.first + else + self[0..1] + end + GeoMagic::Point.new point_args + end + + def to_points + res = [] + each_slice(2) {|point| res << point.to_point } + res end def are_points? @@ -72,11 +84,7 @@ def are_points? end true end - - def as_map_points - self.extend GeoMagic::MapPoints - end - + def sort_by_distance self.sort_by { |item| get_dist_obj(item).dist } end
7 lib/geo_magic/distance.rb
 @@ -3,6 +3,7 @@ require 'geo_magic/distance/unit' require 'geo_magic/distance/vector' require 'geo_magic/distance/formula' +require 'geo_magic/distance/point_distance' require 'geo_magic/distance/points_distance' module GeoMagic @@ -65,10 +66,14 @@ def self.valid_radius_types end GeoMagic::Distance.units.each do |unit| - class_eval %{ + class_eval %{ def #{unit} self[:#{unit}] end + + def in_#{unit} + GeoMagic::Distance::Unit.new(unit, distance).in_#{unit} + end } end
18 lib/geo_magic/distance/point_distance.rb
 @@ -0,0 +1,18 @@ +module GeoMagic + class PointDistance + attr_accessor :distance, :point + + def initialize distance, point + raise ArgumentError, "First argument must be a GeoMagic::Distance, was #{distance}" if !distance.kind_of? GeoMagic::Distance + # raise ArgumentError, "First argument must be a collection of GeoMagic::Point, was #{points}" if !points. + self.distance = distance + self.point = point + end + + # return whether the distance between this point and a given center is within the given distance + def of center + GeoMagic::Vector.new(point, center).distance <= distance + end + end +end +
37 lib/geo_magic/distance/unit.rb
 @@ -8,6 +8,43 @@ def initialize name, number = 0 @number = number end + def meters_map + {:miles => 0.0062, + :feet => 32.8, + :km => 0.01, + :meters => 1 + } + end + + def in_meters + meters_map[name.to_sym] * number + end + + def to_meters! + self.number = meters_map[name] * number + end + + [:miles, :feet, :km].each do |unit| + class_eval %{ + def in_#{unit} + to_meters * meters_map[#{unit}] + end + + def to_#{unit}! + self.number = to_meters * meters_map[#{unit}] + self.name = :#{unit} + end + } + end + + [:<, :<=, :>, :>=, :==].each do |op| + class_eval %{ + def #{op} dist_unit + in_meters #{op} dist_unit.in_meters + end + } + end + def number @number.round_to(precision[name]) end
4 lib/geo_magic/point.rb
 @@ -30,6 +30,10 @@ def within? shape shape.contains? point end + def within distance + GeoMagic::PointDistance.new distance, self + end + # factory method def self.create_from *args latitude, longitude = case args.size
10 lib/geo_magic/point/map_points.rb
 @@ -9,6 +9,16 @@ def rad def within_radius radius_obj, options = {:precision => :lowest} end + # return Points Center + def near center + GeoMagic::PointsCenter.new center, self + end + + # return PointsDistance + def within distance + GeoMagic::PointsDistance.new distance, self + end + # todo: should make radius from distance! def within_distance dist_obj, options = {:precision => :lowest} calc_method = get_proc(options[:precision] || :normal)
17 lib/geo_magic/point/points_center.rb
 @@ -0,0 +1,17 @@ +module GeoMagic + class PointsCenter + attr_accessor :distance, :center + + def initialize distance, point + raise ArgumentError, "First argument must be a GeoMagic::Distance, was #{distance}" if !distance.kind_of? GeoMagic::Distance + # raise ArgumentError, "First argument must be a collection of GeoMagic::Point, was #{points}" if !points. + self.distance = distance + self.point = point + end + + # return whether the distance between this point and a given center is within the given distance + def of center + GeoMagic::Vector.new(point, center).distance <= distance + end + end +end
99 lib/geo_magic/vector.rb
 @@ -1,60 +1,59 @@ module GeoMagic - class Vector - attr_accessor :p0, :p1 - - def initialize p0, p1 - raise "Vector must be initialized with a start end ending point, was: #{p0}, #{p1}" if ![p0, p1].are_points? - @p0 = p0 - @p1 = p1 - end + class Vector + attr_accessor :p0, :p1 + + def initialize p0, p1 + raise "Vector must be initialized with a start end ending point, was: #{p0}, #{p1}" if ![p0, p1].are_points? + @p0 = p0 + @p1 = p1 + end - def create_at center, vector - new center, center.move(vector) - end + def create_at center, vector + new center, center.move(vector) + end - def length type = nil - case type - when nil - GeoMagic::Distance.distance(p0, p1) - when :latitude - (p0.latitude - p1.latitude).abs - when :longitude - (p0.longitude - p1.longitude).abs - else - raise ArgumentError, "Bad argument for calculating lenght, valid args are: nil, :latitude or :longitude" - end + def length type = nil + case type + when nil + GeoMagic::Distance.distance(p0, p1) + when :latitude + (p0.latitude - p1.latitude).abs + when :longitude + (p0.longitude - p1.longitude).abs + else + raise ArgumentError, "Bad argument for calculating lenght, valid args are: nil, :latitude or :longitude" end + end - def vector_distance - GeoMagic::Distance::Vector.new length(:latitude), length(:longitude) - end + def vector_distance + GeoMagic::Distance::Vector.new length(:latitude), length(:longitude) + end - def distance options = { :unit => :meters } - dist = Math.sqrt((delta_longitude(vector) + delta_latitude).abs) - unit_dist = ::GeoMagic::Distance.new(dist).send unit - unit_dist.number - end - - def [] key - case key - when 0, :p0 - p0 - when 1, :p1 - p1 - else - raise "Vector key must be either 0/1 or :p0/:p1" - end - end + def distance options = { :unit => :meters } + dist = Math.sqrt((delta_longitude + delta_latitude).abs) + unit = options[:unit] || :meters + ::GeoMagic::Distance.new(dist).send unit + end - protected - - def delta_longitude - (p0.longitude - p1.longitude)**2 + def [] key + case key + when 0, :p0 + p0 + when 1, :p1 + p1 + else + raise "Vector key must be either 0/1 or :p0/:p1" end - - def delta_latitude vector - (p0.latitude - p1.latitude)**2 - end end + + protected + + def delta_longitude + (p0.longitude - p1.longitude)**2 + end + + def delta_latitude + (p0.latitude - p1.latitude)**2 + end end -end +end
27 spec/geo_magic/distance/point_distance_spec.rb
 @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe GeoMagic::PointDistance do + before do + @a = [45.1, 11].to_point + @b = [48, 11].to_point + @center = [45, 11].to_point + end + + context '5km from point A and B' do + before do + a5 = GeoMagic::PointDistance.new 5.km, @a + b5 = GeoMagic::PointDistance.new 5.km, @b + end + + describe '#of' do + it "should be that A is within 5km of center" do + a5.of(@center).should be_true + end + + it "should NOT be that B is within 5km of center" do + b5.of(@center).should_not be_true + end + end + end +end +
6 spec/geo_magic/distance/points_distance_spec.rb
 @@ -5,13 +5,17 @@ @a = [45.1, 11].to_point @b = [48, 11].to_point @center = [45, 11].to_point + + GeoMagic::PointsDistance.new 5.km, [@a, @b].to_points end context 'Distance vector' do subject { GeoMagic::PointsDistance.new 5.km, [@a, @b].to_points } describe '#near' do - subject.near(@center).should include?(@a) + it "should only select points near center" do + subject.near(@center).should include(@a) + end end end end
8 spec/geo_magic/distance_spec.rb
 @@ -12,21 +12,21 @@ subject { 5.km } describe 'Select subset of points within distance' do - describe '#select_within' + describe '#select_within' do subject.select_within @points, @center end - describe '#select_all #near' + describe '#select_all #near' do subject.select_all(@points).near(@center) end end describe 'Reject subset of points within distance' do - describe '#reject_within' + describe '#reject_within' do subject.reject_near @points, @center end - describe '#reject_all #within' + describe '#reject_all #within' do subject.reject_all(@points).near(@center) end end