import os
from osgeo import osr
import pandas as pd
import numpy as np
import math
import subprocess
From the PO.DAAC Cookbook, to access the GitHub version of the notebook, follow this link.
Compare SWOT water surface elevation with USGS gage heights
Datum Transformation Tutorial
Summary
This notebook showcases how to transform the horizontal and vertical coordinates of USGS gage heights to match SWOT LakeSP water surface elevation using GDAL.
Requirements
Compute environment:
This tutorial is written to run in the following environment: - Local compute environment e.g. laptop, server: this tutorial can be run on your local machine - GDAL must be installed (https://gdal.org/)
Import libraries
Datasets
1. USGS Lake/Reservoir Water Surface Elevation dataset can be acccessed using the DataRetrieval python module with the parameter code 62615.
2. SWOT LakeSP dataset can be accessed using the Earthaccess python module or the PO.DAAC Data Downloader.
The above data were combined outside of this tutorial into a csv file called ‘usgs_swot_merged_example.csv’.
Datum Transformation to Compare with SWOT Data
Cotemporal SWOT LakeSP and USGS observations were used to form the dataset used for comparisons including X lakes with gages. To directly compare SWOT and USGS datasets, the USGS horizontal and vertical coordiantes must be transformed to match the SWOT datums. Datums for each dataset are noted in Table 1.
It is important to note that using the generic WGS84 (EPSG:4326) is not recommended because it is based on a datum ensemble whose positional accuracy is approximately two meters. Instead, a realization such as WGS84 (G1762) is recommended. WGS84 (G1762) and ITRF 2014 are equivalent for all practical purposes when their epochs are the same.
Epochs are used to define a reference date for positions esablished using the datum ellipsoid and reference frame. Due to tectonic plate movement, landmasses are constantly moving in relationship to each other and in relation to the reference frame. Therefore, accounting for the epoch is necessary for accurate coordinate transformations. The NAD83 (2011) epoch is 2010.0. The standard epoch of WGS84 (G1762) is 2005.0 and the standard epoch of ITRF2014 is 2010.0. Since SWOT is based on ITRF2014, we set the target epoch to 2010.0.
Table 1. SWOT and USGS datum information
Data source | Horizontal Datum | Reference Ellipsoid | Vertical Datum | EPSG Code |
---|---|---|---|---|
SWOT | ITRF2014 | WGS84 | EGM2008 | EPSG:9057+EPSG:3855 |
USGS | NAD83 (2011) | GRS80 | NAVD88 | EPSG:6349 |
USGS data is represented using EPSG:6349. EPSG:6349 is a compound CRS that represents NAD83 (2011) + NAVD88 height.
# Details of the the compound NAD83(2011) + NAVD88 (EPSG:6349)
= osr.SpatialReference()
src 6349)
src.ImportFromEPSG(print(src)
COMPD_CS["NAD83(2011) + NAVD88 height",
GEOGCS["NAD83(2011)",
DATUM["NAD83_National_Spatial_Reference_System_2011",
SPHEROID["GRS 1980",6378137,298.257222101,
AUTHORITY["EPSG","7019"]],
AUTHORITY["EPSG","1116"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree",0.0174532925199433,
AUTHORITY["EPSG","9122"]],
AXIS["Latitude",NORTH],
AXIS["Longitude",EAST],
AUTHORITY["EPSG","6318"]],
VERT_CS["NAVD88 height",
VERT_DATUM["North American Vertical Datum 1988",2005,
AUTHORITY["EPSG","5103"]],
UNIT["metre",1,
AUTHORITY["EPSG","9001"]],
AXIS["Gravity-related height",UP],
AUTHORITY["EPSG","5703"]],
AUTHORITY["EPSG","6349"]]
SWOT data is represented using EPSG:9057 + EPSG:3855. EPSG:9057 represents WGS84 (G1762) and EPSG: 3855 represents EGM 2008.
# Details for EPSG:9057 WGS84 (G1762)
= osr.SpatialReference()
dst 9057)
dst.ImportFromEPSG(print(dst)
GEOGCS["WGS 84 (G1762)",
DATUM["World_Geodetic_System_1984_G1762",
SPHEROID["WGS 84",6378137,298.257223563,
AUTHORITY["EPSG","7030"]],
AUTHORITY["EPSG","1156"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree",0.0174532925199433,
AUTHORITY["EPSG","9122"]],
AXIS["Latitude",NORTH],
AXIS["Longitude",EAST],
AUTHORITY["EPSG","9057"]]
# Details of EPSG:3855 EGM2008
= osr.SpatialReference()
v_dst 3855)
v_dst.ImportFromEPSG(print(v_dst)
VERT_CS["EGM2008 height",
VERT_DATUM["EGM2008 geoid",2005,
AUTHORITY["EPSG","1027"]],
UNIT["metre",1,
AUTHORITY["EPSG","9001"]],
AXIS["Gravity-related height",UP],
AUTHORITY["EPSG","3855"]]
Transform USGS coordinates for comparison with SWOT LakeSP
Prep the data
# Change working directory to tutorials folder
'.')
os.chdir(
# Open the dataset of cotemporal SWOT and USGS observations
= pd.read_csv("../resources/usgs_swot_merged_example.csv", index_col=0)
swot_usgs_df
# How many cotemporal observations?
print('The number of cotemporal USGS and SWOT observations = ' + str(swot_usgs_df.shape[0]))
# Get data into correct format to pass to gdal including longitude, latitude, and gage height in feet
= swot_usgs_df[["usgs_long", "usgs_lat", "usgs_X_62615_00000"]].copy()
in_gdal
# Since the USGS heights are in feet but the projection we have assigned are in meters, convert heights to meters
'usgs_X_62615_00000'] = in_gdal.loc[:,'usgs_X_62615_00000'] * 0.3048
in_gdal.loc[:,
# Create a column with long, lat, height combined
"out"] = [
in_gdal.loc[:,str(i) + " " + str(j) + " " + str(k)
for i, j, k in zip(
"usgs_long"],
in_gdal["usgs_lat"],
in_gdal["usgs_X_62615_00000"],
in_gdal[
)
]
# Save the combined column to a text file
"out"].to_csv("../resources/gdal_in.txt", header=False, index=False) in_gdal[
The number of cotemporal USGS and SWOT observations = 425
Transform using gdal
# Write the gdal command and run in the shell
'../')
os.chdir(= "cd " + os.getcwd() + "//resources && "
cd_command = 'gdaltransform -s_coord_epoch "2010.0" -t_coord_epoch "2010.0" -s_srs "EPSG:6349" -t_srs "EPSG:9057+EPSG:3855" < "gdal_in.txt" > "gdal_out.txt"'
gdal_command + gdal_command, shell=True) subprocess.run(cd_command
CompletedProcess(args='cd /home/jovyan/tutorials/notebooks//resources && gdaltransform -s_coord_epoch "2010.0" -t_coord_epoch "2010.0" -s_srs "EPSG:6349" -t_srs "EPSG:9057+EPSG:3855" < "gdal_in.txt" > "gdal_out.txt"', returncode=0)
Calculate error statistics between USGS datum-transformed data and SWOT data
# Merge back with the original data
= pd.read_csv("resources/gdal_out.txt", header=None)
out_gdal = out_gdal.rename(columns={0: "result"})
out_gdal "usgs_long", "usgs_lat", "usgs_X_62615_00000_egm0_meters"]] = out_gdal["result"].str.split(" ", expand=True)
out_gdal[["usgs_X_62615_00000_egm0_meters"] = out_gdal["usgs_X_62615_00000_egm0_meters"].astype(float)
swot_usgs_df[
# Error stats
= np.mean(np.abs(np.subtract(swot_usgs_df["usgs_X_62615_00000_egm0_meters"], swot_usgs_df["swot_wse"])))
mae = np.mean(np.subtract(swot_usgs_df["usgs_X_62615_00000_egm0_meters"], swot_usgs_df["swot_wse"]))
bias = math.sqrt(np.square(np.subtract(swot_usgs_df["usgs_X_62615_00000_egm0_meters"], swot_usgs_df["swot_wse"])).mean())
rmse = np.quantile(np.abs(np.subtract(swot_usgs_df["usgs_X_62615_00000_egm0_meters"], swot_usgs_df["swot_wse"])),0.68)
one_sigma = pd.DataFrame([[bias, mae, rmse, one_sigma]], columns=["MBE (m)", "MAE (m)", "RMSE (m)", "One-Sigma (m)"])
results print(results)
MBE (m) MAE (m) RMSE (m) One-Sigma (m)
0 -0.144761 0.299489 0.974874 0.139806