Skip to content

Commit

Permalink
calculates the location of a destination point
Browse files Browse the repository at this point in the history
  • Loading branch information
mamantoha committed Apr 10, 2024
1 parent 160c7f5 commit e769bb6
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 27 deletions.
8 changes: 8 additions & 0 deletions spec/haversine_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,12 @@ describe Haversine do
it { dist.to_feet.should eq(18275860.669896744) }
end
end

describe ".destination" do
describe "calculates the location of a destination point" do
it { Haversine.destination(39, -75, 5000, 90, :kilometers).should eq({26.440010707631124, -22.885355549364313}) }
it { Haversine.destination([39, -75], 5000, 90, :kilometers).should eq({26.440010707631124, -22.885355549364313}) }
it { Haversine.destination({39, -75}, 5000, 90, :kilometers).should eq({26.440010707631124, -22.885355549364313}) }
end
end
end
79 changes: 77 additions & 2 deletions src/haversine.cr
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
require "./haversine/*"

# The haversine formula determines the great-circle distance between two points on a sphere
# given their latitudes and longitudes.
#
# https://en.wikipedia.org/wiki/Haversine_formula
module Haversine
extend self

EARTH_RADIUS = 6371008.8

# Unit of measurement factors using a spherical (non-ellipsoid) earth radius.
#
# Keys are the name of the unit, values are the number of that unit in a single radians
FACTORS = {
centimeters: EARTH_RADIUS * 100,
centimetres: EARTH_RADIUS * 100,
degrees: 360 / (2 * Math::PI),
feet: EARTH_RADIUS * 3.28084,
inches: EARTH_RADIUS * 39.37,
kilometers: EARTH_RADIUS / 1000,
kilometres: EARTH_RADIUS / 1000,
meters: EARTH_RADIUS,
metres: EARTH_RADIUS,
miles: EARTH_RADIUS / 1609.344,
millimeters: EARTH_RADIUS * 1000,
millimetres: EARTH_RADIUS * 1000,
nautical_miles: EARTH_RADIUS / 1852,
radians: 1,
yards: EARTH_RADIUS * 1.0936,
}

alias Number = Int32 | Float32 | Float64

RAD_PER_DEG = Math::PI / 180
Expand Down Expand Up @@ -38,11 +59,65 @@ module Haversine
distance(lat1, lon1, lat2, lon2)
end

# Takes an original point by `latitude`, `longitude` and calculates the location of a destination point
# given a `distance` factor in degrees, radians, miles, or kilometers; and `bearing` in degrees.
#
# `bearing` ranging from -180 to 180
#
# https://github.com/Turfjs/turf/blob/master/packages/turf-destination/index.ts
def destination(latitude : Number, longitude : Number, distance : Float64, bearing : Float64, unit : Symbol = :kilometers) : Tuple(Float64, Float64)
factor = FACTORS[unit]

radians = distance / factor
bearing_rad = to_radians(bearing)

latitude1 = to_radians(latitude)
longitude1 = to_radians(longitude)

latitude2 = Math.asin(
Math.sin(latitude1) * Math.cos(radians) +
Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearing_rad)
)

longitude2 =
longitude1 +
Math.atan2(
Math.sin(bearing_rad) * Math.sin(radians) * Math.cos(latitude1),
Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2)
)

{to_degrees(latitude2), to_degrees(longitude2)}
end

# :ditto:
def destination(coord : Array(Number), distance : Float64, bearing : Float64, unit : Symbol = :kilometers) : Tuple(Float64, Float64)
latitude, longitude = coord

destination(latitude, longitude, distance, bearing, unit)
end

# :ditto:
def destination(coord : Tuple(Number, Number), distance : Float64, bearing : Float64, unit : Symbol = :kilometers) : Tuple(Float64, Float64)
latitude, longitude = coord

destination(latitude, longitude, distance, bearing, unit)
end

private def calc(dlat : Number, lat1 : Number, lat2 : Number, dlon : Number) : Number
(Math.sin(rpd(dlat) / 2)) ** 2 + Math.cos(rpd(lat1)) * Math.cos((rpd(lat2))) * (Math.sin(rpd(dlon) / 2)) ** 2
end

private def rpd(num : Number) : Number
num * RAD_PER_DEG
end

private def to_radians(degrees : Float64) : Float64
degrees * Math::PI / 180.0
end

private def to_degrees(radians : Float64) : Float64
radians * 180.0 / Math::PI
end
end

require "./haversine/*"
27 changes: 2 additions & 25 deletions src/haversine/distance.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,14 @@ module Haversine
class Distance
include Comparable(self)

EARTH_RADIUS = 6371008.8

# Unit of measurement factors using a spherical (non-ellipsoid) earth radius.
#
# Keys are the name of the unit, values are the number of that unit in a single radians
FACTORS = {
centimeters: EARTH_RADIUS * 100,
centimetres: EARTH_RADIUS * 100,
degrees: 360 / (2 * Math::PI),
feet: EARTH_RADIUS * 3.28084,
inches: EARTH_RADIUS * 39.37,
kilometers: EARTH_RADIUS / 1000,
kilometres: EARTH_RADIUS / 1000,
meters: EARTH_RADIUS,
metres: EARTH_RADIUS,
miles: EARTH_RADIUS / 1609.344,
millimeters: EARTH_RADIUS * 1000,
millimetres: EARTH_RADIUS * 1000,
nautical_miles: EARTH_RADIUS / 1852,
radians: 1,
yards: EARTH_RADIUS * 1.0936,
}

property distance

def initialize(@distance : Number)
end

{% for factor in FACTORS.keys %}
{% for factor in Haversine::FACTORS.keys %}
def to_{{factor.id}} : Number
@distance * FACTORS[:{{factor.id}}]
@distance * Haversine::FACTORS[:{{factor.id}}]
end
{% end %}

Expand Down

0 comments on commit e769bb6

Please sign in to comment.