Skip to content

Added struct #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 19, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
name = "Haversine"
uuid = "5360f785-287a-47c2-b373-25ddc8e348fa"
authors = ["Ali Shannon"]
version = "1.0.0"
version = "1.1.0"

[deps]
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"

[compat]
julia = "1"
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Haversine.jl

[![Build Status](https://travis-ci.com/techshot25/Haversine.jl.svg?branch=master)](https://travis-ci.com/techshot25/Haversine.jl)
[![Build status](https://ci.appveyor.com/api/projects/status/r28nu7ghasrwgwcj?svg=true)](https://ci.appveyor.com/project/techshot25/haversine-jl)
[![codecov](https://codecov.io/gh/techshot25/Haversine.jl/branch/master/graph/badge.svg?token=W0VM6KD0CW)](https://codecov.io/gh/techshot25/Haversine.jl)
[![Coverage Status](https://coveralls.io/repos/github/techshot25/Haversine.jl/badge.svg?branch=master)](https://coveralls.io/github/techshot25/Haversine.jl?branch=master)

---

@@ -17,8 +15,10 @@ This uses the great circle distance to find the approximate distance between two
```julia
using Haversine

p1 = [1, 2] # (lon, lat) in degrees
p2 = [3, 4]


p1 = GeoLocation(λ=1, ϕ=2)
p2 = GeoLocation(3, 4) # (lon, lat) in degrees

# returns distance in meters
HaversineDistance(p1, p2)
@@ -31,8 +31,8 @@ This returns the bearing/heading between from point 1 to point 2 in degrees
```julia
using Haversine

p1 = [1, 2] # (lon, lat) in degrees
p2 = [3, 4]
p1 = GeoLocation(λ=1, ϕ=2)
p2 = GeoLocation(3, 4)

# returns heading in degrees
HaversineBearing(p1, p2)
@@ -62,24 +62,24 @@ All functions as of version 1.0.0 can now support broadcasting. Arguments can br
```julia
using Haversine

p = [5, 4] # initial location
p = GeoLocation(5, 4) # initial location
θ = [30, 60] # multiple headings
d = [10, 900000] # destination for each heading

HaversineDestination(p, θ, d)
>>> 2-element Array{Array{Float64,1},1}:
>>> [5.000045075887166, 4.0000778835344555]
>>> [12.072951161820168, 8.006647216172182]
>>> 2-element Vector{GeoLocation}:
>>> GeoLocation(5.000045075887166, 4.0000778835344555)
>>> GeoLocation(12.072951161820168, 8.006647216172182
```

```julia
using Haversine

p1 = [[1, 2], [3, 4]] # multiple points
p2 = [[5, 1], [0, 9]]
p1 = [GeoLocation(1, 2), GeoLocation(3, 4)] # multiple points
p2 = [GeoLocation(5, 1), GeoLocation(0, 9)]

HaversineBearing(p1, p2)
>>> 2-element Array{Float64,1}:
>>> 126.81261556373533
>>> -11.186184406292147
>>> 103.98283865771535
>>> -30.644744331249175
```
112 changes: 31 additions & 81 deletions src/Haversine.jl
Original file line number Diff line number Diff line change
@@ -1,115 +1,65 @@
module Haversine

using Parameters

R = 6.371e6 # earth's volumetric mean radius in meters


function BaseHaversineDistance(λ1::Number, ϕ1::Number, λ2::Number, ϕ2::Number)::Float64
# Elementwise implementation of HaversineDistance
@with_kw struct GeoLocation
λ::Real
ϕ::Real
end


function HaversineDistance(p1::GeoLocation, p2::GeoLocation)::Float64
λ1, ϕ1 = p1.λ, p1.ϕ
λ2, ϕ2 = p2.λ, p2.ϕ
Δϕ = ϕ2 - ϕ1
Δλ = λ2 - λ1
a = sind(Δϕ / 2)^2 + cosd(ϕ1) * cosd(ϕ2) * sind(Δλ / 2)^2
c = 2 * atan(sqrt(a), sqrt(1 - a))
return c * R
return c * R
end


function BaseHaversineBearing(λ1::Number, ϕ1::Number, λ2::Number, ϕ2::Number)::Float64
# Elementwise implementation of HaversineBearing
Δϕ = ϕ2 - ϕ1
function HaversineBearing(p1::GeoLocation, p2::GeoLocation)::Float64
λ1, ϕ1 = p1.λ, p1.ϕ
λ2, ϕ2 = p2.λ, p2.ϕ
Δλ = λ2 - λ1
θ = atand(sind(Δλ) * cosd(ϕ2), cosd(ϕ1) * sind(ϕ2) - sind(ϕ1) * cosd(ϕ2) * cosd(Δλ))
return θ
end


function BaseHaversineDestination(λ1::Number, ϕ1::Number, θ::Number, d::Number)::Array{Float64}
# Elementwise implementation of HaversineDestination
function HaversineDestination(geopoint::GeoLocation, θ::Real, d::Real)::GeoLocation
λ1, ϕ1 = geopoint.λ, geopoint.ϕ
δ = d / R
ϕ2 = asind(sind(ϕ1) * cos(δ) + cosd(ϕ1) * sin(δ) * cosd(θ))
λ2 = λ1 + atand(sind(θ) * sin(δ) * cosd(ϕ1), cos(δ) - sind(ϕ1) * sind(ϕ2))
return [λ2, ϕ2]
return GeoLocation(λ=λ2, ϕ=ϕ2)
end


function HaversineDistance(p1, p2)
#=
Get the haversine distance between two points

Parameters
----------
p1 : Array-like[Number] or nested array of multiple points
First point in degrees (lon, lat)
p2 : Array-like[Number] or nested array of multiple points
Second point in degrees (lon, lat)

Returns
-------
Float64 or Array{Float64}
Haversine distance between points in meters
=#
p1, p2 = reduce(hcat, p1), reduce(hcat, p2)
if size(p1)[1] == 1
(λ1, ϕ1), (λ2, ϕ2) = p1, p2
else
(λ1, ϕ1), (λ2, ϕ2) = (p1[1, :], p1[2, :]), (p2[1, :], p2[2, :])
end
return broadcast((λ1, ϕ1, λ2, ϕ2) -> BaseHaversineDistance(λ1, ϕ1, λ2, ϕ2), λ1, ϕ1, λ2, ϕ2)
function HaversineDistance(p1::AbstractArray{GeoLocation}, p2::AbstractArray{GeoLocation})::Vector{Float64}
return map(HaversineDistance, p1, p2)
end


function HaversineBearing(p1, p2)
#=
Find the heading from point 1 to point 2

Parameters
----------
p1 : Array-like[Number] or nested array of multiple points
First point coordinates in degrees (lon, lat)
p2 : Array-like[Number] or nested array of multiple points
Second point coordinates in degrees (lon, lat)

Returns
-------
Float64 or Array{Float64}
The heading in degrees
=#
p1, p2 = reduce(hcat, p1), reduce(hcat, p2)
if size(p1)[1] == 1
(λ1, ϕ1), (λ2, ϕ2) = p1, p2
else
(λ1, ϕ1), (λ2, ϕ2) = (p1[1, :], p1[2, :]), (p2[1, :], p2[2, :])
end
return broadcast((λ1, ϕ1, λ2, ϕ2) -> BaseHaversineBearing(λ1, ϕ1, λ2, ϕ2), λ1, ϕ1, λ2, ϕ2)
function HaversineBearing(p1::AbstractArray{GeoLocation}, p2::AbstractArray{GeoLocation})::Vector{Float64}
return map(HaversineBearing, p1, p2)
end


function HaversineDestination(p, θ, d)
#=
Find the destination coordinates given a starting point

Parameters
----------
p : Array-like[Number] or nested array of multiple points
Initial coordinates in degrees (lon, lat)
θ : Number or Array-like{Number}
Heading in degrees
d : Number or Array-like{Number}
Distance in meters

Returns
-------
Array[Float, Float]
The destination point coordinates in degrees
=#
p = reduce(hcat, p)
if size(p)[1] == 1
λ1, ϕ1 = p
else
λ1, ϕ1 = p[1, :], p[2, :]
function HaversineDestination(
p::Union{AbstractArray, GeoLocation},
θ::Union{AbstractArray, Real},
d::Union{AbstractArray, Real}
)::Vector{GeoLocation}
if isa(p, GeoLocation)
return broadcast((y, z) -> HaversineDestination(p, y, z), θ, d)
end
return broadcast((λ1, ϕ1, θ, d) -> BaseHaversineDestination(λ1, ϕ1, θ, d), λ1, ϕ1, θ, d)
return broadcast((x, y, z) -> HaversineDestination(x, y, z), p, θ, d)
end

export HaversineDistance, HaversineBearing, HaversineDestination
export GeoLocation, HaversineDistance, HaversineBearing, HaversineDestination

end
27 changes: 20 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -2,9 +2,9 @@ using Haversine
using Test

# constants for single value test
single_p1, single_p2 = [1, 2], [9, 1]
single_p1, single_p2 = GeoLocation(1, 2), GeoLocation(9, 1)
single_θ = 45
p = [5, 4]
p = GeoLocation(5, 4)
d = 900000


@@ -13,20 +13,33 @@ bearing = 97.01
destination = [10.80, 9.69]

# constants for broadcasting test
p1 = [[1, 2], [3, 4], [0, 9]]
p2 = [[5, 1], [0, 9], [12, 4]]
p1 = [GeoLocation(1, 2), GeoLocation(3, 4), GeoLocation(0, 9)]
p2 = [GeoLocation(5, 1), GeoLocation(0, 9), GeoLocation(12, 4)]
θ = [30, 60]

geo_p1 = [GeoLocation(1, 2), GeoLocation(3, 4), GeoLocation(0, 9)]
geo_p2 = [GeoLocation(5, 1), GeoLocation(0, 9), GeoLocation(12, 4)]

distances = [458315.02, 647215.42, 1.44e6]
bearings = [103.98, -30.645, 111.99]
destinations = [[9.11, 10.99], [12.07, 8.01]]
destinations = [[12.86, 2.01], [0.81, 10.95], [12.50, 0.94]]
destinations2 = [[8.85, 0.032], [-1.19, 10.95], [7.54, 5.90]]

computed_geo_dest = HaversineDestination(p, single_θ, d)
computed_dest = [computed_geo_dest.λ, computed_geo_dest.ϕ]
computed_dests = [[x.λ, x.ϕ] for x in HaversineDestination(p, bearings, d)]
computed_dests2 = [[x.λ, x.ϕ] for x in HaversineDestination(geo_p1, bearings, d)]

@testset "Haversine.jl" begin
@test isapprox(HaversineDistance(single_p1, single_p2), distance, rtol=0.01)
@test isapprox(HaversineBearing(single_p1, single_p2), bearing, rtol=0.01)
@test isapprox(HaversineDestination(p, single_θ, d), destination, rtol=0.01)
@test isapprox(computed_dest, destination, rtol=0.01)

@test isapprox(HaversineDistance(p1, p2), distances, rtol=0.01)
@test isapprox(HaversineBearing(p1, p2), bearings, rtol=0.01)
@test isapprox(HaversineDestination(p, θ, d), destinations, rtol=0.01)
@test isapprox(computed_dests, destinations, rtol=0.01)

@test isapprox(HaversineDistance(geo_p1, geo_p2), distances, rtol=0.01)
@test isapprox(HaversineBearing(geo_p1, geo_p2), bearings, rtol=0.01)
@test isapprox(computed_dests2, destinations2, rtol=0.01)
end