- Introduction
- Processing
<xarray.Dataset> Size: 177MB Dimensions: (region: 7, time: 12, longitude: 720, latitude: 360) Coordinates: * region (region) int8 7B 1 2 3 4 5 6 7 * time (time) int8 12B 1 2 3 4 5 6 7 8 9 10 11 12 * longitude (longitude) float64 6kB -179.8 -179.2 -178.8 ... 179.2 179.8 * latitude (latitude) float64 3kB -89.75 -89.25 -88.75 ... 89.25 89.75 Data variables: melt (region, time, latitude, longitude) float64 174MB ... melt_GL (latitude, longitude) float64 2MB ... region_map (latitude, longitude) int8 259kB ... region_name (region) <U2 56B ... spatial_ref int8 1B ... Attributes: geospatial_lat_min: -90 geospatial_lat_max: 90 geospatial_lon_min: -180 geospatial_lon_max: 180 date_created: 20241117T144923Z title: Normalised iceberg melt climatology per region of ca... history: Processed for Schmidt (YYYY; in prep); by Ken Mankoff Conventions: CF-1.8 DOI: https://doi.org/10.5281/zenodo.14020895
Warning
Iceberg tracks come from Marson (2024) http://doi.org/10.1029/2023jc020697. The highest melt rates (largest meltwater injection) occurs in near-coastal cells. If your model has land covering some of these cells, you may lose large melt inputs. Rescaling the melt so melt*area sums to 1 for your ocean is a good idea, but this also redistributes the largest melt points over the entire melt region. It may be better to re-scale the melt by increasing only the largest cell or largest few cells (which are hopefully nearby the coast and the high melt rate cells that were covered by land)
100% 8/8 [03:41<00:00, 27.66s/it]
Data from Marson (2024) http://doi.org/10.1029/2023jc020697
- Find the first and last iceberg track indices, derive melt from that, check inputs equal outputs
- For some reason, last time often has large melt. Maybe we shouldn’t include it. TBD.
# synthetic ice mass array, dimesions [x=time, y=mass]
mass = np.zeros((5,5)) * np.nan
mass[0,0] = 5
mass[1,[1,2]] = [10,9]
mass[2,[1,2]] = [10,5]
mass[3,[2,3,4]] = [20,10,5]
mass[4,[3,4]] = [1.1,1]
print('mass', mass)
# flag the first time the iceberg appears
first = (~np.isnan(mass) & np.isnan(np.roll(mass,1,axis=1)))
print('first', first)
# flag the last time the iceberg appears
last = (~np.isnan(mass) & np.isnan(np.roll(mass,-1,axis=1)))
print('last', last)
# the water mass is just the derivative of the ice mass in time
h2o = -1 * np.diff(mass, axis=1, append=np.nan)
h2o[last] = mass[last]
print('h2o', h2o)
h2o_nolast = -1 * np.diff(mass, axis=1, append=np.nan) # NO LAST
print('h2o no last', h2o_nolast)
print('ice mass: ', mass[first].sum(), mass[first])
print('water mass: ', np.nansum(h2o), np.nansum(h2o,axis=1))
print('water mass no last: ', np.nansum(h2o_nolast), np.nansum(h2o_nolast,axis=1))
mass [[ 5. nan nan nan nan] [ nan 10. 9. nan nan] [ nan 10. 5. nan nan] [ nan nan 20. 10. 5. ] [ nan nan nan 1.1 1. ]] first [[ True False False False False] [False True False False False] [False True False False False] [False False True False False] [False False False True False]] last [[ True False False False False] [False False True False False] [False False True False False] [False False False False True] [False False False False True]] h2o [[ 5. nan nan nan nan] [ nan 1. 9. nan nan] [ nan 5. 5. nan nan] [ nan nan 10. 5. 5. ] [ nan nan nan 0.1 1. ]] h2o no last [[ nan nan nan nan nan] [ nan 1. nan nan nan] [ nan 5. nan nan nan] [ nan nan 10. 5. nan] [ nan nan nan 0.1 nan]] ice mass: 46.1 [ 5. 10. 10. 20. 1.1] water mass: 46.1 [ 5. 10. 10. 20. 1.1] water mass no last: 21.1 [ 0. 1. 5. 15. 0.1]
import xarray as xr
import numpy as np
root = "~/data/Marson_2024/"
mass = xr.open_mfdataset(root+'from_email/mass_01.nc')
bits = xr.open_mfdataset(root+'from_email/mass_of_bits_01.nc')
scale = xr.open_mfdataset(root+'from_email/mass_scaling_01.nc')
# xarray needs things named the same in order to multiply them together.
bits = bits.rename({'mass_of_bits':'mass'})
scale = scale.rename({'mass_scaling':'mass'})
ds = xr.merge([(mass+bits)*scale])
ds = ds.rename({'timestep':'time'})
# %time ds = ds.isel({'particle':np.arange(1000), 'time':np.arange(1000)}).load()
ds['time'].attrs['calendar'] = 'noleap'
ds['time'].attrs['units'] = 'days since 2000-01-01'
ds['time'] = pd.Timestamp("2000-01-01") + pd.to_timedelta(ds['time'].values, unit='D')
ds['particle'] = ds['particle'].astype(np.int32)
print(ds)
<xarray.Dataset> Size: 467MB Dimensions: (time: 5840, particle: 10000) Coordinates: * time (time) datetime64[ns] 47kB 2000-01-02 2000-01-03 ... 2000-12-31 * particle (particle) int32 40kB 117 118 128 129 ... 205896 205897 205916 Data variables: mass (particle, time) float64 467MB dask.array<chunksize=(910, 531), meta=np.ndarray>
# flag the first time the iceberg appears
first = (~np.isnan(ds['mass'].values) & np.isnan(np.roll(ds['mass'].values,1,axis=1)))
last = (~np.isnan(ds['mass'].values) & np.isnan(np.roll(ds['mass'].values,-1,axis=1)))
# the water mass is just the derivative of the ice mass in time
h2o = -1 * np.diff(ds['mass'].values, axis=1, append=np.nan)
h2o[last] = ds['mass'].values[last]
h2oX = -1 * np.diff(ds['mass'].values, axis=1, append=np.nan) # NO LAST
ds['h2o'] = (('particle','time'), h2o)
ds['h2oX'] = (('particle','time'), h2oX)
print(ds)
ice_mass = ds['mass'].values[first].sum(); print('ice mass: ', ice_mass)
water_mass = ds['h2o'].sum().values; print('water mass: ', water_mass)
print(f'diff: {water_mass - ice_mass} ({water_mass / ice_mass * 100} %)')
water_mass_X = ds['h2oX'].sum().values; print('water mass X: ', water_mass_X)
print(f'diff: {water_mass_X - ice_mass} ({water_mass_X / ice_mass * 100} %)')
<xarray.Dataset> Size: 1GB Dimensions: (time: 5840, particle: 10000) Coordinates: * time (time) datetime64[ns] 47kB 2000-01-02 2000-01-03 ... 2000-12-31 * particle (particle) int32 40kB 117 118 128 129 ... 205896 205897 205916 Data variables: mass (particle, time) float64 467MB dask.array<chunksize=(910, 531), meta=np.ndarray> h2o (particle, time) float64 467MB nan nan nan nan ... nan nan nan nan h2oX (particle, time) float64 467MB nan nan nan nan ... nan nan nan nan ice mass: 1886583699309968.5 water mass: 1886583699309959.8 diff: -8.75 (99.99999999999953 %) water mass X: 1410154213770365.2 diff: -476429485539603.25 (74.74644322889779 %)
From the above, the algorithm appears to work, and water mass, computed from derivative of ice mass, matches. If we drop the last time which is always artificially large we lose 25 % of mass. However, because this is a WEIGHTED MASK, not a flux product, the magnitude does not matter, and the weights are probably more realistic by removing the last element. Unless icebergs catastrophically fail at the end and should have a large melt pulse at their final location.
Per Marson (2021) http://doi.org/10.1029/2021jc017542
The annual mass loss (hereafter referred as discharge) from the Greenland Ice Sheet (GrIS) is currently estimated to be around 1,100 Gt/yr, half of which is attributed to liquid runoff and the other half to solid discharge (Bamber et al., 2012, 2018)
Greenland discharge was provided by Bamber et al. (2012) on a 5 × 5 km grid and was remapped to the ANHA4 grid. According to the averages estimated in Bamber et al. (2012), we divided the total discharge into 46% liquid runoff and 54% solid discharge.
So discharge should be ~1100*0.54 = 594 Gt/yr
grass ./G_3413/PERMANENT
g.mapset PERMANENT
v.import input=${DATADIR}/Mouginot_2019/GL_regions.gpkg output=ROIs
v.db.select map=ROIs
v.to.rast input=ROIs output=ROIs use=attr attribute_column=cat_
In addition to loading the public data from Marson (2024) http://doi.org/10.1029/2023jc020697 we need to add in the bergy bits (personal communication). Also, the provided mass is particles (groups of bergs) and needs to be scaled by Martin (2010) http://doi.org/10.1016/j.ocemod.2010.05.001 Table 1 to convert particle mass to ice mass.
import xarray as xr
import pandas as pd
import numpy as np
root='~/data/Marson_2024/'
lon = xr.open_mfdataset(root+'lon_*.nc', join='override', concat_dim='particle', combine='nested')
lat = xr.open_mfdataset(root+'lat_*.nc', join='override', concat_dim='particle', combine='nested')
mass = xr.open_mfdataset([root+'from_email/mass_01.nc',
root+'from_email/mass_02.nc',
root+'from_email/mass_03.nc',
root+'from_email/mass_04.nc'],
join='override', concat_dim='particle', combine='nested')
bits = xr.open_mfdataset(root+'from_email/mass_of_bits_*.nc', join='override', concat_dim='particle', combine='nested')
scale = xr.open_mfdataset(root+'from_email/mass_scaling_*.nc', join='override', concat_dim='particle', combine='nested')
# xarray needs things named the same in order to multiply them together.
bits = bits.rename({'mass_of_bits':'mass'})
scale = scale.rename({'mass_scaling':'mass'})
%time ds = xr.merge([lon,lat,(mass+bits)*scale])
ds = ds.rename({'timestep':'time'})
ds['time'].attrs['calendar'] = 'noleap'
ds['time'].attrs['units'] = 'days since 2000-01-01'
ds['time'] = pd.Timestamp("2000-01-01") + pd.to_timedelta(ds['time'].values, unit='D')
ds['particle'] = ds['particle'].astype(np.int32)
print(ds)
CPU times: user 6.31 ms, sys: 0 ns, total: 6.31 ms Wall time: 6.31 ms <xarray.Dataset> Size: 5GB Dimensions: (time: 5840, particle: 34025) Coordinates: * time (time) datetime64[ns] 47kB 2000-01-02 2000-01-03 ... 2000-12-31 * particle (particle) int32 136kB 117 118 128 129 ... 1806577 1806831 1807085 Data variables: lon (particle, time) float64 2GB dask.array<chunksize=(1667, 974), meta=np.ndarray> lat (particle, time) float64 2GB dask.array<chunksize=(1667, 974), meta=np.ndarray> mass (particle, time) float64 2GB dask.array<chunksize=(910, 531), meta=np.ndarray>
# flag the first time the iceberg appears
first = (~np.isnan(ds['mass'].values) & np.isnan(np.roll(ds['mass'].values,1,axis=1)))
# the water mass is just the derivative of the ice mass in time
h2o = -1 * np.diff(ds['mass'].values, axis=1, append=np.nan) # drop last time as diff does naturally
ds['h2o'] = (('particle','time'), h2o)
ds['first'] = (('particle','time'), first)
print(ds)
<xarray.Dataset> Size: 7GB Dimensions: (time: 5840, particle: 34025) Coordinates: * time (time) datetime64[ns] 47kB 2000-01-02 2000-01-03 ... 2000-12-31 * particle (particle) int32 136kB 117 118 128 129 ... 1806577 1806831 1807085 Data variables: lon (particle, time) float64 2GB dask.array<chunksize=(1667, 974), meta=np.ndarray> lat (particle, time) float64 2GB dask.array<chunksize=(1667, 974), meta=np.ndarray> mass (particle, time) float64 2GB dask.array<chunksize=(910, 531), meta=np.ndarray> h2o (particle, time) float64 2GB nan nan nan nan ... nan nan nan nan first (particle, time) bool 199MB False False False ... False False True
comp = dict(zlib=True, complevel=2)
encoding = {var: comp for var in ds.data_vars}
delayed_obj = ds.to_netcdf('tmp/bergs.nc', encoding=encoding, compute=False)
from dask.diagnostics import ProgressBar
with ProgressBar():
results = delayed_obj.compute()
# saves as 250 MB file. Takes a few minutes...
[########################################] | 100% Completed | 231.73 s
import xarray as xr
import numpy as np
import pandas as pd
%time ds = xr.open_dataset('tmp/bergs.nc').load() # load everything into memory
# Takes a while...
CPU times: user 13.9 s, sys: 18 s, total: 31.8 s Wall time: 43.3 s
%time ice_mass = ds['mass'].values[ds['first'].values].sum()
print('ice mass: ', ice_mass * 1E-12 / 16) # total kg over 16 years -> Gt/yr
%time water_mass = np.nansum(ds['h2o'].values)
print('water mass: ', water_mass * 1E-12 / 16)
CPU times: user 92.9 ms, sys: 0 ns, total: 92.9 ms Wall time: 91.4 ms ice mass: 407.2388163829433 CPU times: user 996 ms, sys: 4.79 s, total: 5.79 s Wall time: 7.51 s water mass: 296.9381105981331
The difference between the Marson (2024) http://doi.org/10.1029/2023jc020697 407 Gt/year and the Mankoff (2020) http://doi.org/10.5194/essd-12-1367-2020 ~500 Gt/year (subject to change with each version) is not important. It can represent a lot of things, most likely that Mankoff (2020) is discharge across flux gates upstream from the terminus, so 100 - 407/500 % = 18.6 % is submarine melt, and the remainder is the Marson icebergs.
Additional melting occurs in the fjord and must be handled if the model does not resolve fjords.
This product should be shared as one and several weighted masks that sum to 1, and then users can scale by their own estimated discharge.
The difference between ice inputs and water outputs is described above - we drop the last melt event of each berg which seems anomalously high.
- Warning: 34k files generated here.
from tqdm import tqdm
for p in tqdm(range(ds['particle'].values.size)):
df = ds.isel({'particle':p})\
.to_dataframe()\
.dropna()
if df.size == 0: continue
df['first'] = df['first'].astype(int)
df[['particle','lon','lat','mass','h2o','first']]\
.to_csv(f"./Marson_2024_tmp/{str(p).zfill(5)}.csv", header=None)
100% 34025/34025 [03:16<00:00, 173.55it/s]
[[ -e G_3413 ]] || grass -ec EPSG:3413 ./G_3413
grass ./G_3413/PERMANENT
g.mapset -c Marson_2024
export GRASS_OVERWRITE=1
ogr2ogr ./tmp/Mouginot.gpkg -t_srs "EPSG:3413" ${DATADIR}/Mouginot_2019/Greenland_Basins_PS_v1.4.2.shp
v.import input=${DATADIR}/Mouginot_2019/GL_regions.gpkg output=GL
v.db.select map=GL
g.region vector=GL res=10000 -pa
v.to.rast input=GL output=GL use=attr attribute_column=cat_
- Take time (month) into account.
- 84 maps: 7 roi * 12 month: CE_01, CE_02, etc…
# reorder from "cat,id,lon,lat,ice mass,water mass" to lon,lat,water,id,time
cat Marson_2024_tmp/*.csv | awk -F, '{OFS=",";print $3,$4,$6,$7,$2,$1}' > tmp/tracks.csv
# head -n100 tmp/tracks.csv \
cat tmp/tracks.csv \
| m.proj -i input=- separator=comma \
| tr ' ' ',' \
| v.in.ascii -n input=- output=bergs sep=, \
columns='x double,y double,water double,first int,id int,time varchar(10)'
g.region vector=bergs res=25000 -pa
g.region save=iceberg_region
r.mapcalc "x = x()"
r.mapcalc "y = y()"
r.mapcalc "area = area()"
# Record nearest region at all times, by finding the region nearest the 1st time
v.db.addcolumn map=bergs columns="roi VARCHAR(3)"
# v.db.select map=bergs|head
v.extract input=bergs where='(first == 1)' output=t0
v.distance from=t0 to=GL upload=to_attr to_column=label column=roi
db.select table=t0|head| column -s"|" -t
db.select table=bergs|head| column -s"|" -t
roi=NO # debug
for roi in NO NE SE SW CW NW CE; do
echo "Processing ROI: ${roi}"
ids=$(db.select -c sql="select id from t0 where roi == '${roi}'")
ids=$(echo ${ids}| tr ' ' ',')
db.execute sql="update bergs set roi = \"${roi}\" where id in (${ids})"
done
db.select table=bergs | head -n 10 | column -s"|" -t
# # convert to raster, binned by melt per cell (a.k.a density or heat or quilt map)
# roi=NO; month=03 # debug
# # this loop takes a few minutes per ROI. Could use GNU parallel.
# for roi in NO NE SE SW CW NW CE; do
# echo "Processing ROI: ${roi}"
# for month in $(seq -w 12); do
# echo "Processing month: ${month}"
# v.out.ascii input=bergs output=- format=point columns=water \
# where="roi == \"${roi}\" AND time LIKE \"2000-${month}%\""\
# | r.in.xyz input=- z=4 output=${roi}_${month} method=sum
# r.colors -g map=${roi}_${month} color=viridis
# # Convert from kg/16years to kg/s
# r.mapcalc "${roi}_${month} = ${roi}_${month} / 16 / 365 / 86400"
# done
# done
rois="NO NE SE SW CW NW CE"
months=$(seq -w 12)
# convert to raster, binned by melt per cell (a.k.a density or heat or quilt map)
parallel -j4 --bar "v.out.ascii input=bergs output=- format=point columns=water \
where=\"roi == '{1}' AND time LIKE '2000-{2}%'\" \
| r.in.xyz input=- z=4 output={1}_{2} method=sum" ::: $rois ::: $months
# Convert from kg/16years to kg/s
parallel --bar "r.mapcalc \"{1}_{2} = {1}_{2} / 16 / 365 / 86400\"" ::: $rois ::: $months
parallel --bar "r.colors -g map={1}_{2} color=viridis" ::: $rois ::: $months
tot=0
for roi in CE CW NE NO NW SE SW; do
roimonths=$(g.list type=raster pattern=${roi}_?? sep=,)
eval $(r.univar -g ${roimonths})
# convert from kg/s to Gt/year
roi_gt=$(echo "${sum} * 86400 * 365 * 10^(-12)" | bc -l)
echo "${roi}: ${roi_gt}"
tot=$(echo "${tot} + ${roi_gt}" | bc -l)
done
echo ""
echo "total: " ${tot}
My estimates of discharge by ROI?
import xarray as xr
dd = xr.open_dataset('/home/kdm/data/Mankoff_2020/ice/latest/region.nc')\
.sel({'time':slice('2000-01-01','2019-12-31')})\
.resample({'time':'YS'})\
.mean()\
.mean(dim='time')\
['discharge']
print(dd.sum())
dd.to_dataframe()
<xarray.DataArray 'discharge' ()> Size: 8B array(476.48053387)
region | discharge |
---|---|
CE | 77.8964 |
CW | 86.1499 |
NE | 25.9822 |
NO | 25.329 |
NW | 103.127 |
SE | 139.048 |
SW | 18.9477 |
Display problem:
g.mapset Marson_2024
d.mon wx0
r.colors -g map=CE color=viridis
d.rast CE_01 values=0.001-1000
d.rast CE_06 values=0.001-1000
Load global coastlines for eventual cropping
r.mask -r
v.import extent=region input=${DATADIR}/NaturalEarth/ne_50m_land.shp output=land
v.to.rast input=land output=land use=value value=1
r.mask -i land
Problem: Small isolate tracks
Algorithm:
- Smooth (if this is all we do, it would remove the high melt cells near the coast)
- Crop by coastlines (Greenland, Canada, Iceland, Svalbard, etc.)
- Set new area equal to old melt values where both exist (undo smooth where there was data)
- Set new area (from smooth) equal to some low amount (median) where new area but no old melt
- Rescale so total melt is unchanged
export GRASS_OVERWRITE=1
rois="NO NE SE SW CW NW CE"
months=$(seq -w 12)
# g.gui.mapswipe first=CE_08 second=CE_08_n
roi=NW; month=08 # debug
for roi in $rois; do
for month in $(seq -w 12); do
r.mapcalc "r_main = if(${roi}_${month} > 0, ${roi}_${month}, null())"
eval $(r.univar -g -e r_main percentile=95)
roisum=${sum}
roimedian=${median}
# buffer by X m to get a smooth border
r.grow.distance -m input=r_main distance=distance
r.mapcalc "r_buffer = distance < 250000" # 250 km
# at 25 km resolution this is 10 cells
# Expand original melt map with average of neighbors
# SIZE here should take into account buffer distance above
r.neighbors input=${roi}_${month} output=r_neighbor method=average size=15 weighting_function=gaussian weighting_factor=3
# Convert new area to melt w/ median values where filled in
r.mask -i land
r.mapcalc "r_newmelt = if(r_buffer, if((${roi}_${month} > ${percentile_95}), ${roi}_${month}, r_neighbor))"
r.mask -r
# rescale to total melt from original map
eval $(r.univar -g r_newmelt) # get $sum
r.mapcalc "${roi}_${month}_smooth = (r_newmelt / ${sum}) * ${roisum}"
r.colors -g map=${roi}_${month}_smooth,${roi}_${month} color=viridis
done
done
# x=${roi}_${month}; g.gui.mapswipe first=$x second=r_newmelt
# x=${roi}_${month}; g.gui.mapswipe first=$x second=${x}_smooth # mode=mirror
x=NO_08; g.gui.mapswipe first=$x second=${x}_smooth mode=mirror
# # Generate GL-wide map
# r.mapcalc "GL_notail = 0"
# for roi in NO NE SE SW CW NW CE; do
# r.mapcalc "GL_notail = GL_notail + if(isnull(${roi}_notail), 0, ${roi}_notail)"
# done
# r.null map=GL_notail setnull=0
# convert to m-2 prior to reproject
for roi in $rois; do
for mm in $months; do
r.mapcalc "${roi}_${mm}_m2 = ${roi}_${mm}_smooth / area"
r.colors map=${roi}_m2 color=viridis -g
done
done
- GRASS raster reprojection uses nearest neighbor.
- We need to convert to points in 3413, and then sum multiple 3413 points that fall within a 4326 grid cell.
- Work at a high resolution in 3413 so that there are no cells in 4326 that are left empty.
- If we reproject points from the current 25 km resolution there will be gaps because in N Greenland at EPSG:4326 and 0.5 degree resolution grid cells are ~15 km wide.
- Therefore, resample to 5 km and then reproject points at that resolution.
- Check: The sum of flux (kg) and the sum of flux (m-2) should be the same after reprojection.
3413:
# grass ./G_3413/Marson_2024
g.region res=5000 -pa # Divide value by 25 because they were calculated on a grid 5x5 larger
roi=NO; mm=09 # debug
for roi in $rois; do
for mm in $months; do
echo $roi $mm
r.mapcalc --q "tmp_5km = ${roi}_${mm}_smooth / 25"
r.out.xyz --q input=tmp_5km output=- \
| grep -v "|0$" \
| m.proj --q -o -d input=- > ./tmp/${roi}_${mm}.xyz
done
done
# r.univar -gr tmp_5km | grep -E "mean|sum"
# r.univar -gr NO_09_m2 | grep -E "mean|sum"
g.region -pa region=iceberg_region # reset region
4326:
grass ./G_4326/Marson_2024
rois="NO NE SE SW CW NW CE"
months=$(seq -w 12)
export GRASS_OVERWRITE=1
r.proj project=G_3413 input=area output=area_3413
roi=NO; mm=09 # debug
for roi in $rois; do
for mm in $months; do
echo $roi $mm
r.in.xyz --q input=./tmp/${roi}_${mm}.xyz z=3 output=${roi}_${mm} method=sum
#r.univar -g ${roi}_${mm} | grep -E "mean|sum" # matches SUM [kg] from 3413
# r.mapcalc "${roi}_${mm}_m2 = ${roi}_${mm} / area_3413"
# r.univar -g ${roi}_${month}_m2 | grep -E "mean|sum" # matches SUM [m-2] from 3413
done
done
tot=0
for roi in $rois; do
roimonths=$(g.list type=raster pattern=${roi}_?? sep=,)
eval $(r.univar -g ${roimonths})
# convert from kg/s to Gt/year
roi_gt=$(echo "${sum} * 86400 * 365 * 10^(-12)" | bc -l)
echo "${roi}: ${roi_gt}"
tot=$(echo "${tot} + ${roi_gt}" | bc -l)
done
echo ""
echo "total: " ${tot}
CE: 44.76374035974432864000 CW: 55.79715155801872896000 NE: 19.41732943638206054400 NO: 20.40425666413177809600 NW: 62.87261995622087280000 SE: 80.38475519025573360000 SW: 13.37355447290342323200 total: 297.01340763765692587200
These are the results without last berg removed:
CE: 60.83514096745521600000 CW: 77.10383458384345872000 NE: 25.40014065127645070400 NO: 28.68057265378820385600 NW: 85.30203183944359392000 SE: 111.19281504778338912000 SW: 18.72428302446387782400 total: 407.23881876805419014400
import numpy as np
import xarray as xr
import rioxarray as rxr
from tqdm import tqdm
import datetime
from grass_session import Session
from grass.script import core as gcore
import grass.script as gscript
# import grass.script.setup as gsetup
# import grass python libraries
from grass.pygrass.modules.shortcuts import general as g
from grass.pygrass.modules.shortcuts import raster as r
from grass.pygrass.modules.shortcuts import vector as v
from grass.pygrass.modules.shortcuts import temporal as t
from grass.script import array as garray
S = Session()
S.open(gisdb=".", location="G_4326", mapset="Marson_2024", create_opts=None)
lon = garray.array("x")[::-1,:]
lat = garray.array("y")[::-1,:]
melt = np.zeros((7, 12, lon.shape[0], lat.shape[1]))
for roi_i,roi in enumerate(['CE','CW','NE','NO','NW','SE','SW']):
for month in range(12):
mm = str(month+1).zfill(2)
melt[roi_i,month,:,:] = garray.array(roi+'_'+mm)[::-1,:]
ds = xr.Dataset({
'melt': xr.DataArray(data = melt,
dims = ['region','time','latitude','longitude'],
coords = {'region':np.arange(7).astype(np.int8)+1,
'time': np.arange(12).astype(np.int8)+1,
'longitude':lon[0,:],
'latitude':lat[:,0]})})
ds['melt_GL'] = ds['melt'].sum(dim=['region','time'])
# normalize from kg m-2 to m-2
llon,llat = np.meshgrid(ds['longitude'].values, ds['latitude'].values)
earth_rad = 6.371e6 # Earth radius in m
resdeg = 0.5 # output grid resolution in degrees
cell_area = np.cos(np.deg2rad(llat)) * earth_rad**2 * np.deg2rad(resdeg)**2
ds['melt'] = ds['melt'] / ds['melt_GL'].sum() / cell_area
ds['melt_GL'] = ds['melt_GL'] / ds['melt_GL'].sum() / cell_area
ROIs = garray.array("ROIs")[::-1,:]
ds['region_map'] = (('latitude','longitude'), ROIs.astype(np.byte))
S.close() # Done with GRASS
ds['region_name'] = (('region'), ['CE','CW','NE','NO','NW','SE','SW'])
ds = ds.rio.write_crs('epsg:4326')
ds['spatial_ref'] = ds['spatial_ref'].astype(np.byte)
ds = ds.rio.set_spatial_dims('longitude','latitude')
ds['latitude'].attrs['long_name'] = 'latitude'
ds['latitude'].attrs['axis'] = 'Y'
ds['latitude'].attrs['units'] = 'degrees_north'
ds['latitude'].attrs['standard_name'] = 'latitude'
ds['longitude'].attrs['long_name'] = 'longitude'
ds['longitude'].attrs['axis'] = 'X'
ds['longitude'].attrs['units'] = 'degrees_east'
ds['longitude'].attrs['standard_name'] = 'longitude'
ds['time'].attrs['standard_name'] = 'time'
ds['melt'].attrs['long_name'] = 'Normalised iceberg melt climatology per region of calving'
ds['melt'].attrs['units'] = 'm-2'
ds['melt'].attrs['standard_name'] = 'water_flux_into_sea_water_from_icebergs'
ds['melt'].attrs['comment'] = 'This value summed by area and time and multiplied by cell area should sum to 1'
ds['melt_GL'].attrs['long_name'] = 'Normalised iceberg melt climatology for all Greenland'
ds['melt_GL'].attrs['units'] = 'm-2'
ds['melt_GL'].attrs['standard_name'] = 'water_flux_into_sea_water_from_icebergs'
ds['melt_GL'].attrs['comment'] = 'This value multiplied by cell area should sum to 1'
ds['region'].attrs['long_name'] = 'Region IDs'
ds['region_name'].attrs['long_name'] = 'Mouginot (2019) region'
ds['region_name'].attrs['standard_name'] = 'region'
ds['region_map'].attrs['long_name'] = 'Region IDs'
ds['spatial_ref'].attrs['horizontal_datum_name'] = 'WGS 84'
ds.attrs['geospatial_lat_min'] = -90 # ds['latitude'].values.min()
ds.attrs['geospatial_lat_max'] = 90 # ds['latitude'].values.max()
ds.attrs['geospatial_lon_min'] = -180 # ds['longitude'].values.min()
ds.attrs['geospatial_lon_max'] = 180 # ds['longitude'].values.max()
ds.attrs['date_created'] = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
ds.attrs['title'] = 'Normalised iceberg melt climatology per region of calving from Marson (2024)'
ds.attrs['history'] = 'Processed for Schmidt (YYYY; in prep); by Ken Mankoff'
ds.attrs['Conventions'] = 'CF-1.8'
ds.attrs['DOI'] = 'https://doi.org/10.5281/zenodo.14020895'
comp = dict(zlib=True, complevel=2) # Internal NetCDF compression
encoding = {var: comp for var in ds.drop_vars('region_name').data_vars}
!rm ./dat/GL_iceberg_melt.nc
ds.to_netcdf('./dat/GL_iceberg_melt.nc', encoding=encoding)
print(ds)
<xarray.Dataset> Size: 177MB Dimensions: (region: 7, time: 12, longitude: 720, latitude: 360) Coordinates: * region (region) int8 7B 1 2 3 4 5 6 7 * time (time) int8 12B 1 2 3 4 5 6 7 8 9 10 11 12 * longitude (longitude) float64 6kB -179.8 -179.2 -178.8 ... 179.2 179.8 * latitude (latitude) float64 3kB -89.75 -89.25 -88.75 ... 89.25 89.75 spatial_ref int8 1B 0 Data variables: melt (region, time, latitude, longitude) float64 174MB 0.0 ... 0.0 melt_GL (latitude, longitude) float64 2MB 0.0 0.0 0.0 ... 0.0 0.0 0.0 region_map (latitude, longitude) int8 259kB 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 region_name (region) <U2 56B 'CE' 'CW' 'NE' 'NO' 'NW' 'SE' 'SW' Attributes: geospatial_lat_min: -90 geospatial_lat_max: 90 geospatial_lon_min: -180 geospatial_lon_max: 180 date_created: 20241117T144923Z title: Normalised iceberg melt climatology per region of ca... history: Processed for Schmidt (YYYY; in prep); by Ken Mankoff Conventions: CF-1.8 DOI: https://doi.org/10.5281/zenodo.14020895
import xarray as xr
import numpy as np
ds = xr.open_dataset('dat/GL_iceberg_melt.nc')
llon,llat = np.meshgrid(ds['longitude'].values, ds['latitude'].values)
earth_rad = 6.371e6 # Earth radius in m
resdeg = 0.5 # output grid resolution in degrees
cell_area = np.cos(np.deg2rad(llat)) * earth_rad**2 * np.deg2rad(resdeg)**2
ds['area'] = (('latitude','longitude'), cell_area)
# print(ds)
print( 'melt_GL', (ds['melt_GL']*ds['area']).sum(dim=['latitude','longitude']) )
times = (ds['melt']*ds['area']).sum(dim=['latitude','longitude','region'])
print( 'melt times', times.values, times.sum().values)
rois = (ds['melt']*ds['area']).sum(dim=['latitude','longitude','time'])
print( 'melt rois', rois.values, rois.sum().values)
melt_GL <xarray.DataArray ()> Size: 8B array(1.) melt times [0.05709769 0.05647682 0.05988504 0.0497022 0.05714598 0.07278505 0.12313315 0.15238323 0.13335883 0.09943356 0.07442078 0.06417767] 1.0 melt rois [0.15071286 0.18786072 0.06537526 0.0686981 0.21168277 0.27064352 0.04502677] 0.9999999999999999