Skip to content

Commit

Permalink
upgrade to copernicus datahub and new pyproj version
Browse files Browse the repository at this point in the history
  • Loading branch information
BuddyVolly committed May 30, 2024
1 parent 24ba17f commit 1c2514b
Show file tree
Hide file tree
Showing 8 changed files with 702 additions and 165 deletions.
87 changes: 33 additions & 54 deletions ost/Project.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@

from ost.helpers import vector as vec, raster as ras
from ost.helpers import scihub, helpers as h, srtm, copdem
from ost.helpers import copernicus as cop
from ost.helpers.settings import set_log_level, setup_logfile, OST_ROOT
from ost.helpers.settings import check_ard_parameters

from ost.s1 import search, refine_inventory, download
from ost.s1 import search_data as search
from ost.s1 import refine_inventory, download
from ost.s1 import burst_inventory, burst_batch
from ost.s1 import grd_batch

Expand Down Expand Up @@ -178,55 +180,23 @@ class Sentinel1(Generic):
"""

product_type = None
"TBD"

beam_mode = None
"TBD"

polarisation = None
"TBD"

inventory_file = None
"TBD"

inventory = None
"TBD"

refined_inventory_dict = None
"TBD"

coverages = None
"TBD"

burst_inventory = None
"TBD"

burst_inventory_file = None
"TBD"

scihub_uname = None
"str: the scihub username"

scihub_pword = None
"str: the scihub password"

dataspace_uname = None
dataspace_pword = None
asf_uname = None
"TBD"

asf_pword = None
"TBD"

peps_uname = None
"TBD"

peps_pword = None
"TBD"

onda_uname = None
"TBD"

onda_pword = None
"TBD"


def __init__(
self,
Expand All @@ -235,9 +205,9 @@ def __init__(
start="2014-10-01",
end=datetime.today().strftime(OST_DATEFORMAT),
data_mount=None,
product_type="*",
beam_mode="*",
polarisation="*",
product_type=None,
beam_mode=None,
polarisation=None,
log_level=logging.INFO,
):

Expand All @@ -247,21 +217,21 @@ def __init__(

# ------------------------------------------
# 2 Check and set product type
if product_type in ["*", "RAW", "SLC", "GRD"]:
if product_type in [None, "RAW", "SLC", "GRD"]:
self.product_type = product_type
else:
raise ValueError("Product type must be one out of '*', 'RAW', " "'SLC', 'GRD'")
raise ValueError("Product type must be one out of None, 'RAW', " "'SLC', 'GRD'")

# ------------------------------------------
# 3 Check and set beam mode
if beam_mode in ["*", "IW", "EW", "SM"]:
if beam_mode in [None, "IW", "EW", "SM"]:
self.beam_mode = beam_mode
else:
raise ValueError("Beam mode must be one out of 'IW', 'EW', 'SM'")
raise ValueError("Beam mode must be one out of None, 'IW', 'EW', 'SM'")

# ------------------------------------------
# 4 Check and set polarisations
possible_pols = ["*", "VV", "VH", "HV", "HH", "VV VH", "HH HV"]
possible_pols = [None, "VV", "VH", "HV", "HH", "VV VH", "HH HV", "*"]
if polarisation in possible_pols:
self.polarisation = polarisation
else:
Expand Down Expand Up @@ -292,8 +262,8 @@ def __init__(

# ------------------------------------------
# 7 Initialize uname and pword to None
self.scihub_uname = None
self.scihub_pword = None
self.dataspace_uname = None
self.dataspace_pword = None

self.asf_uname = None
self.asf_pword = None
Expand All @@ -310,7 +280,7 @@ def search(
self,
outfile=OST_INVENTORY_FILE,
append=False,
base_url="https://apihub.copernicus.eu/apihub",
base_url="https://catalogue.dataspace.copernicus.eu/resto/api/",
):
"""High Level search function
Expand All @@ -332,30 +302,39 @@ def search(
# construct the final query
query = urllib.parse.quote(f"Sentinel-1 AND {product_specs} AND {aoi} AND {toi}")

if not self.scihub_uname or not self.scihub_pword:
# create query
aoi = cop.create_aoi_str(self.aoi)
toi = cop.create_toi_str(self.start, self.end)
specs = cop.create_s1_product_specs(
self.product_type, self.polarisation, self.beam_mode
)
query = aoi + toi + specs + '&maxRecords=100'

if not self.dataspace_uname or not self.dataspace_pword:
# ask for username and password
self.scihub_uname, self.scihub_pword = scihub.ask_credentials()
self.dataspace_uname, self.dataspace_pword = cop.ask_credentials()

# do the search
if outfile == OST_INVENTORY_FILE:
self.inventory_file = self.inventory_dir / OST_INVENTORY_FILE
else:
self.inventory_file = outfile

search.scihub_catalogue(
base_url = base_url + 'collections/Sentinel1/search.json?'
search.dataspace_catalogue(
query,
self.inventory_file,
append,
self.scihub_uname,
self.scihub_pword,
self.dataspace_uname,
self.dataspace_pword,
base_url,
)

if self.inventory_file.exists():
# read inventory into the inventory attribute
self.read_inventory()
else:
logger.info("No images found in the AOI for this date range")
logger.info("No matching scenes found for the specified search parameters")

def read_inventory(self):
"""Read the Sentinel-1 data inventory from a OST invetory shapefile
Expand Down Expand Up @@ -568,7 +547,7 @@ def __init__(
data_mount=None,
product_type="SLC",
beam_mode="IW",
polarisation="*",
polarisation="VV VH",
ard_type="OST-GTC",
snap_cpu_parallelism=cpu_count(),
max_workers=1,
Expand Down
178 changes: 178 additions & 0 deletions ost/helpers/copernicus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"""This module provides helper functions for Copernicus Dataspace API."""

import getpass
import logging
from pathlib import Path
from datetime import datetime as dt

import requests
from shapely.wkt import loads

logger = logging.getLogger(__name__)

def ask_credentials():
"""Interactive function to ask for Copernicus credentials."""
print(
"If you do not have a Copernicus dataspace user account"
" go to: https://dataspace.copernicus.eu/ and register"
)
uname = input("Your Copernicus Dataspace Username:")
pword = getpass.getpass("Your Copernicus Dataspace Password:")

return uname, pword


def get_access_token(username, password: None):

if not password:
logger.info(' Please provide your Copernicus Dataspace password:')
password = getpass.getpass()

data = {
"client_id": "cdse-public",
"username": username,
"password": password,
"grant_type": "password",
}
try:
r = requests.post(
"https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token",
data=data,
)
r.raise_for_status()
except Exception as e:
raise Exception(
f"Access token creation failed. Reponse from the server was: {r.json()}"
)
return r.json()["access_token"]


def refresh_access_token(refresh_token: str) -> str:
data = {
"client_id": "cdse-public",
"refresh_token": refresh_token,
"grant_type": "refresh_token",
}

try:
r = requests.post(
"https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token",
data=data,
)
r.raise_for_status()
except Exception as e:
raise Exception(
f"Access token refresh failed. Reponse from the server was: {r.json()}"
)

return r.json()["access_token"]


def create_aoi_str(aoi):
"""Convert WKT formatted AOI to dataspace's geometry attribute."""
# load to shapely geometry to easily test for geometry type
geom = loads(aoi)

# dependent on the type construct the query string
if geom.geom_type == "Point":
return f'&lon={geom.y}&lat={geom.x}'

else:
# simplify geometry, as we might otherwise bump into too long string issue
aoi_convex = geom.convex_hull

# create scihub-confrom aoi string
return f'&geometry={aoi_convex}'

def create_toi_str(start="2014-10-01", end=dt.now().strftime("%Y-%m-%d")):
"""Convert start and end date to scihub's search url time period attribute."""
# bring start and end date to query format
return f"&startDate={start}T00:00:00Z&completionDate={end}T23:59:59Z"

def create_s1_product_specs(product_type=None, polarisation=None, beam=None):
"""Convert Sentinel-1's product metadata to scihub's product attributes."""
# transform product type, polarisation and beam to query format
product_type_query = f'&productType={product_type}' if product_type else ''
polarisation_query = f'&polarisation={polarisation.replace(" ", "%26")}' if polarisation else ''
sensor_mode_query = f'&sensorMode={beam}' if beam else ''

return product_type_query + polarisation_query + sensor_mode_query


def extract_basic_metadata(properties):

# those are the things we wnat out of the standard json
wanted = ['title', 'orbitDirection', 'platform', 'polarisation', 'swath', 'thumbnail', 'published']

# loop through all properties
_dict = {}
for k, v in properties.items():
# consider if in the list of wanted properties
if k in wanted:
if k == 'polarisation':
# remove & sign
_dict[k] = v.replace('&', ' ')
elif k == 'title':
# remove .SAFE extension
_dict[k] = v[:-5]
elif k == 'thumbnail':
_dict[k] = '/'.join(v.split('/')[:-2]) + '/manifest.safe'
else:
_dict[k] = v

sorted_dict = dict(sorted(_dict.items(), key=lambda item: wanted.index(item[0])))
return sorted_dict.values()


def get_entry(line):

return line.split('>')[1].split('<')[0]


def get_advanced_metadata(metafile, access_token):

with requests.Session() as session:
headers={'Authorization': f'Bearer {access_token}'}
request = session.request("get", metafile)
response = session.get(request.url, headers=headers, stream=True)

for line in response.iter_lines():

line = line.decode('utf-8')
if 's1sarl1:sliceNumber' in line:
slicenumber = get_entry(line)
if 's1sarl1:totalSlices' in line:
total_slices = get_entry(line)
if 'relativeOrbitNumber type="start"' in line:
relativeorbit = get_entry(line)
if 'relativeOrbitNumber type="stop"' in line:
lastrelativeorbit = get_entry(line)
if 'safe:nssdcIdentifier' in line:
platformidentifier = get_entry(line)
if 's1sarl1:missionDataTakeID' in line:
missiondatatakeid = get_entry(line)
if 's1sarl1:mode' in line:
sensoroperationalmode = get_entry(line)
if 'orbitNumber type="start"' in line:
orbitnumber = get_entry(line)
if 'orbitNumber type="stop"' in line:
lastorbitnumber = get_entry(line)
if 'safe:startTime' in line:
beginposition = get_entry(line)
if 'safe:stopTime' in line:
endposition = get_entry(line)
if '1sarl1:productType' in line:
product_type = get_entry(line)

# add acquisitiondate
acqdate = dt.strftime(dt.strptime(beginposition, '%Y-%m-%dT%H:%M:%S.%f'), format='%Y%m%d')

return (
slicenumber, total_slices,
relativeorbit, lastrelativeorbit,
platformidentifier, missiondatatakeid,
sensoroperationalmode, product_type,
orbitnumber, lastorbitnumber,
beginposition, endposition, acqdate,
0 # placeholder for size
)
4 changes: 2 additions & 2 deletions ost/helpers/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def polygonize_ls(infile, outfile, driver="GeoJSON"):
outfile,
"w",
driver=driver,
crs=pyproj.Proj(src.crs).srs,
crs=src.crs,
schema={"properties": [("raster_val", "int")], "geometry": "Polygon"},
) as dst:
dst.writerecords(results)
Expand Down Expand Up @@ -105,7 +105,7 @@ def polygonize_bounds(infile, outfile, mask_value=1, driver="GeoJSON"):
outfile,
"w",
driver=driver,
crs=pyproj.Proj(src.crs).srs,
crs=src.crs,
schema={"properties": [("raster_val", "int")], "geometry": "MultiPolygon"},
) as dst:
dst.writerecords(results)
Expand Down
1 change: 1 addition & 0 deletions ost/s1/burst_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def bursts_to_ards(burst_gdf, config_file):

logger.info("Preparing the processing pipeline. This may take a moment.")
proc_inventory = prepare_burst_inventory(burst_gdf, config_file)
#print(proc_inventory)

with open(config_file, "r") as file:
config_dict = json.load(file)
Expand Down
Loading

0 comments on commit 1c2514b

Please sign in to comment.