Read more

Fixing Graticule's "distance" for edge cases

Arne Hartherz
February 09, 2012Software engineer at makandra GmbH

Ever seen this error when using Graticule?

Numerical argument out of domain - acos
Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

Similarly to the to_sql problem for some edge cases, Graticule::Distance::Spherical.distance (and possibly those of Graticule's other distance computation classes) is subject to Float rounding errors.

This can cause the above error, when the arc cosine of something slightly more than 1.0 is to be computed, e.g. for the (zero) distance between the same coordinates (applies only for some).

How to fix

So, similar to the SQL fix, we just force the value we compute the arc cosine of into [-1; 1] bounds.

Put this into a config/initializers/graticule_spherical_distance.rb initializer:

Graticule::Distance::Spherical.class_eval do
  def self.distance(from, to, units = :miles)
    from_longitude  = from.longitude.to_radians
    from_latitude   = from.latitude.to_radians
    to_longitude    = to.longitude.to_radians
    to_latitude     = to.latitude.to_radians

    input = [ [ (
      Math.sin(from_latitude) *
      Math.sin(to_latitude) +
      Math.cos(from_latitude) *
      Math.cos(to_latitude) *
      Math.cos(to_longitude - from_longitude)
    ), -1 ].max, 1 ].min
    Math.acos(input) * Graticule::Distance::EARTH_RADIUS[units.to_sym]
  end
end

Spec

Here is a spec to add to your application:

describe Graticule::Distance::Spherical do

  describe '.distance' do
    it 'should work for edge case zero-distance computation' do
      from = stub :latitude => BigDecimal('0.4991657542 5E2'), :longitude => BigDecimal('-0.1169188713 5E3')
      to = stub :latitude => BigDecimal('0.4991657542 5E2'), :longitude => BigDecimal('-0.1169188713 5E3')

      Graticule::Distance::Spherical.distance(from, to).should == 0
    end
  end

end
Posted by Arne Hartherz to makandra dev (2012-02-09 11:42)