Skip to content

Commit 5a92a9d

Browse files
authored
feat: API for coordinate system (#10)
1 parent 95ae76a commit 5a92a9d

File tree

7 files changed

+128
-49
lines changed

7 files changed

+128
-49
lines changed

Project.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ version = "0.1.9"
66
[deps]
77
Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
88
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
9-
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
109

1110
[compat]
1211
Accessors = "0.1"
1312
Dates = "1"
14-
StaticArraysCore = "1.4"
1513
julia = "1.10"

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ makedocs(
99
modules = [SpaceDataModel],
1010
pages = [
1111
"Home" => "index.md",
12+
"Interface" => "interface.md",
1213
"API" => "api.md",
1314
],
1415
checkdocs = :exports,

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ See [Data Model and Project Module - SPEDAS.jl](https://beforerr.github.io/SPEDA
3636

3737
- [SPASE Model](https://spase-group.org/data/model/index.html)
3838
- [HAPI Data Access Specification](https://github.com/hapi-server/data-specification)
39-
- [CommonDataModel.jl](https://github.com/JuliaGeo/CommonDataModel.jl)
39+
- [CommonDataModel.jl](https://github.com/JuliaGeo/CommonDataModel.jl) contains abstracts type definition for loading and manipulating GRIB, NetCDF, geoTiff and Zarr files.
4040
- [NetCDF Data Model](https://docs.unidata.ucar.edu/netcdf-c/current/netcdf_data_model.html)

docs/src/interface.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Interface
2+
3+
SpaceDataModel.jl provides a set of abstractions and generic functions that can be extended by users to create custom implementations. This page documents these interfaces and provides examples of how to use them.
4+
5+
## References
6+
7+
- [DataAPI.jl](https://github.com/JuliaData/DataAPI.jl): A data-focused namespace for packages to share functions
8+
9+
## Coordinate Systems
10+
11+
The package exports three key components for coordinate system handling:
12+
13+
- [`AbstractCoordinateSystem`](@ref): Base abstract type for all coordinate system implementations
14+
- [`AbstractCoordinateVector`](@ref): Base abstract type to represent coordinates in a coordinate systems
15+
- [`getcsys`](@ref): Function to retrieve the coordinate system from an object
16+
17+
18+
There are two main approaches to implementing coordinate vectors and their associated systems:
19+
20+
### Approach 1: Explicit Coordinate System Field
21+
22+
Store the coordinate system directly as a field in the vector type:
23+
24+
```julia
25+
# Define a coordinate system
26+
struct GEO <: AbstractCoordinateSystem end
27+
28+
# Define a vector with an explicit coordinate system field
29+
struct CoordinateVector{D, T} <: AbstractCoordinateVector
30+
data::D
31+
csys::T
32+
end
33+
34+
# Implementation of getcsys simply returns the stored field
35+
getcsys(x::CoordinateVector) = x.csys
36+
```
37+
38+
### Approach 2: Implicit Coordinate System
39+
40+
Associate a specific coordinate system with a vector type:
41+
42+
```julia
43+
# Define a coordinate system
44+
struct GEO <: AbstractCoordinateSystem end
45+
46+
# Define a vector type specific to a coordinate system
47+
struct GEOVector{D} <: AbstractCoordinateVector
48+
data::D
49+
end
50+
51+
# Implementation of getcsys returns the appropriate system
52+
getcsys(x::GEOVector) = GEO()
53+
```
54+
55+
Both approaches are highly efficient and provide equivalent performance due to Julia's type inference system.
56+
57+
### Elsewhere
58+
59+
- [CoordRefSystems.jl](https://github.com/JuliaEarth/CoordRefSystems.jl) provides conversions between Coordinate Reference Systems (CRS) for cartography use cases.
60+
- [Geodesy.jl](https://github.com/JuliaGeo/Geodesy.jl) for working with points in various world and local coordinate systems.
61+
- [WCS.jl](https://juliaastro.org/WCS/stable/) : Astronomical World Coordinate System library

src/coord.jl

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,37 @@
1-
module CoordinateSystem
2-
using StaticArraysCore
3-
import Base: size, getindex, cconvert, unsafe_convert, String
1+
import Base: String
42

5-
export AbstractCoordinateSystem, CoordinateVector, coord
3+
export AbstractCoordinateSystem, AbstractCoordinateVector, getcsys
64

5+
"""
6+
AbstractCoordinateSystem
7+
8+
Base abstract type for all coordinate system implementations.
9+
"""
710
abstract type AbstractCoordinateSystem end
811

9-
struct CoordinateVector{T,C} <: FieldVector{3,T}
10-
x::T
11-
y::T
12-
z::T
13-
sym::C
14-
end
12+
"""
13+
AbstractCoordinateVector
1514
16-
for sys in (:GDZ, :GEO, :GSM, :GSE, :SM, :GEI, :MAG, :SPH, :RLL, :HEE, :HAE, :HEEQ, :J2000)
17-
@eval struct $sys <: AbstractCoordinateSystem end
18-
@eval $sys(x, y, z) = CoordinateVector(x, y, z, $sys())
19-
@eval $sys(x) = CoordinateVector(x..., $sys())
20-
@eval export $sys
21-
end
15+
Base abstract type to represent coordinates in a coordinate systems.
16+
"""
17+
abstract type AbstractCoordinateVector end
2218

23-
@doc """Geocentric Solar Magnetospheric (GSM)\n\nX points sunward from Earth's center. The X-Z plane is defined to contain Earth's dipole axis (positive North).
24-
""" GSM
19+
"""
20+
getcsys(x)
2521
26-
coord(v::CoordinateVector) = v.sym
27-
Base.String(::Type{S}) where {S<:AbstractCoordinateSystem} = String(nameof(S))
28-
Base.String(::S) where {S<:AbstractCoordinateSystem} = T(S)
22+
Get the coordinate system of `x`.
2923
30-
# https://github.com/JuliaArrays/StaticArrays.jl/blob/master/src/FieldArray.jl
31-
Base.size(::CoordinateVector) = (3,)
32-
Base.@propagate_inbounds Base.getindex(a::CoordinateVector, i::Int) = getfield(a, i)
24+
If `x` is a instance of `AbstractCoordinateSystem`, return `x` itself.
25+
If `x` is a type of `AbstractCoordinateSystem`, return an instance of the coordinate system, i.e. `x()`.
26+
27+
This is a generic function, packages should extend it for their own types.
28+
"""
29+
function getcsys(v)
30+
return hasfield(typeof(v), :csys) ?
31+
getfield(v, :csys) : nothing
32+
end
3333

34-
# C interface
35-
Base.cconvert(::Type{<:Ptr}, a::CoordinateVector) = Base.RefValue(a)
36-
Base.unsafe_convert(::Type{Ptr{T}}, m::Base.RefValue{FA}) where {T,FA<:CoordinateVector{T}} =
37-
Ptr{T}(Base.unsafe_convert(Ptr{FA}, m))
34+
getcsys(::Type{S}) where {S <: AbstractCoordinateSystem} = S()
35+
getcsys(x::AbstractCoordinateSystem) = x
3836

39-
end
37+
Base.String(::Type{S}) where {S <: AbstractCoordinateSystem} = String(nameof(S))

test/coord.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@testitem "CoordinateSystem" begin
2+
# We demonstrate two ways to implement a CoordinateVector
3+
import SpaceDataModel: getcsys
4+
5+
struct GEO <: AbstractCoordinateSystem end
6+
struct CoordinateVector{D, T} <: AbstractCoordinateVector
7+
data::D
8+
csys::T
9+
end
10+
11+
struct GEOVector{D} <: AbstractCoordinateVector
12+
data::D
13+
end
14+
getcsys(x::GEOVector) = GEO()
15+
16+
data = (1.0, 2.0, 3.0)
17+
18+
x1 = CoordinateVector(data, GEO())
19+
x2 = GEOVector(data)
20+
21+
# we can see their memory usages are the same
22+
@test Base.sizeof(x1) == Base.sizeof(x2)
23+
@test Base.summarysize(x1) == Base.summarysize(x2)
24+
@test isbits(x1)
25+
@test isbits(x2)
26+
27+
@test isnothing(getcsys(data))
28+
@test getcsys(x1) == GEO()
29+
@test getcsys(x2) == GEO()
30+
@test getcsys(GEO) == GEO()
31+
@test getcsys(GEO()) == GEO()
32+
@test String(GEO) == "GEO"
33+
end

test/runtests.jl

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ end
1414

1515
@testitem "Project" begin
1616
using SpaceDataModel: meta, abbr, name
17-
project = Project(name="Project Name", abbreviation="Proj", links="links")
18-
instrument = Instrument(name="Instrument Name")
19-
dataset = DataSet(name="Dataset Name")
17+
project = Project(name = "Project Name", abbreviation = "Proj", links = "links")
18+
instrument = Instrument(name = "Instrument Name")
19+
dataset = DataSet(name = "Dataset Name")
2020
push!(project, instrument, dataset)
2121
push!(instrument, dataset)
2222
@test name(project) == "Project Name"
@@ -43,18 +43,6 @@ end
4343
@test dataset2[2] (var1, var2)
4444
end
4545

46-
@testitem "CoordinateSystem" begin
47-
using SpaceDataModel.CoordinateSystem
48-
using SpaceDataModel.CoordinateSystem: coord
49-
using StaticArrays
50-
using LinearAlgebra
51-
x = GEO(3, 4, 0)
52-
@test x == GEO([3, 4, 0])
53-
@test coord(x) == GEO()
54-
@test norm(x) == 5
55-
@test_nowarn display(x)
56-
end
57-
5846
@testitem "parse_datetime" begin
5947
using SpaceDataModel: parse_datetime
6048
using SpaceDataModel.Dates: DateTime
@@ -74,7 +62,7 @@ end
7462
"1989-01-01T00:00:00.", "1989-01-01T00:00:00.0",
7563
"1989-001T00:00:00.0", "1989-01-01T00:00:00.00",
7664
"1989-001T00:00:00.00", "1989-01-01T00:00:00.000",
77-
"1989-001T00:00:00.000"
65+
"1989-001T00:00:00.000",
7866
]
7967

8068
expected = DateTime(1989, 1, 1)
@@ -94,4 +82,4 @@ end
9482
using JET
9583
println(@report_opt ignored_modules = (Base,) SpaceDataModel.workload())
9684
println(@report_call ignored_modules = (Base,) SpaceDataModel.workload())
97-
end
85+
end

0 commit comments

Comments
 (0)