Skip to content

Commit 47dcb61

Browse files
torfjeldenalimilan
andauthored
Small improvements to Haversine (#226)
Co-authored-by: Milan Bouchet-Valat <[email protected]>
1 parent ce6f72d commit 47dcb61

File tree

2 files changed

+49
-4
lines changed

2 files changed

+49
-4
lines changed

src/haversine.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ The computed distance has the unit of the radius.
1111
struct Haversine{T<:Number} <: Metric
1212
radius::T
1313
end
14-
Haversine() = Haversine(Float32(6_371_000))
14+
Haversine{T}() where {T<:Number} = Haversine(T(6_371_000))
15+
Haversine() = Haversine{Int}()
1516

1617
function (dist::Haversine)(x, y)
1718
length(x) == length(y) == 2 || haversine_error(dist)
@@ -26,13 +27,16 @@ function (dist::Haversine)(x, y)
2627
a = sind(Δφ/2)^2 + cosd(φ₁)*cosd(φ₂)*sind(Δλ/2)^2
2728

2829
# distance on the sphere
29-
2 * dist.radius * asin( min(a, one(a)) ) # take care of floating point errors
30+
2 * (dist.radius * asin( min(a, one(a)) )) # take care of floating point errors
3031
end
3132

32-
haversine(x, y, radius::Number=Float32(6_371_000)) = Haversine(radius)(x, y)
33+
haversine(x, y, radius::Number=6_371_000) = Haversine(radius)(x, y)
3334

3435
@noinline haversine_error(dist) = throw(ArgumentError("expected both inputs to have length 2 in $dist distance"))
3536

37+
result_type(::Haversine{T1}, ::Type{T2}, ::Type{T3}) where {T1<:Number,T2<:Number,T3<:Number} =
38+
float(promote_type(T1, T2, T3))
39+
3640
"""
3741
SphericalAngle()
3842
@@ -61,4 +65,4 @@ end
6165

6266
const spherical_angle = SphericalAngle()
6367

64-
result_type(::Union{Haversine, SphericalAngle}, ::Type, ::Type) = Float64
68+
result_type(::SphericalAngle, ::Type{T1}, ::Type{T2}) where {T1<:Number,T2<:Number} = float(promote_type(T1, T2))

test/test_dists.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,36 @@ function test_metricity(dist, x, y, z)
6060
end
6161
end
6262

63+
function test_batch(dist, _x, _y, args=(); Tins=(Float32, Float64), Touts=Tins)
64+
_xs = hcat(_x, _x)
65+
_ys = hcat(_y, _y)
66+
67+
for (Tin, Tout) in zip(Tins, Touts)
68+
x, y = Tin.(_x), Tin.(_y)
69+
xs, ys = Tin.(_xs), Tin.(_ys)
70+
71+
# Instantiate using `args` if not already instantiated.
72+
d = if dist isa PreMetric
73+
dist
74+
else
75+
dist(map(a -> Tin.(a), args)...)
76+
end
77+
78+
# Ensure that `result_type` preserves the type.
79+
@test result_type(d, xs, ys) === Tout
80+
81+
# Ensure that `pairwise` preserves the type,
82+
# e.g. if someone decides to implement their own.
83+
colpairs = pairwise(d, xs, ys; dims=2)
84+
@test eltype(colpairs) === Tout
85+
@test all(colpairs .== d(x, y))
86+
87+
cols = colwise(d, xs, ys)
88+
@test eltype(cols) === Tout
89+
@test all(cols .== d(x, y))
90+
end
91+
end
92+
6393
@testset "PreMetric, SemiMetric, Metric on $T" for T in (Float64, F64)
6494
Random.seed!(123)
6595
n = 100
@@ -424,6 +454,17 @@ end
424454
end #testset
425455

426456
@testset "haversine" begin
457+
let x = [1.,-15.625], y = [-179.,15.625]
458+
# Type used in default constructor should work for both `Float32` and `Float64`.
459+
test_batch(Haversine(), x, y)
460+
461+
# `Float32` should be promoted to `Float64`.
462+
test_batch(Haversine{Float32}(), x, y)
463+
464+
# `Haversine(T(radius))` should result in `T` when inputs also has eltype `T`.
465+
test_batch(Haversine, x, y, (6_371_000, ))
466+
end
467+
427468
for T in (Float64, F64)
428469
@test haversine([-180.,0.], [180.,0.], 1.) 0 atol=1e-10
429470
@test haversine([0.,-90.], [0.,90.], 1.) π atol=1e-10

0 commit comments

Comments
 (0)