ECOv003-L2T-STARS 1.0.1__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ECOv003_L2T_STARS/BRDF/BRDF.py +57 -0
- ECOv003_L2T_STARS/BRDF/SZA.py +65 -0
- ECOv003_L2T_STARS/BRDF/__init__.py +1 -0
- ECOv003_L2T_STARS/BRDF/statistical_radiative_transport.txt +90 -0
- ECOv003_L2T_STARS/BRDF/version.txt +1 -0
- ECOv003_L2T_STARS/ECOv003_DL.py +527 -0
- ECOv003_L2T_STARS/ECOv003_DL.xml +47 -0
- ECOv003_L2T_STARS/ECOv003_L2T_STARS.py +169 -0
- ECOv003_L2T_STARS/ECOv003_L2T_STARS.xml +47 -0
- ECOv003_L2T_STARS/L2TSTARSConfig.py +190 -0
- ECOv003_L2T_STARS/L2T_STARS.py +503 -0
- ECOv003_L2T_STARS/LPDAAC/LPDAACDataPool.py +444 -0
- ECOv003_L2T_STARS/LPDAAC/__init__.py +9 -0
- ECOv003_L2T_STARS/LPDAAC/version.txt +1 -0
- ECOv003_L2T_STARS/Manifest.toml +2332 -0
- ECOv003_L2T_STARS/Project.toml +14 -0
- ECOv003_L2T_STARS/VIIRS/VIIRSDataPool.py +294 -0
- ECOv003_L2T_STARS/VIIRS/VIIRSDownloader.py +26 -0
- ECOv003_L2T_STARS/VIIRS/VIIRS_CMR_LOGIN.py +36 -0
- ECOv003_L2T_STARS/VIIRS/VNP09GA.py +1278 -0
- ECOv003_L2T_STARS/VIIRS/VNP43IA4.py +288 -0
- ECOv003_L2T_STARS/VIIRS/VNP43MA3.py +323 -0
- ECOv003_L2T_STARS/VIIRS/__init__.py +9 -0
- ECOv003_L2T_STARS/VIIRS/version.txt +1 -0
- ECOv003_L2T_STARS/VNP43NRT/VNP43NRT.py +863 -0
- ECOv003_L2T_STARS/VNP43NRT/__init__.py +1 -0
- ECOv003_L2T_STARS/VNP43NRT/process_VNP43NRT.jl +169 -0
- ECOv003_L2T_STARS/VNP43NRT/version.txt +1 -0
- ECOv003_L2T_STARS/VNP43NRT_jl/Manifest.toml +995 -0
- ECOv003_L2T_STARS/VNP43NRT_jl/Project.toml +15 -0
- ECOv003_L2T_STARS/VNP43NRT_jl/__init__.py +0 -0
- ECOv003_L2T_STARS/VNP43NRT_jl/instantiate.jl +25 -0
- ECOv003_L2T_STARS/VNP43NRT_jl/instantiate.py +13 -0
- ECOv003_L2T_STARS/VNP43NRT_jl/src/VNP43NRT.jl +411 -0
- ECOv003_L2T_STARS/VNP43NRT_jl/src/__init__.py +0 -0
- ECOv003_L2T_STARS/__init__.py +3 -0
- ECOv003_L2T_STARS/calibrate_fine_to_coarse.py +60 -0
- ECOv003_L2T_STARS/constants.py +38 -0
- ECOv003_L2T_STARS/daterange/__init__.py +1 -0
- ECOv003_L2T_STARS/daterange/daterange.py +35 -0
- ECOv003_L2T_STARS/generate_L2T_STARS_runconfig.py +249 -0
- ECOv003_L2T_STARS/generate_NDVI_coarse_directory.py +21 -0
- ECOv003_L2T_STARS/generate_NDVI_coarse_image.py +30 -0
- ECOv003_L2T_STARS/generate_NDVI_fine_directory.py +14 -0
- ECOv003_L2T_STARS/generate_NDVI_fine_image.py +28 -0
- ECOv003_L2T_STARS/generate_STARS_inputs.py +231 -0
- ECOv003_L2T_STARS/generate_albedo_coarse_directory.py +18 -0
- ECOv003_L2T_STARS/generate_albedo_coarse_image.py +30 -0
- ECOv003_L2T_STARS/generate_albedo_fine_directory.py +17 -0
- ECOv003_L2T_STARS/generate_albedo_fine_image.py +30 -0
- ECOv003_L2T_STARS/generate_filename.py +37 -0
- ECOv003_L2T_STARS/generate_input_staging_directory.py +23 -0
- ECOv003_L2T_STARS/generate_model_state_tile_date_directory.py +28 -0
- ECOv003_L2T_STARS/generate_output_directory.py +28 -0
- ECOv003_L2T_STARS/install_STARS_jl.py +43 -0
- ECOv003_L2T_STARS/instantiate_STARS_jl.py +38 -0
- ECOv003_L2T_STARS/load_prior.py +250 -0
- ECOv003_L2T_STARS/prior.py +56 -0
- ECOv003_L2T_STARS/process_ECOSTRESS_data_fusion_distributed_bias.jl +420 -0
- ECOv003_L2T_STARS/process_STARS_product.py +507 -0
- ECOv003_L2T_STARS/process_julia_data_fusion.py +110 -0
- ECOv003_L2T_STARS/retrieve_STARS_sources.py +101 -0
- ECOv003_L2T_STARS/runconfig.py +70 -0
- ECOv003_L2T_STARS/timer/__init__.py +1 -0
- ECOv003_L2T_STARS/timer/timer.py +77 -0
- ECOv003_L2T_STARS/version.py +8 -0
- ECOv003_L2T_STARS/version.txt +1 -0
- {ECOv003_L2T_STARS-1.0.1.dist-info → ecov003_l2t_stars-1.2.0.dist-info}/METADATA +31 -24
- ecov003_l2t_stars-1.2.0.dist-info/RECORD +73 -0
- {ECOv003_L2T_STARS-1.0.1.dist-info → ecov003_l2t_stars-1.2.0.dist-info}/WHEEL +1 -1
- ecov003_l2t_stars-1.2.0.dist-info/entry_points.txt +3 -0
- ecov003_l2t_stars-1.2.0.dist-info/top_level.txt +1 -0
- ECOv003_L2T_STARS-1.0.1.dist-info/RECORD +0 -5
- ECOv003_L2T_STARS-1.0.1.dist-info/top_level.txt +0 -1
- {ECOv003_L2T_STARS-1.0.1.dist-info → ecov003_l2t_stars-1.2.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
name = "VNP43NRT"
|
2
|
+
uuid = "ebab2807-db99-43b5-90ec-49665e98c321"
|
3
|
+
authors = ["Gregory Halverson <gregory.h.halverson@jpl.nasa.gov>"]
|
4
|
+
version = "0.1.0"
|
5
|
+
|
6
|
+
[deps]
|
7
|
+
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
|
8
|
+
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
|
9
|
+
DimensionalData = "0703355e-b756-11e9-17c0-8b28908087d0"
|
10
|
+
GDAL = "add2ef01-049f-52c4-9ee2-e494f65e021a"
|
11
|
+
Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
|
12
|
+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
13
|
+
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
|
14
|
+
Rasters = "a3a2b9e3-a471-40c9-b274-f788e487c689"
|
15
|
+
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
using Pkg
|
2
|
+
Pkg.activate(".")
|
3
|
+
|
4
|
+
if haskey(ENV, "CONDA_PREFIX")
|
5
|
+
try
|
6
|
+
run(pipeline(`curl "https://raw.githubusercontent.com/JuliaLang/MbedTLS.jl/master/src/cacert.pem"`, stdout="$CONDA_PREFIX/share/julia/cert.pem"))
|
7
|
+
catch
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
try
|
12
|
+
run(pipeline(`curl "https://raw.githubusercontent.com/JuliaLang/MbedTLS.jl/master/src/cacert.pem"`, stdout="/opt/conda/share/julia/cert.pem"))
|
13
|
+
catch
|
14
|
+
end
|
15
|
+
|
16
|
+
Pkg.add("IJulia")
|
17
|
+
Pkg.build("IJulia")
|
18
|
+
Pkg.add("HDF5")
|
19
|
+
Pkg.build("HDF5")
|
20
|
+
Pkg.add("CoordinateTransformations")
|
21
|
+
Pkg.add(Pkg.PackageSpec(;name="DimensionalData",version="0.24.7"))
|
22
|
+
Pkg.add(Pkg.PackageSpec(;name="Rasters",version="0.5.3"))
|
23
|
+
Pkg.add(Pkg.PackageSpec(;name="GDAL",version="1.5.1"))
|
24
|
+
Pkg.instantiate()
|
25
|
+
Pkg.precompile()
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import subprocess
|
2
|
+
from os.path import abspath, dirname
|
3
|
+
|
4
|
+
directory = abspath(dirname(__file__))
|
5
|
+
|
6
|
+
def main():
|
7
|
+
command = f'cd "{directory}" && julia instantiate.jl'
|
8
|
+
print(command)
|
9
|
+
# system(command)
|
10
|
+
subprocess.run(command, shell=True)
|
11
|
+
|
12
|
+
if __name__ == "__main":
|
13
|
+
main()
|
@@ -0,0 +1,411 @@
|
|
1
|
+
module VNP43NRT
|
2
|
+
|
3
|
+
using Dates
|
4
|
+
using LinearAlgebra
|
5
|
+
using Statistics
|
6
|
+
using Rasters
|
7
|
+
using DimensionalData.Dimensions.LookupArrays
|
8
|
+
using ProgressMeter
|
9
|
+
|
10
|
+
SINUSOIDAL_CRS = WellKnownText("PROJCS[\"unknown\",GEOGCS[\"unknown\",DATUM[\"unknown\",SPHEROID[\"unknown\",6371007.181,0]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]],PROJECTION[\"Sinusoidal\"],PARAMETER[\"longitude_of_center\",0],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]")
|
11
|
+
|
12
|
+
function sinusoidal_tile_dims(h::Int, v::Int, tile_width_cells::Int)::Tuple{X,Y}
|
13
|
+
# boundaries of sinusodial projection
|
14
|
+
GLOBE_UPPER_LEFT_X = -20015109.355798
|
15
|
+
GLOBE_UPPER_LEFT_Y = 10007554.677899
|
16
|
+
GLOBE_LOWER_RIGHT_X = 20015109.355798
|
17
|
+
GLOBE_LOWER_RIGHT_Y = -10007554.677899
|
18
|
+
|
19
|
+
# size across (width or height) of any equal-area sinusoidal tile
|
20
|
+
TILE_SIZE = 1111950.5197665554
|
21
|
+
|
22
|
+
# rows and columns of sinusoidal tile grid
|
23
|
+
TOTAL_ROWS = 18
|
24
|
+
TOTAL_COLUMNS = 36
|
25
|
+
|
26
|
+
cell_size = TILE_SIZE / tile_width_cells
|
27
|
+
tile_left_x = GLOBE_UPPER_LEFT_X + h * TILE_SIZE
|
28
|
+
tile_right_x = GLOBE_UPPER_LEFT_X + (h + 1) * TILE_SIZE - cell_size
|
29
|
+
tile_upper_y = GLOBE_LOWER_RIGHT_Y + (TOTAL_ROWS - v) * TILE_SIZE - cell_size
|
30
|
+
tile_lower_y = GLOBE_LOWER_RIGHT_Y + (TOTAL_ROWS - 1 - v) * TILE_SIZE
|
31
|
+
sampling = Intervals(Start())
|
32
|
+
x_dim = X(Projected(LinRange(tile_left_x, tile_right_x, tile_width_cells), order=ForwardOrdered(), span=Regular(cell_size), sampling=sampling, crs=SINUSOIDAL_CRS))
|
33
|
+
y_dim = Y(Projected(LinRange(tile_upper_y, tile_lower_y, tile_width_cells), order=ReverseOrdered(), span=Regular(-cell_size), sampling=sampling, crs=SINUSOIDAL_CRS))
|
34
|
+
dims = (x_dim, y_dim)
|
35
|
+
|
36
|
+
return dims
|
37
|
+
end
|
38
|
+
|
39
|
+
export sinusoidal_tile_dims
|
40
|
+
|
41
|
+
struct BRDFParameters
|
42
|
+
brdf::AbstractMatrix
|
43
|
+
se::AbstractVector
|
44
|
+
R2::AbstractVector
|
45
|
+
uncert::AbstractArray{Float64,3}
|
46
|
+
end
|
47
|
+
|
48
|
+
function zenith_from_solarnoon_time(tm::Float64, lat::Float64, lon::Float64)
|
49
|
+
rad = π/180.0
|
50
|
+
|
51
|
+
Jd = tm/86400.0 + 2440587.5
|
52
|
+
Jc = (Jd - 2451545.0)/36525.0
|
53
|
+
L0 = mod((280.46646 + Jc * (36000.76983 + 0.0003032 * Jc)),360)
|
54
|
+
M = 357.52911 + Jc * (35999.05029 - 0.0001537 * Jc)
|
55
|
+
e = 0.016708634 - Jc * (4.2037e-05 + 1.267e-07 * Jc)
|
56
|
+
eqctr = sin(rad * M) * (1.914602 - Jc * (0.004817 + 1.4e-05 *
|
57
|
+
Jc)) + sin(rad * 2.0 * M) * (0.019993 - 0.000101 * Jc) +
|
58
|
+
sin(rad * 3.0 * M) * 0.000289
|
59
|
+
lambda0 = L0 + eqctr
|
60
|
+
omega = 125.04 - 1934.136 * Jc
|
61
|
+
lambda = lambda0 - 0.00569 - 0.00478 * sin(rad * omega)
|
62
|
+
seconds = 21.448 - Jc * (46.815 + Jc * (0.00059 - Jc * (0.001813)))
|
63
|
+
obliq0 = 23.0 + (26.0 + (seconds/60.0))/60.0
|
64
|
+
obliq = obliq0 + 0.00256 * cos(rad * omega)
|
65
|
+
y = tan(rad * obliq/2)^2
|
66
|
+
eqnTime = 4/rad * (y*sin(rad*2*L0) - 2*e*sin(rad*M) +
|
67
|
+
4*e*y*sin(rad*M)*cos(rad*2*L0) - y^2/2*sin(rad*4*L0) -
|
68
|
+
e^2*1.25*sin(rad*2*M))
|
69
|
+
solarDec = asin(sin(rad*obliq)*sin(rad*lambda))
|
70
|
+
sinSolarDec = sin(solarDec)
|
71
|
+
cosSolarDec = cos(solarDec)
|
72
|
+
solarTime = (mod(Jd-1/2,1)*1440+eqnTime)/4
|
73
|
+
hourAngle = solarTime+lon-180
|
74
|
+
cosZenith = sin(rad*lat)*sinSolarDec+cos(rad*lat)*cosSolarDec*cos(rad*hourAngle)
|
75
|
+
|
76
|
+
if cosZenith < -1
|
77
|
+
cosZenith=-1
|
78
|
+
end
|
79
|
+
|
80
|
+
if cosZenith > 1
|
81
|
+
cosZenith=1
|
82
|
+
end
|
83
|
+
|
84
|
+
return acos(cosZenith)/rad
|
85
|
+
end
|
86
|
+
|
87
|
+
function zenith_from_solarnoon_vec(tm::Vector{Float64}, lat::Vector{Float64}, lon::Vector{Float64})
|
88
|
+
n = length(tm)
|
89
|
+
szn = Vector{Float64}(undef, n)
|
90
|
+
|
91
|
+
for i in 1:n
|
92
|
+
szn[i] = zenith_from_solarnoon_time(tm[i], lat[i], lon[i])
|
93
|
+
end
|
94
|
+
|
95
|
+
return szn
|
96
|
+
end
|
97
|
+
|
98
|
+
function Kvol(sz::Matrix, vz::Matrix, rz::Matrix)::Matrix
|
99
|
+
sc = π / 180
|
100
|
+
eps = acos.(cos.(sc .* sz) .* cos.(sc .* vz) .+ sin.(sc .* sz) .* sin.(sc .* vz) .* cos.(sc .* rz))
|
101
|
+
Kvol = 1 ./ (cos.(sc .* sz) .+ cos.(sc .* vz)) .* ((π / 2 .- eps) .* cos.(eps) .+ sin.(eps)) .- π / 4
|
102
|
+
|
103
|
+
return Kvol
|
104
|
+
end
|
105
|
+
|
106
|
+
function Kvol_vec(sz::AbstractVector, vz::AbstractVector, rz::AbstractVector)
|
107
|
+
sc = pi/180
|
108
|
+
eps = acos.(cos.(sc .* sz) .* cos.(sc .* vz) .+ sin.(sc .* sz) .* sin.(sc .* vz) .* cos.(sc .* rz))
|
109
|
+
Kvol_vec = 1 ./ (cos.(sc .* sz) .+ cos.(sc .* vz)) .* ((pi/2 .- eps) .* cos.(eps) .+ sin.(eps)) .- pi/4
|
110
|
+
|
111
|
+
return Kvol_vec
|
112
|
+
end
|
113
|
+
|
114
|
+
function Kvol_sc(sz::Real, vz::Real, rz::Real)
|
115
|
+
sc = pi/180
|
116
|
+
eps = acos(cos(sc*sz)*cos(sc*vz) + sin(sc*sz)*sin(sc*vz)*cos(sc*rz))
|
117
|
+
Kvol_sc = 1/(cos(sc*sz) + cos(sc*vz))*((pi/2-eps)*cos(eps) + sin(eps)) - pi/4
|
118
|
+
|
119
|
+
return Kvol_sc
|
120
|
+
end
|
121
|
+
|
122
|
+
function Kgeo_sc(sz::Real, vz::Real, rz::Real)
|
123
|
+
sc = pi/180
|
124
|
+
eps = acos(cos(sc*sz)*cos(sc*vz) + sin(sc*sz)*sin(sc*vz)*cos(sc*rz))
|
125
|
+
D = sqrt(tan(sc*sz)^2 + tan(sc*vz)^2 - 2*tan(sc*sz)*tan(sc*vz)*cos(sc*rz))
|
126
|
+
cost = 2*sqrt(D^2 + (tan(sc*sz)*tan(sc*vz)*sin(sc*rz))^2)/(1/cos(sc*sz) + 1/cos(sc*vz))
|
127
|
+
cost = clamp(cost, -1.0, 1.0)
|
128
|
+
t = acos(cost)
|
129
|
+
O = 1/pi*(t-sin(t)*cos(t))*(1/cos(sc*sz) + 1/cos(sc*vz))
|
130
|
+
O = max(O, 0)
|
131
|
+
Kgeo_sc = O - 1/cos(sc*sz) - 1/cos(sc*vz) + 0.5*(1+cos(eps))/cos(sc*sz)/cos(sc*vz)
|
132
|
+
|
133
|
+
return Kgeo_sc
|
134
|
+
end
|
135
|
+
|
136
|
+
function Kgeo(sz::AbstractMatrix, vz::AbstractMatrix, rz::AbstractMatrix)
|
137
|
+
n, t = size(sz)
|
138
|
+
Kg = zeros(n, t)
|
139
|
+
for i in 1:n
|
140
|
+
for j in 1:t
|
141
|
+
Kg[i,j] = Kgeo_sc(sz[i,j], vz[i,j], rz[i,j])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
return Kg
|
145
|
+
end
|
146
|
+
|
147
|
+
function Kgeo_vec(sz::AbstractVector, vz::AbstractVector, rz::AbstractVector)
|
148
|
+
t = length(vz)
|
149
|
+
Kg = zeros(t)
|
150
|
+
|
151
|
+
for j in 1:t
|
152
|
+
Kg[j] = Kgeo_sc(sz[j], vz[j], rz[j])
|
153
|
+
end
|
154
|
+
|
155
|
+
return Kg
|
156
|
+
end
|
157
|
+
|
158
|
+
function NRT_BRDF(Y::AbstractMatrix, kv::AbstractMatrix, kg::AbstractMatrix, weighted::Bool, scale::Real)
|
159
|
+
n, p = size(Y)
|
160
|
+
brdf = fill(NaN, n, 3)
|
161
|
+
R2 = fill(NaN, n)
|
162
|
+
se = fill(NaN, n)
|
163
|
+
uncert = fill(NaN, 3, 3, n)
|
164
|
+
|
165
|
+
x = ones(3, p)
|
166
|
+
for i in 1:n
|
167
|
+
yt = Y[i,:]
|
168
|
+
non_missing = findall(isfinite.(yt))
|
169
|
+
nt = length(non_missing)
|
170
|
+
|
171
|
+
if nt < 7
|
172
|
+
continue
|
173
|
+
else
|
174
|
+
x[2,:] = kv[i,:]
|
175
|
+
x[3,:] = kg[i,:]
|
176
|
+
|
177
|
+
xt = x[:,non_missing]
|
178
|
+
ytt = yt[non_missing]
|
179
|
+
|
180
|
+
Si = inv(xt * xt')
|
181
|
+
brdf[i,:] = (Si * xt * ytt)'
|
182
|
+
yp = (brdf[i,:] * xt)'
|
183
|
+
uncert[:,:,i] = Si
|
184
|
+
se[i] = sqrt(sum((ytt - yp).^2)/(nt-3))
|
185
|
+
R2[i] = 1 - se[i]^2 * (nt-3) / var(ytt; corrected=true) / nt
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
return BRDFParameters(brdf, se, R2, uncert)
|
190
|
+
end
|
191
|
+
|
192
|
+
function NRT_BRDF_albedo(Y::AbstractMatrix, sz::AbstractMatrix, vz::AbstractMatrix, rz::AbstractMatrix, soz_noon::AbstractVector, weighted::Bool, scale::Real)
|
193
|
+
# RossThick constant
|
194
|
+
g0vol = -0.007574
|
195
|
+
g1vol = -0.070987
|
196
|
+
g2vol = 0.307588
|
197
|
+
|
198
|
+
# LiSparseR constant
|
199
|
+
g0geo = -1.284909
|
200
|
+
g1geo = -0.166314
|
201
|
+
g2geo = 0.041840
|
202
|
+
|
203
|
+
gwsa = [1.0, 0.189184, -1.377622]
|
204
|
+
gbsa = [1.0, 0.0, 0.0]
|
205
|
+
|
206
|
+
n, p = size(Y)
|
207
|
+
@info "reflectance n: $(n) p: $(p)"
|
208
|
+
results = fill(NaN, n, 7) # (wsa, bsa, wsa_se, bsa_se, rmse, R2, nt)
|
209
|
+
@info "results shape rows: $(size(results)[1]) cols: $(size(results)[2])"
|
210
|
+
|
211
|
+
x = ones(3, p)
|
212
|
+
for i in 1:n
|
213
|
+
yt = Y[i,:]
|
214
|
+
non_missing = findall(isfinite.(yt))
|
215
|
+
nt = length(non_missing)
|
216
|
+
|
217
|
+
if nt < 7
|
218
|
+
continue
|
219
|
+
else
|
220
|
+
sznrad = soz_noon[i] * pi/180
|
221
|
+
gbsa[2] = g0vol + g1vol * sznrad^2 + g2vol * sznrad^3
|
222
|
+
gbsa[3] = g0geo + g1geo * sznrad^2 + g2geo * sznrad^3
|
223
|
+
|
224
|
+
x[2,:] = Kvol_vec(sz[i,:], vz[i,:], rz[i,:])
|
225
|
+
x[3,:] = Kgeo_vec(sz[i,:], vz[i,:], rz[i,:])
|
226
|
+
|
227
|
+
xt = x[:,non_missing]
|
228
|
+
ytt = yt[non_missing]
|
229
|
+
|
230
|
+
if weighted
|
231
|
+
xx = exp.(-0.5 .* range(p-1, stop=0; length=p) ./ scale)
|
232
|
+
xxt = xx[non_missing]
|
233
|
+
|
234
|
+
SSi = Diagonal(xxt)
|
235
|
+
Si = inv(xt * SSi * xt')
|
236
|
+
brdf = Si * xt * SSi * ytt
|
237
|
+
yp = (brdf' * xt)'
|
238
|
+
se = sqrt(sum(((ytt - yp) .* xxt).^2)/(nt-3))
|
239
|
+
else
|
240
|
+
Si = inv(xt * xt')
|
241
|
+
brdf = Si * xt * ytt
|
242
|
+
yp = (brdf' * xt)'
|
243
|
+
se = sqrt(sum((ytt - yp).^2)/(nt-3))
|
244
|
+
end
|
245
|
+
|
246
|
+
results[i,1] = dot(gwsa, brdf) # wsa
|
247
|
+
results[i,2] = dot(gbsa, brdf) # bsa
|
248
|
+
|
249
|
+
results[i,3] = se * sqrt(dot(gwsa, Si * gwsa)) # wsa se
|
250
|
+
results[i,4] = se * sqrt(dot(gbsa, Si * gbsa)) # bsa se
|
251
|
+
|
252
|
+
results[i,5] = se # brdf rmse
|
253
|
+
results[i,6] = 1 - se^2 * (nt-3) / var(ytt; corrected=true) / nt # brdf R2
|
254
|
+
results[i,7] = nt # number of obs for brdf estimation
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
return results
|
259
|
+
end
|
260
|
+
|
261
|
+
function NRT_BRDF_nadir(Y::AbstractMatrix, sz::AbstractMatrix, vz::AbstractMatrix, rz::AbstractMatrix, soz_noon::AbstractVector, weighted::Bool, scale::Real)
|
262
|
+
n, p = size(Y)
|
263
|
+
results = fill(NaN, n, 5) # (nadir, nadir_se, rmse, R2, nt)
|
264
|
+
x = ones(3, p)
|
265
|
+
Knadir = [1.0, 0.0, 0.0]
|
266
|
+
|
267
|
+
for i in 1:n
|
268
|
+
yt = Y[i,:]
|
269
|
+
non_missing = findall(isfinite.(yt))
|
270
|
+
nt = length(non_missing)
|
271
|
+
|
272
|
+
if nt < 7
|
273
|
+
continue
|
274
|
+
else
|
275
|
+
szn = soz_noon[i]
|
276
|
+
x[2,:] = Kvol_vec(sz[i,:], vz[i,:], rz[i,:])
|
277
|
+
x[3,:] = Kgeo_vec(sz[i,:], vz[i,:], rz[i,:])
|
278
|
+
|
279
|
+
xt = x[:,non_missing]
|
280
|
+
ytt = yt[non_missing]
|
281
|
+
|
282
|
+
if weighted
|
283
|
+
xx = exp.(-0.5 .* range(p-1, stop=0; length=p) ./ scale)
|
284
|
+
xxt = xx[non_missing]
|
285
|
+
SSi = Diagonal(xxt)
|
286
|
+
Si = inv(xt * SSi * xt')
|
287
|
+
brdf = Si * xt * SSi * ytt
|
288
|
+
yp = (brdf' * xt)'
|
289
|
+
se = sqrt(sum(((ytt - yp) .* xxt).^2)/(nt-3))
|
290
|
+
else
|
291
|
+
Si = inv(xt * xt')
|
292
|
+
brdf = Si * xt * ytt
|
293
|
+
yp = (brdf' * xt)'
|
294
|
+
se = sqrt(sum((ytt - yp).^2)/(nt-3))
|
295
|
+
end
|
296
|
+
|
297
|
+
Knadir[2] = Kvol_sc(szn, 0.0, 0.0)
|
298
|
+
Knadir[3] = Kgeo_sc(szn, 0.0, 0.0)
|
299
|
+
results[i,1] = dot(Knadir, brdf) # nadir
|
300
|
+
results[i,2] = se * sqrt(dot(Knadir, Si * Knadir)) # nadir se
|
301
|
+
results[i,3] = se # brdf rmse
|
302
|
+
results[i,4] = 1 - se^2 * (nt-3) / var(ytt; corrected=true) / nt # brdf R2
|
303
|
+
results[i,5] = nt # number of obs for brdf estimation
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
return results
|
308
|
+
end
|
309
|
+
|
310
|
+
function NRT_BRDF_all(Y::AbstractMatrix, sz::AbstractMatrix, vz::AbstractMatrix, rz::AbstractMatrix, soz_noon::AbstractVector, weighted::Bool = true, scale::Real = 1.87)
|
311
|
+
# the rows are separate locations and the columns are separate times
|
312
|
+
@info "processing BRDF"
|
313
|
+
@info "reflectance rows: $(size(Y)[1]) cols: $(size(Y)[2])"
|
314
|
+
@info "solar zenith rows: $(size(sz)[1]) cols: $(size(sz)[2])"
|
315
|
+
@info "sensor zenith rows: $(size(vz)[1]) cols: $(size(vz)[2])"
|
316
|
+
@info "relative azimuth rows: $(size(rz)[1]) cols: $(size(rz)[2])"
|
317
|
+
@info "solar zenith noon size: $(size(soz_noon)[1])"
|
318
|
+
|
319
|
+
# RossThick constants
|
320
|
+
g0vol = -0.007574
|
321
|
+
g1vol = -0.070987
|
322
|
+
g2vol = 0.307588
|
323
|
+
|
324
|
+
# LiSparseR constants
|
325
|
+
g0geo = -1.284909
|
326
|
+
g1geo = -0.166314
|
327
|
+
g2geo = 0.041840
|
328
|
+
|
329
|
+
gwsa = [1.0, 0.189184, -1.377622]
|
330
|
+
gbsa = [1.0, 0.0, 0.0]
|
331
|
+
gnbar = [1.0, 0.0, 0.0]
|
332
|
+
|
333
|
+
n, p = size(Y)
|
334
|
+
@info "n: $(n) p: $(p)"
|
335
|
+
results = fill(NaN, n, 9) # (wsa, bsa, nadir, wsa_se, bsa_se, nadir_se, rmse, R2, nt)
|
336
|
+
@info "results rows: $(size(results)[1]) cols: $(size(results)[2])"
|
337
|
+
|
338
|
+
x = ones(3, p)
|
339
|
+
|
340
|
+
@showprogress for i in 1:n
|
341
|
+
yt = Y[i,:]
|
342
|
+
non_missing = findall(isfinite.(yt))
|
343
|
+
nt = length(non_missing)
|
344
|
+
|
345
|
+
if nt < 7
|
346
|
+
continue
|
347
|
+
else
|
348
|
+
sznrad = soz_noon[i] * pi/180
|
349
|
+
gbsa[2] = g0vol + g1vol * sznrad^2 + g2vol * sznrad^3
|
350
|
+
gbsa[3] = g0geo + g1geo * sznrad^2 + g2geo * sznrad^3
|
351
|
+
|
352
|
+
gnbar[2] = Kvol_sc(soz_noon[i], 0.0, 0.0)
|
353
|
+
gnbar[3] = Kgeo_sc(soz_noon[i], 0.0, 0.0)
|
354
|
+
|
355
|
+
x[2,:] = Kvol_vec(sz[i,:], vz[i,:], rz[i,:])
|
356
|
+
x[3,:] = Kgeo_vec(sz[i,:], vz[i,:], rz[i,:])
|
357
|
+
|
358
|
+
xt = x[:,non_missing]
|
359
|
+
ytt = yt[non_missing]
|
360
|
+
|
361
|
+
if weighted
|
362
|
+
xx = exp.(-0.5 .* range(p-1, stop=0; length=p) ./ scale)
|
363
|
+
xxt = xx[non_missing]
|
364
|
+
|
365
|
+
SSi = Diagonal(xxt)
|
366
|
+
Si = inv(xt * SSi * xt')
|
367
|
+
brdf = Si * xt * SSi * ytt
|
368
|
+
yp = (brdf' * xt)'
|
369
|
+
se = sqrt(sum(((ytt - yp) .* xxt).^2)/(nt-3))
|
370
|
+
else
|
371
|
+
Si = inv(xt * xt')
|
372
|
+
brdf = Si * xt * ytt
|
373
|
+
yp = (brdf' * xt)'
|
374
|
+
se = sqrt(sum((ytt - yp).^2)/(nt-3))
|
375
|
+
end
|
376
|
+
|
377
|
+
results[i,1] = dot(gwsa, brdf) # wsa
|
378
|
+
results[i,2] = dot(gbsa, brdf) # bsa
|
379
|
+
results[i,3] = dot(gnbar, brdf) # nadir
|
380
|
+
|
381
|
+
results[i,4] = se * sqrt(dot(gwsa, Si * gwsa)) # wsa se
|
382
|
+
results[i,5] = se * sqrt(dot(gbsa, Si * gbsa)) # bsa se
|
383
|
+
results[i,6] = se * sqrt(dot(gnbar, Si * gnbar)) # nadir se
|
384
|
+
|
385
|
+
results[i,7] = se # brdf rmse
|
386
|
+
results[i,8] = 1 - se^2 * (nt-3) / var(ytt; corrected=true) / nt # brdf R2
|
387
|
+
results[i,9] = nt # number of obs for brdf estimation
|
388
|
+
|
389
|
+
replace!(results, missing=>NaN)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
return results
|
394
|
+
end
|
395
|
+
|
396
|
+
export NRT_BRDF_all
|
397
|
+
|
398
|
+
function date_range(start::Date, stop::Date)::Vector{Date}
|
399
|
+
return collect(start:Day(1):stop)
|
400
|
+
end
|
401
|
+
|
402
|
+
function calculate_SZA(day_of_year::Array{Int64,1}, hour_of_day::Union{Array{Float64,1}, Float64}, lat::Array{Float64,1})::Array{Float64,1}
|
403
|
+
day_angle = (2 * pi * (day_of_year .- 1)) / 365
|
404
|
+
lat = deg2rad.(lat)
|
405
|
+
dec = deg2rad.((0.006918 .- 0.399912 .* cos.(day_angle) .+ 0.070257 .* sin.(day_angle) .- 0.006758 .* cos.(2 .* day_angle) .+ 0.000907 .* sin.(2 .* day_angle) .- 0.002697 .* cos.(3 .* day_angle) .+ 0.00148 .* sin.(3 .* day_angle)) .* (180 / pi))
|
406
|
+
hour_angle = deg2rad.(hour_of_day * 15.0 .- 180.0)
|
407
|
+
SZA = rad2deg.(acos.(sin.(lat) .* sin.(dec) .+ cos.(lat) .* cos.(dec) .* cos.(hour_angle)))
|
408
|
+
return SZA
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
File without changes
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
from scipy import stats
|
5
|
+
|
6
|
+
from rasters import Raster
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
def calibrate_fine_to_coarse(fine_image: Raster, coarse_image: Raster) -> Raster:
|
11
|
+
"""
|
12
|
+
Calibrates a fine-resolution raster image to a coarse-resolution raster image
|
13
|
+
using linear regression.
|
14
|
+
|
15
|
+
This function aggregates the fine image to the geometry of the coarse image,
|
16
|
+
then performs a linear regression between the aggregated fine image and the
|
17
|
+
original coarse image. The derived slope and intercept are then applied to
|
18
|
+
the original fine image for calibration.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
fine_image (Raster): The higher-resolution raster image to be calibrated.
|
22
|
+
coarse_image (Raster): The lower-resolution raster image used as the reference
|
23
|
+
for calibration.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
Raster: The calibrated fine-resolution raster image. If too few valid
|
27
|
+
data points are available for regression (less than 30), the
|
28
|
+
original fine_image is returned.
|
29
|
+
"""
|
30
|
+
# Aggregate the fine image to the coarse image's geometry for comparison
|
31
|
+
aggregated_image = fine_image.to_geometry(coarse_image.geometry, resampling="average")
|
32
|
+
x = np.array(aggregated_image).flatten() # Independent variable (aggregated fine)
|
33
|
+
y = np.array(coarse_image).flatten() # Dependent variable (coarse)
|
34
|
+
|
35
|
+
# Create a mask to remove NaN values from both arrays, ensuring valid data points for regression
|
36
|
+
mask = ~np.isnan(x) & ~np.isnan(y)
|
37
|
+
|
38
|
+
# Check if there are enough valid data points for a meaningful linear regression
|
39
|
+
if np.count_nonzero(mask) < 30:
|
40
|
+
logger.warning(
|
41
|
+
f"Insufficient valid data points ({np.count_nonzero(mask)}) for calibration. "
|
42
|
+
"Returning original fine image."
|
43
|
+
)
|
44
|
+
return fine_image
|
45
|
+
|
46
|
+
# Apply the mask to get only valid data points
|
47
|
+
x = x[mask]
|
48
|
+
y = y[mask]
|
49
|
+
|
50
|
+
# Perform linear regression
|
51
|
+
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
|
52
|
+
logger.info(
|
53
|
+
f"Linear regression for calibration: slope={slope:.4f}, intercept={intercept:.4f}, "
|
54
|
+
f"R-squared={r_value**2:.4f}"
|
55
|
+
)
|
56
|
+
|
57
|
+
# Apply the derived calibration to the original fine image
|
58
|
+
calibrated_image = fine_image * slope + intercept
|
59
|
+
|
60
|
+
return calibrated_image
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from os.path import join, abspath, dirname
|
2
|
+
|
3
|
+
# Template file for generating the L2T_STARS run configuration XML.
|
4
|
+
L2T_STARS_TEMPLATE = join(abspath(dirname(__file__)), "ECOv003_L2T_STARS.xml")
|
5
|
+
|
6
|
+
# Default directories and parameters for the L2T_STARS processing.
|
7
|
+
DEFAULT_WORKING_DIRECTORY = "." # Current directory
|
8
|
+
DEFAULT_BUILD = "0700" # Default build ID
|
9
|
+
PRIMARY_VARIABLE = "NDVI" # Primary variable of interest
|
10
|
+
DEFAULT_OUTPUT_DIRECTORY = "L2T_STARS_output"
|
11
|
+
DEFAULT_STARS_SOURCES_DIRECTORY = "L2T_STARS_SOURCES"
|
12
|
+
DEFAULT_STARS_INDICES_DIRECTORY = "L2T_STARS_INDICES"
|
13
|
+
DEFAULT_STARS_MODEL_DIRECTORY = "L2T_STARS_MODEL"
|
14
|
+
DEFAULT_STARS_PRODUCTS_DIRECTORY = "STARS_products"
|
15
|
+
DEFAULT_HLS_DOWNLOAD_DIRECTORY = "HLS2_download"
|
16
|
+
DEFAULT_LANDSAT_DOWNLOAD_DIRECTORY = "HLS2_download" # Redundant but kept for clarity
|
17
|
+
DEFAULT_HLS_PRODUCTS_DIRECTORY = "HLS2_products"
|
18
|
+
DEFAULT_VIIRS_DOWNLOAD_DIRECTORY = "VIIRS_download"
|
19
|
+
DEFAULT_VIIRS_PRODUCTS_DIRECTORY = "VIIRS_products"
|
20
|
+
DEFAUL_VIIRS_MOSAIC_DIRECTORY = "VIIRS_mosaic"
|
21
|
+
DEFAULT_GEOS5FP_DOWNLOAD_DIRECTORY = "GEOS5FP_download"
|
22
|
+
DEFAULT_GEOS5FP_PRODUCTS_DIRECTORY = "GEOS5FP_products"
|
23
|
+
DEFAULT_VNP09GA_PRODUCTS_DIRECTORY = "VNP09GA_products"
|
24
|
+
DEFAULT_VNP43NRT_PRODUCTS_DIRECTORY = "VNP43NRT_products"
|
25
|
+
|
26
|
+
# Processing parameters
|
27
|
+
VIIRS_GIVEUP_DAYS = 4 # Number of days to give up waiting for VIIRS data
|
28
|
+
DEFAULT_SPINUP_DAYS = 7 # Spin-up period for time-series analysis
|
29
|
+
DEFAULT_TARGET_RESOLUTION = 70 # Target output resolution in meters
|
30
|
+
DEFAULT_NDVI_RESOLUTION = 490 # NDVI coarse resolution in meters
|
31
|
+
DEFAULT_ALBEDO_RESOLUTION = 980 # Albedo coarse resolution in meters
|
32
|
+
DEFAULT_USE_SPATIAL = False # Flag for using spatial interpolation (currently unused)
|
33
|
+
DEFAULT_USE_VNP43NRT = True # Flag for using VNP43NRT VIIRS product
|
34
|
+
DEFAULT_CALIBRATE_FINE = False # Flag for calibrating fine resolution data to coarse
|
35
|
+
|
36
|
+
# Product short and long names
|
37
|
+
L2T_STARS_SHORT_NAME = "ECO_L2T_STARS"
|
38
|
+
L2T_STARS_LONG_NAME = "ECOSTRESS Tiled Auxiliary NDVI and Albedo L2 Global 70 m"
|
@@ -0,0 +1 @@
|
|
1
|
+
from .daterange import *
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import datetime
|
2
|
+
import logging
|
3
|
+
from typing import List, Union
|
4
|
+
|
5
|
+
from dateutil import parser
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
def get_date(dt: Union[datetime.date, datetime.datetime, str]) -> datetime.date or None:
|
11
|
+
if dt is None:
|
12
|
+
return None
|
13
|
+
elif isinstance(dt, datetime.datetime):
|
14
|
+
return dt.date()
|
15
|
+
elif isinstance(dt, datetime.date):
|
16
|
+
return dt
|
17
|
+
elif isinstance(dt, str):
|
18
|
+
return parser.parse(dt).date()
|
19
|
+
else:
|
20
|
+
raise ValueError(f"invalid date type: {type(dt)}")
|
21
|
+
|
22
|
+
|
23
|
+
def date_range(start: Union[datetime.date, str], end: Union[datetime.date, str]) -> List[datetime.date]:
|
24
|
+
start = get_date(start)
|
25
|
+
end = get_date(end)
|
26
|
+
|
27
|
+
try:
|
28
|
+
count = (end - start).days
|
29
|
+
except TypeError as e:
|
30
|
+
logger.exception(e)
|
31
|
+
raise TypeError(f"start type: {type(start)} end type: {type(end)}")
|
32
|
+
|
33
|
+
dates = [start + datetime.timedelta(days=days) for days in range(count + 1)]
|
34
|
+
|
35
|
+
return dates
|