Skip to content

Commit aaa3e83

Browse files
Merge pull request #4 from gregory-halverson/main
aligning `Modland.jl` package with former `MODLAND` sub-module of `STARSDataFusion.jl`
2 parents 1649ca6 + 12806dd commit aaa3e83

File tree

3 files changed

+104
-53
lines changed

3 files changed

+104
-53
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "Modland"
22
uuid = "2a2d4310-a4da-4416-b9e4-81e65019a994"
33
authors = ["Gregory Halverson <[email protected]>"]
4-
version = "0.1.1"
4+
version = "1.0.0"
55

66
[deps]
77
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"

src/Modland.jl

Lines changed: 96 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,167 @@
11
module Modland
22

3+
# MODLAND.jl: Utilities for working with MODIS land grid and projections
4+
# Provides functions to convert between latitude/longitude and MODIS sinusoidal projection,
5+
# and to determine MODIS tile indices for given coordinates or polygons.
6+
7+
8+
# Import required packages for geospatial operations and data handling
39
import ArchGDAL as AG
410
import ArchGDAL
511
import GeoDataFrames as GDF
612
import GeoFormatTypes as GFT
713
using DataFrames
814
using Rasters
9-
using DimensionalData.Dimensions.LookupArrays
1015
import JSON
1116

12-
# boundaries of sinusodial projection
13-
UPPER_LEFT_X_METERS = -20015109.355798
14-
UPPER_LEFT_Y_METERS = 10007554.677899
15-
LOWER_RIGHT_X_METERS = 20015109.355798
16-
LOWER_RIGHT_Y_METERS = -10007554.677899
1717

18-
# size across (width or height) of any equal-area sinusoidal target
18+
# Constants defining the boundaries of the MODIS sinusoidal projection (in meters)
19+
UPPER_LEFT_X_METERS = -20015109.355798 # Westernmost x coordinate
20+
UPPER_LEFT_Y_METERS = 10007554.677899 # Northernmost y coordinate
21+
LOWER_RIGHT_X_METERS = 20015109.355798 # Easternmost x coordinate
22+
LOWER_RIGHT_Y_METERS = -10007554.677899 # Southernmost y coordinate
23+
24+
# Size (width or height) of a MODIS tile in the sinusoidal projection (in meters)
1925
TILE_SIZE_METERS = 1111950.5197665554
2026

21-
# boundaries of MODIS land grid
27+
# Number of rows and columns in the MODIS land grid
2228
TOTAL_ROWS = 18
2329
TOTAL_COLUMNS = 36
2430

31+
# Coordinate reference systems (CRS) for WGS84 and MODIS sinusoidal projection
2532
WGS84 = ProjString("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
2633
SINUSOIDAL = ProjString("+proj=sinu +lon_0=0 +x_0=0 +y_0=0 +a=6371007.181 +b=6371007.181 +units=m +no_defs")
2734

28-
export UPPER_LEFT_X_METERS, UPPER_LEFT_Y_METERS, LOWER_RIGHT_X_METERS, LOWER_RIGHT_Y_METERS, TILE_SIZE_METERS, TOTAL_ROWS, TOTAL_COLUMNS, WGS84, SINUSOIDAL
29-
3035

3136
"""
32-
latlon_to_sinusoidal(lat::Float64, lon::Float64)::Tuple{Float64,Float64}
37+
latlon_to_sinusoidal(lat::Float64, lon::Float64) -> (x::Float64, y::Float64)
3338
34-
Convert latitude and longitude coordinates to sinusoidal projection coordinates.
39+
Convert latitude and longitude (in degrees, WGS84) to MODIS sinusoidal projection coordinates (in meters).
3540
36-
# Arguments
37-
- `lat::Float64`: Latitude in degrees.
38-
- `lon::Float64`: Longitude in degrees.
41+
Arguments:
42+
lat::Float64: Latitude in degrees (-90 to 90)
43+
lon::Float64: Longitude in degrees (-180 to 180)
3944
40-
# Returns
41-
- `Tuple{Float64, Float64}`: Sinusoidal projection coordinates (x, y).
45+
Returns:
46+
Tuple (x, y): Sinusoidal projection coordinates in meters
47+
48+
Throws:
49+
Error if latitude or longitude is out of bounds.
4250
"""
4351
function latlon_to_sinusoidal(lat::Float64, lon::Float64)::Tuple{Float64,Float64}
44-
# Check if latitude is within valid bounds
52+
# Validate latitude
4553
if lat < -90 || lat > 90
4654
error("latitude ($(lat)) out of bounds")
4755
end
48-
49-
# Check if longitude is within valid bounds
56+
# Validate longitude
5057
if lon < -180 || lon > 180
5158
error("longitude ($(lon))) out of bounds")
5259
end
5360

54-
# Create a point in latitude and longitude coordinates
61+
# Create a point in WGS84 and reproject to sinusoidal
5562
point_latlon = AG.createpoint(lon, lat)
56-
# Reproject the point from WGS84 to sinusoidal projection
5763
point_sinusoidal = AG.reproject(point_latlon, WGS84, SINUSOIDAL)
58-
# Extract the x coordinate from the reprojected point
5964
x = AG.getx(point_sinusoidal, 0)
60-
# Extract the y coordinate from the reprojected point
6165
y = AG.gety(point_sinusoidal, 0)
6266

63-
# Return the sinusoidal coordinates as a tuple
6467
return x, y
6568
end
6669

6770
export latlon_to_sinusoidal
6871

72+
6973
"""
70-
sinusoidal_to_modland(x::Float64, y::Float64)::String
74+
sinusoidal_to_MODLAND(x::Float64, y::Float64) -> String
75+
76+
Given sinusoidal projection coordinates (x, y), return the MODIS land tile index (e.g., "h12v04")
77+
that contains the point.
7178
72-
Convert sinusoidal projection coordinates to MODIS land tile indices.
79+
Arguments:
80+
x::Float64: Sinusoidal x coordinate (meters)
81+
y::Float64: Sinusoidal y coordinate (meters)
7382
74-
# Arguments
75-
- `x::Float64`: Sinusoidal x coordinate.
76-
- `y::Float64`: Sinusoidal y coordinate.
83+
Returns:
84+
String: MODIS tile index in the format "hXXvYY"
7785
78-
# Returns
79-
- `String`: MODIS land tile index in the format "hXXvYY".
86+
Throws:
87+
Error if x or y is out of bounds for the MODIS grid.
8088
"""
81-
function sinusoidal_to_modland(x::Float64, y::Float64)::String
82-
# Check if the x coordinate is within the valid bounds of the sinusoidal projection
89+
function sinusoidal_to_MODLAND(x::Float64, y::Float64)::String
90+
# Check if x is within the valid range
8391
if x < UPPER_LEFT_X_METERS || x > LOWER_RIGHT_X_METERS
8492
error("sinusoidal x coordinate ($(x)) out of bounds")
8593
end
86-
87-
# Check if the y coordinate is within the valid bounds of the sinusoidal projection
94+
# Check if y is within the valid range
8895
if y < LOWER_RIGHT_Y_METERS || y > UPPER_LEFT_Y_METERS
8996
error("sinusoidal y ($(y))) coordinate out of bounds")
9097
end
9198

92-
# Calculate the horizontal tile index
99+
# Calculate horizontal (column) and vertical (row) indices
93100
horizontal_index = Int(floor((x - UPPER_LEFT_X_METERS) / TILE_SIZE_METERS))
94-
# Calculate the vertical tile index
95101
vertical_index = Int(floor((-1 * (y + LOWER_RIGHT_Y_METERS)) / TILE_SIZE_METERS))
96102

97-
# Adjust the horizontal index if it is at the boundary
103+
# Clamp indices to valid range if on the edge
98104
if horizontal_index == TOTAL_COLUMNS
99105
horizontal_index -= 1
100106
end
101-
102-
# Adjust the vertical index if it is at the boundary
103107
if vertical_index == TOTAL_ROWS
104108
vertical_index -= 1
105109
end
106110

107-
# Format the tile index as a string in the format "hXXvYY"
111+
# Format as MODIS tile string (e.g., h12v04)
108112
tile = "h$(lpad(horizontal_index, 2, '0'))v$(lpad(vertical_index, 2, '0'))"
113+
return tile
114+
end
115+
116+
export sinusoidal_to_MODLAND
117+
118+
119+
"""
120+
latlon_to_MODLAND(lat, lon) -> String
109121
110-
# Return the formatted tile index
122+
Convert latitude and longitude (in degrees, WGS84) directly to the MODIS land tile index (e.g., "h12v04").
123+
124+
Arguments:
125+
lat: Latitude in degrees
126+
lon: Longitude in degrees
127+
128+
Returns:
129+
String: MODIS tile index in the format "hXXvYY"
130+
"""
131+
function latlon_to_MODLAND(lat, lon)
132+
# Convert lat/lon to sinusoidal coordinates
133+
x, y = latlon_to_sinusoidal(lat, lon)
134+
# Get MODIS tile index for the coordinates
135+
tile = sinusoidal_to_MODLAND(x, y)
111136
return tile
112137
end
113138

114-
export sinusoidal_to_modland
139+
export latlon_to_MODLAND
140+
141+
142+
"""
143+
MODLAND_tiles_in_polygon(poly::ArchGDAL.IGeometry{ArchGDAL.wkbPolygon25D}) -> Set{String}
144+
MODLAND_tiles_in_polygon(poly::AG.IGeometry{AG.wkbPolygon}) -> Set{String}
145+
146+
Given a polygon geometry, return the set of MODIS land tile indices that contain the polygon's vertices.
147+
Note: Only the tiles containing the polygon's vertices are returned, not all tiles intersecting the polygon.
148+
149+
Arguments:
150+
poly: Polygon geometry (ArchGDAL.IGeometry)
115151
116-
end
152+
Returns:
153+
Set{String}: Set of MODIS tile indices (e.g., Set(["h12v04", ...]))
154+
"""
155+
function MODLAND_tiles_in_polygon(poly::ArchGDAL.IGeometry{ArchGDAL.wkbPolygon25D})::Set{String}
156+
# Convert polygon to JSON, extract coordinates, and map to MODIS tiles
157+
Set([latlon_to_MODLAND(lat, lon) for (lon, lat) in JSON.parse(AG.toJSON(poly))["coordinates"][1]])
158+
end
159+
160+
function MODLAND_tiles_in_polygon(poly::AG.IGeometry{AG.wkbPolygon})::Set{String}
161+
# Convert polygon to JSON, extract coordinates, and map to MODIS tiles
162+
Set([latlon_to_MODLAND(lat, lon) for (lon, lat) in JSON.parse(AG.toJSON(poly))["coordinates"][1]])
163+
end
164+
165+
export MODLAND_tiles_in_polygon
166+
167+
end

test/runtests.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ using Modland
1717
end
1818

1919
# Test sinusoidal_to_modland
20-
@testset "sinusoidal_to_modland tests" begin
20+
@testset "sinusoidal_to_MODLAND tests" begin
2121
# Test with valid sinusoidal coordinates
22-
@test sinusoidal_to_modland(0.0, 0.0) == "h18v09"
23-
@test sinusoidal_to_modland(3.538204887918666e6, 5.003777338949354e6) == "h21v04"
22+
@test sinusoidal_to_MODLAND(0.0, 0.0) == "h18v09"
23+
@test sinusoidal_to_MODLAND(3.538204887918666e6, 5.003777338949354e6) == "h21v04"
2424

2525
# Test with out-of-bounds sinusoidal coordinates
26-
@test_throws ErrorException sinusoidal_to_modland(UPPER_LEFT_X_METERS - 1, 0.0)
27-
@test_throws ErrorException sinusoidal_to_modland(LOWER_RIGHT_X_METERS + 1, 0.0)
28-
@test_throws ErrorException sinusoidal_to_modland(0.0, LOWER_RIGHT_Y_METERS - 1)
29-
@test_throws ErrorException sinusoidal_to_modland(0.0, UPPER_LEFT_Y_METERS + 1)
26+
@test_throws ErrorException sinusoidal_to_MODLAND(Modland.UPPER_LEFT_X_METERS - 1, 0.0)
27+
@test_throws ErrorException sinusoidal_to_MODLAND(Modland.LOWER_RIGHT_X_METERS + 1, 0.0)
28+
@test_throws ErrorException sinusoidal_to_MODLAND(0.0, Modland.LOWER_RIGHT_Y_METERS - 1)
29+
@test_throws ErrorException sinusoidal_to_MODLAND(0.0, Modland.UPPER_LEFT_Y_METERS + 1)
3030
end

0 commit comments

Comments
 (0)