Commit f3b18efb authored by Remi Cresson's avatar Remi Cresson
Browse files

WIP: PC --> OK

1 merge request!50Support de STAC
Showing with 65 additions and 57 deletions
+65 -57
...@@ -6,7 +6,7 @@ def main(): ...@@ -6,7 +6,7 @@ def main():
bbox = scenes.BoundingBox(xmin=2, ymin=45.23, xmax=2.1, ymax=45.26) bbox = scenes.BoundingBox(xmin=2, ymin=45.23, xmax=2.1, ymax=45.26)
provider = scenes.stac.DinamisSpot67Provider() provider = scenes.stac.DinamisSpot67Provider()
scs = provider.search(bbox_wgs84=bbox) scs = provider.scenes_search(bbox_wgs84=bbox)
for i, sc in enumerate(scs): for i, sc in enumerate(scs):
print(sc) print(sc)
pyotb.HaralickTextureExtraction({"in": sc.get_xs(), "out": f"/data/tex_{i}.img"}) pyotb.HaralickTextureExtraction({"in": sc.get_xs(), "out": f"/data/tex_{i}.img"})
......
...@@ -6,9 +6,10 @@ def main(): ...@@ -6,9 +6,10 @@ def main():
bbox = scenes.BoundingBox(xmin=2, ymin=45.23, xmax=2.1, ymax=45.26) bbox = scenes.BoundingBox(xmin=2, ymin=45.23, xmax=2.1, ymax=45.26)
provider = scenes.stac.MPCProvider() provider = scenes.stac.MPCProvider()
scs = provider.search(bbox_wgs84=bbox) results = provider.stac_search(bbox_wgs84=bbox)
for i, sc in enumerate(scs): for i, result in enumerate(results):
print(sc) print(result)
sc = provider.stac_item_to_scene(result)
dyn = pyotb.DynamicConvert(sc.get_10m_bands()[0:256, 0:256, :]) dyn = pyotb.DynamicConvert(sc.get_10m_bands()[0:256, 0:256, :])
dyn.write(f"/tmp/{i}.png") dyn.write(f"/tmp/{i}.png")
......
...@@ -23,6 +23,7 @@ import tempfile ...@@ -23,6 +23,7 @@ import tempfile
import datetime import datetime
import pystac import pystac
from pystac_client import Client from pystac_client import Client
from scenes.core import Scene
from scenes.spatial import BoundingBox from scenes.spatial import BoundingBox
from scenes.dates import any2datetime from scenes.dates import any2datetime
from scenes.spot import Spot67DRSScene from scenes.spot import Spot67DRSScene
...@@ -31,16 +32,20 @@ from scenes.auth import OAuth2KeepAlive ...@@ -31,16 +32,20 @@ from scenes.auth import OAuth2KeepAlive
import requests import requests
from tqdm.autonotebook import tqdm from tqdm.autonotebook import tqdm
import threading import threading
from abc import abstractmethod
class ProviderBase: class ProviderBase:
def __init__(self, url): def __init__(self, url, default_collections):
""" """
Args: Args:
url: STAC endpoint url: STAC endpoint
default_collections: default collections
""" """
assert url assert url
self.url = url self.url = url
self.default_collections = default_collections
self.vsicurl_media_types = [pystac.MediaType.GEOPACKAGE, self.vsicurl_media_types = [pystac.MediaType.GEOPACKAGE,
pystac.MediaType.GEOJSON, pystac.MediaType.GEOJSON,
pystac.MediaType.COG, pystac.MediaType.COG,
...@@ -48,15 +53,16 @@ class ProviderBase: ...@@ -48,15 +53,16 @@ class ProviderBase:
pystac.MediaType.TIFF, pystac.MediaType.TIFF,
pystac.MediaType.JPEG2000] pystac.MediaType.JPEG2000]
def stac_search(self, collections: list[str], bbox_wgs84: BoundingBox, date_min: datetime.datetime | str = None, def stac_search(self, bbox_wgs84: BoundingBox, collections: list[str] = None,
date_max: datetime.datetime | str = None, filt: dict = None, query: dict = None): date_min: datetime.datetime | str = None, date_max: datetime.datetime | str = None,
filt: dict = None, query: dict = None):
""" """
Search an item in a STAC catalog. Search an item in a STAC catalog.
see https://pystac-client.readthedocs.io/en/latest/api.html#pystac_client.Client.search see https://pystac-client.readthedocs.io/en/latest/api.html#pystac_client.Client.search
Args: Args:
collections: names of the collections to search
bbox_wgs84: The bounding box in WGS84 (BoundingBox instance) bbox_wgs84: The bounding box in WGS84 (BoundingBox instance)
collections: names of the collections to search
date_min: date min (datetime.datetime or str) date_min: date min (datetime.datetime or str)
date_max: date max (datetime.datetime or str) date_max: date max (datetime.datetime or str)
filt: JSON of query parameters as per the STAC API filter extension filt: JSON of query parameters as per the STAC API filter extension
...@@ -67,10 +73,28 @@ class ProviderBase: ...@@ -67,10 +73,28 @@ class ProviderBase:
""" """
dt = [any2datetime(date) for date in [date_min, date_max] if date] if date_min or date_max else None dt = [any2datetime(date) for date in [date_min, date_max] if date] if date_min or date_max else None
api = Client.open(self.url) api = Client.open(self.url)
results = api.search(max_items=None, bbox=bbox_wgs84.to_list(), datetime=dt, collections=collections, results = api.search(max_items=None, bbox=bbox_wgs84.to_list(), datetime=dt, filter=filt, query=query,
filter=filt, query=query) collections=self.default_collections if not collections else collections)
return results.items() return results.items()
def scenes_search(self, *args, as_generator=False, **kwargs) -> list[Scene]:
"""
Perform a STAC search then converts the resulting items into `Scene` objects
Args:
*args: same args as stac_search()
as_generator: return scenes as generator, or not
**kwargs: same kwargs as stac_search()
Returns: a list of `Scenes`
"""
items = self.stac_search(*args, **kwargs)
gen = (self.stac_item_to_scene(item) for item in tqdm(items))
if as_generator:
return gen
return list(gen)
def get_asset_path(self, asset: pystac.asset) -> str: def get_asset_path(self, asset: pystac.asset) -> str:
""" """
Return the URI suited for GDAL if the asset is some geospatial data. Return the URI suited for GDAL if the asset is some geospatial data.
...@@ -89,10 +113,24 @@ class ProviderBase: ...@@ -89,10 +113,24 @@ class ProviderBase:
return f"/vsicurl/{url}" return f"/vsicurl/{url}"
return url return url
@abstractmethod
def stac_item_to_scene(self, item: pystac.item) -> Scene:
"""
Convert a STAC item into a `Scene`
Args:
item: STAC item
Returns: scene
"""
raise NotImplementedError("")
class DinamisSpot67Provider(ProviderBase): class DinamisSpot67Provider(ProviderBase):
def __init__(self, url="https://stacapi.147.100.200.143.nip.io", auth=None): def __init__(self, auth=None):
super().__init__(url=url) super().__init__(url="https://stacapi.147.100.200.143.nip.io",
default_collections=["spot-6-7-drs"])
self.temp_dir = tempfile.TemporaryDirectory() self.temp_dir = tempfile.TemporaryDirectory()
self.headers_file = os.path.join(self.temp_dir.name, 'headers.txt') self.headers_file = os.path.join(self.temp_dir.name, 'headers.txt')
self.__auth_headers = None self.__auth_headers = None
...@@ -111,7 +149,6 @@ class DinamisSpot67Provider(ProviderBase): ...@@ -111,7 +149,6 @@ class DinamisSpot67Provider(ProviderBase):
with self.__auth_headers_lock: with self.__auth_headers_lock:
self.__auth_headers = {"Authorization": f"Bearer {access_token}"} self.__auth_headers = {"Authorization": f"Bearer {access_token}"}
def get_auth_headers(self): def get_auth_headers(self):
with self.__auth_headers_lock: with self.__auth_headers_lock:
return self.__auth_headers return self.__auth_headers
...@@ -132,7 +169,6 @@ class DinamisSpot67Provider(ProviderBase): ...@@ -132,7 +169,6 @@ class DinamisSpot67Provider(ProviderBase):
return url return url
def update_headers(self, token): def update_headers(self, token):
print("update headers")
self.set_auth_headers(token=token) self.set_auth_headers(token=token)
tmp_headers_file = f"{self.headers_file}.tmp" tmp_headers_file = f"{self.headers_file}.tmp"
old_headers_file = f"{self.headers_file}.old" old_headers_file = f"{self.headers_file}.old"
...@@ -143,38 +179,24 @@ class DinamisSpot67Provider(ProviderBase): ...@@ -143,38 +179,24 @@ class DinamisSpot67Provider(ProviderBase):
os.rename(self.headers_file, old_headers_file) os.rename(self.headers_file, old_headers_file)
os.rename(tmp_headers_file, self.headers_file) os.rename(tmp_headers_file, self.headers_file)
def search(self, bbox_wgs84: BoundingBox, date_min: datetime.datetime | str = None, def stac_item_to_scene(self, item: pystac.item) -> Scene:
date_max: datetime.datetime | str = None, collections: list[str] = None,
as_generator=False) -> list[Spot67DRSScene]:
""" """
Search and instantiate S2 products from Microsoft Convert a STAC item into a `Spot67DRSScene`
Args: Args:
bbox_wgs84: The bounding box in WGS84 (BoundingBox instance) item: STAC item
date_min: date min (datetime.datetime or str)
date_max: date max (datetime.datetime or str)
collections: list of collections
as_generator: return the scenes as generator
Returns: Returns: scene
`Spot67DRSScene` instances in a list
""" """
if not collections: return Spot67DRSScene(assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()},
collections = ["spot-6-7-drs"] assets_headers=self.get_auth_headers())
items = self.stac_search(collections=collections, bbox_wgs84=bbox_wgs84, date_min=date_min, date_max=date_max)
gen = (Spot67DRSScene(
assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()},
assets_headers=self.get_auth_headers()
) for item in tqdm(items))
if as_generator:
return gen
return list(gen)
class MPCProvider(ProviderBase): class MPCProvider(ProviderBase):
def __init__(self, url="https://planetarycomputer.microsoft.com/api/stac/v1"): def __init__(self):
super().__init__(url=url) super().__init__(url="https://planetarycomputer.microsoft.com/api/stac/v1",
default_collections="sentinel-2-l2a")
def get_asset_path(self, asset: pystac.asset) -> str: def get_asset_path(self, asset: pystac.asset) -> str:
""" """
...@@ -198,29 +220,14 @@ class MPCProvider(ProviderBase): ...@@ -198,29 +220,14 @@ class MPCProvider(ProviderBase):
return f"/vsicurl/{new_url}" return f"/vsicurl/{new_url}"
return url return url
def search(self, bbox_wgs84: BoundingBox, date_min: datetime.datetime | str = None, def stac_item_to_scene(self, item: pystac.item) -> Scene:
date_max: datetime.datetime | str = None, collections: list[str] = None,
as_generator=True) -> list[Sentinel2MPCScene]:
""" """
Search and instantiate Spot-6/7 DRS products from Dinamis Convert a STAC item into a `Sentinel2MPCScene`
Args: Args:
bbox_wgs84: The bounding box in WGS84 (BoundingBox instance) item: STAC item
date_min: date min (datetime.datetime or str)
date_max: date max (datetime.datetime or str)
collections: list of collections
as_generator: return the scenes as generator
Returns: Returns: scene
`Sentinel2MPCScene` instances in a list
""" """
if not collections: return Sentinel2MPCScene(assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()})
collections = ["sentinel-2-l2a"]
items = self.stac_search(collections=collections, bbox_wgs84=bbox_wgs84, date_min=date_min, date_max=date_max)
gen = (Sentinel2MPCScene(
assets_paths={key: self.get_asset_path(asset) for key, asset in item.assets.items()},
) for item in tqdm(items))
if as_generator:
return gen
return list(gen)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment