1
+ #=
2
+
3
+
4
+ # UnitSphericalPoint
5
+
6
+ This file defines the [`UnitSphericalPoint`](@ref) type, which is
7
+ a three-dimensional Cartesian point on the unit 2-sphere (i.e., of radius 1).
8
+
9
+ This file contains the full implementation of the type as well as a `spherical_distance` function
10
+ that computes the great-circle distance between two points on the unit sphere.
11
+
12
+ ```@docs; canonical=false
13
+ UnitSphericalPoint
14
+ spherical_distance
15
+ ```
16
+ =#
17
+
18
+ # ## Type definition and constructors
19
+ """
20
+ UnitSphericalPoint(v)
21
+
22
+ A unit spherical point, i.e., point living on the 2-sphere (𝕊²),
23
+ represented as Cartesian coordinates in ℝ³.
24
+
25
+ This currently has no support for heights, only going from lat long to spherical
26
+ and back again.
27
+
28
+ ## Examples
29
+
30
+ ```jldoctest
31
+ julia> UnitSphericalPoint(1, 0, 0)
32
+ UnitSphericalPoint(1.0, 0.0, 0.0)
33
+ ```
34
+
35
+ """
36
+ struct UnitSphericalPoint{T} <: StaticArrays.FieldVector{3, T}
37
+ x:: T
38
+ y:: T
39
+ z:: T
40
+ end
41
+
42
+ UnitSphericalPoint {T} (v:: SVector{3, T} ) where T = UnitSphericalPoint {T} (v... )
43
+ UnitSphericalPoint (v:: NTuple{3, T} ) where T = UnitSphericalPoint {T} (v... )
44
+ UnitSphericalPoint {T} (v:: NTuple{3, T} ) where T = UnitSphericalPoint {T} (v... )
45
+ UnitSphericalPoint {T} (v:: SVector{3, T} ) where T = UnitSphericalPoint {T} (v... )
46
+ UnitSphericalPoint (v:: SVector{3, T} ) where T = UnitSphericalPoint {T} (v... )
47
+ # # handle the 2-tuple case specifically
48
+ UnitSphericalPoint (v:: NTuple{2, T} ) where T = UnitSphereFromGeographic ()(v)
49
+ # # handle the GeoInterface case, this is the catch-all method
50
+ UnitSphericalPoint (v) = UnitSphericalPoint (GI. trait (v), v)
51
+ UnitSphericalPoint (:: GI.PointTrait , v) = UnitSphereFromGeographic ()(v) # since it works on any GI compatible point
52
+ # # finally, handle the case where a vector is passed in
53
+ # # we may want it to go to the geographic pipeline _or_ direct materialization
54
+ Base. @propagate_inbounds function UnitSphericalPoint (v:: AbstractVector{T} ) where T
55
+ if length (v) == 3
56
+ UnitSphericalPoint {T} (v[1 ], v[2 ], v[3 ])
57
+ elseif length (v) == 2
58
+ UnitSphereFromGeographic ()(v)
59
+ else
60
+ @boundscheck begin
61
+ throw (ArgumentError ("""
62
+ Passed a vector of length `$(length (v)) ` to the `UnitSphericalPoint` constructor,
63
+ which only accepts vectors of lengths:
64
+ - **3** (assumed to be on the unit sphere)
65
+ - **2** (assumed to be geographic lat/long)
66
+ """ ))
67
+ end
68
+ end
69
+ end
70
+
71
+ Base. show (io:: IO , p:: UnitSphericalPoint ) = print (io, " UnitSphericalPoint($(p. x) , $(p. y) , $(p. z) )" )
72
+
73
+ # ## Interface implementations
74
+
75
+ # StaticArraysCore.jl interface implementation
76
+ Base. setindex (p:: UnitSphericalPoint , args... ) = throw (ArgumentError (" `setindex!` on a UnitSphericalPoint is not permitted as it is static." ))
77
+ StaticArrays. similar_type (:: Type{<: UnitSphericalPoint} , :: Type{Eltype} , :: Size{(3,)} ) where Eltype = UnitSphericalPoint{Eltype}
78
+ # Base math implementation (really just forcing re-wrapping)
79
+ # Base.:(*)(a::UnitSphericalPoint, b::UnitSphericalPoint) = a .* b
80
+ function Base. broadcasted (f, a:: AbstractArray{T} , b:: UnitSphericalPoint ) where {T <: UnitSphericalPoint }
81
+ return Base. broadcasted (f, a, (b,))
82
+ end
83
+ Base. isnan (p:: UnitSphericalPoint ) = any (isnan, p)
84
+ Base. isinf (p:: UnitSphericalPoint ) = any (isinf, p)
85
+ Base. isfinite (p:: UnitSphericalPoint ) = all (isfinite, p)
86
+
87
+
88
+ # GeoInterface implementation
89
+ # # Traits:
90
+ GI. trait (:: UnitSphericalPoint ) = GI. PointTrait ()
91
+ GI. geomtrait (:: UnitSphericalPoint ) = GI. PointTrait ()
92
+ # # Coordinate traits:
93
+ GI. is3d (:: GI.PointTrait , :: UnitSphericalPoint ) = true
94
+ GI. ismeasured (:: GI.PointTrait , :: UnitSphericalPoint ) = false
95
+ # # Accessors:
96
+ GI. ncoord (:: GI.PointTrait , :: UnitSphericalPoint ) = 3
97
+ GI. getcoord (:: GI.PointTrait , p:: UnitSphericalPoint ) = p[i]
98
+ # # Metadata (CRS, extent, etc)
99
+ GI. crs (:: UnitSphericalPoint ) = GFT. ProjString (" +proj=cart +R=1 +type=crs" ) # TODO : make this a full WKT definition
100
+ # TODO : extent is a little tricky - do we do a spherical cap or an Extents.Extent?
101
+
102
+ # ## Spherical distance
103
+ """
104
+ spherical_distance(x::UnitSphericalPoint, y::UnitSphericalPoint)
105
+
106
+ Compute the spherical distance between two points on the unit sphere.
107
+ Returns a `Number`, usually Float64 but that depends on the input type.
108
+
109
+ # Extended help
110
+
111
+ ## Doctests
112
+
113
+ ```jldoctest
114
+ julia> spherical_distance(UnitSphericalPoint(1, 0, 0), UnitSphericalPoint(0, 1, 0))
115
+ 1.5707963267948966
116
+ ```
117
+
118
+ ```jldoctest
119
+ julia> spherical_distance(UnitSphericalPoint(1, 0, 0), UnitSphericalPoint(1, 0, 0))
120
+ 0.0
121
+ ```
122
+ """
123
+ spherical_distance (x:: UnitSphericalPoint , y:: UnitSphericalPoint ) = acos (clamp (x ⋅ y, - 1.0 , 1.0 ))
124
+
125
+ # ## Random points
126
+ Random. rand (rng:: Random.AbstractRNG , :: Random.SamplerType{UnitSphericalPoint} ) = rand (rng, UnitSphericalPoint{Float64})
127
+ function Random. rand (rng:: Random.AbstractRNG , :: Random.SamplerType{UnitSphericalPoint{T}} ) where T <: Number
128
+ θ = 2 π * rand (rng, T)
129
+ ϕ = acos (2 * rand (rng, T) - 1 )
130
+ sinθ, cosθ = sincos (θ)
131
+ sinϕ, cosϕ = sincos (ϕ)
132
+ return UnitSphericalPoint (
133
+ sinϕ * cosθ,
134
+ sinϕ * sinθ,
135
+ cosϕ
136
+ )
137
+ end
138
+
139
+ # ## Tests
140
+
141
+ @testitem " UnitSphericalPoint constructor" begin
142
+ using GeometryOps. UnitSpherical
143
+ import GeoInterface as GI
144
+
145
+ northpole = UnitSphericalPoint {Float64} (1 , 0 , 0 )
146
+ # test that the constructor works for a vector of length 3
147
+ @test UnitSphericalPoint ((1 , 0 , 0 )) == northpole
148
+ @test UnitSphericalPoint (SVector (1 , 0 , 0 )) == northpole
149
+ @test UnitSphericalPoint ([1 , 0 , 0 ]) == northpole
150
+ # test that the constructor works for a tuple of length 2
151
+ # and interprets such a thing as a geographic point
152
+ @test UnitSphericalPoint ((90 , 0 )) == northpole
153
+ @test UnitSphericalPoint ([90 , 0 ]) == northpole
154
+ @test UnitSphericalPoint (GI. Point ((90 , 0 ))) == northpole
155
+
156
+
157
+ end
158
+
159
+ #=
160
+ ```@meta
161
+ CollapsedDocStrings = true
162
+ ```
163
+ =#
0 commit comments