Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom projection handling #11

Merged
merged 2 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions src/eodash_catalog/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from eodash_catalog.stac_handling import (
add_collection_information,
add_example_info,
add_projection_info,
get_collection_times_from_config,
get_or_create_collection,
)
Expand Down Expand Up @@ -264,7 +265,10 @@ def process_STACAPI_Endpoint(
else:
link.extra_fields["start_datetime"] = item.properties["start_datetime"]
link.extra_fields["end_datetime"] = item.properties["end_datetime"]

add_projection_info(
endpoint_config,
item,
)
collection.update_extent_from_items()

# replace SH identifier with catalog identifier
Expand Down Expand Up @@ -347,6 +351,7 @@ def handle_SH_WMS_endpoint(
"https://stac-extensions.github.io/web-map-links/v1.1.0/schema.json",
],
)
add_projection_info(endpoint_config, item)
add_visualization_info(item, collection_config, endpoint_config, time=time)
item_link = collection.add_item(item)
item_link.extra_fields["datetime"] = time
Expand Down Expand Up @@ -530,6 +535,7 @@ def handle_WMS_endpoint(
"https://stac-extensions.github.io/web-map-links/v1.1.0/schema.json",
],
)
add_projection_info(endpoint_config, item)
add_visualization_info(item, collection_config, endpoint_config, time=t)
link = collection.add_item(item)
link.extra_fields["datetime"] = t
Expand Down Expand Up @@ -645,6 +651,7 @@ def add_visualization_info(
if "Rescale" in endpoint_config:
vmin = endpoint_config["Rescale"][0]
vmax = endpoint_config["Rescale"][1]
# depending on numerical input only
data_projection = endpoint_config.get("DataProjection", 3857)
epsg_prefix = "" if "EPSG:" in data_projection else "EPSG:"
crs = f"{epsg_prefix}{data_projection}"
Expand Down Expand Up @@ -780,19 +787,18 @@ def handle_raw_source(
)
if len(endpoint_config.get("TimeEntries", [])) > 0:
for time_entry in endpoint_config["TimeEntries"]:
extra_fields = {}
if "DataProjection" in endpoint_config:
extra_fields["proj:epsg"] = endpoint_config["DataProjection"]
assets = {}
media_type = "application/geo+json"
style_type = "text/vector-styles"
if endpoint_config["Name"] == "COG source":
style_type = "text/cog-styles"
media_type = "image/tiff"
for a in time_entry["Assets"]:
assets[a["Identifier"]] = Asset(
href=a["File"], roles=["data"], media_type=media_type, extra_fields=extra_fields
asset = Asset(
href=a["File"], roles=["data"], media_type=media_type, extra_fields={}
)
add_projection_info(endpoint_config, asset)
assets[a["Identifier"]] = asset
bbox = endpoint_config.get("Bbox", [-180, -85, 180, 85])
item = Item(
id=time_entry["Time"],
Expand All @@ -801,7 +807,11 @@ def handle_raw_source(
geometry=create_geojson_from_bbox(bbox),
datetime=parser.isoparse(time_entry["Time"]),
assets=assets,
extra_fields=extra_fields,
extra_fields={},
)
add_projection_info(
endpoint_config,
item,
)
ep_st = endpoint_config["Style"]
style_link = Link(
Expand All @@ -818,8 +828,6 @@ def handle_raw_source(
link = collection.add_item(item)
link.extra_fields["datetime"] = time_entry["Time"]
link.extra_fields["assets"] = [a["File"] for a in time_entry["Assets"]]
if "DataProjection" in endpoint_config:
collection.extra_fields["proj:epsg"] = endpoint_config["DataProjection"]
add_collection_information(catalog_config, collection, collection_config)
collection.update_extent_from_items()
return collection
2 changes: 2 additions & 0 deletions src/eodash_catalog/generate_indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
add_base_overlay_info,
add_collection_information,
add_extra_fields,
add_projection_info,
get_or_create_collection,
)
from eodash_catalog.utils import (
Expand Down Expand Up @@ -249,6 +250,7 @@ def process_collection_file(
raise ValueError("Type of Resource is not supported")
if collection:
add_single_item_if_collection_empty(collection)
add_projection_info(resource, collection)
add_to_catalog(collection, catalog, resource, collection_config)
else:
raise Exception(f"No collection was generated for resource {resource}")
Expand Down
83 changes: 57 additions & 26 deletions src/eodash_catalog/stac_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Catalog,
Collection,
Extent,
Item,
Link,
Provider,
SpatialExtent,
Expand Down Expand Up @@ -87,36 +88,39 @@ def get_or_create_collection(
return collection


def create_web_map_link(layer: dict, role: str) -> Link:
def create_web_map_link(layer_config: dict, role: str) -> Link:
extra_fields = {
"roles": [role],
"id": layer["id"],
"id": layer_config["id"],
}
if layer.get("default"):
if layer_config.get("default"):
extra_fields["roles"].append("default")
if layer.get("visible"):
if layer_config.get("visible"):
extra_fields["roles"].append("visible")
if "visible" in layer and not layer["visible"]:
if "visible" in layer_config and not layer_config["visible"]:
extra_fields["roles"].append("invisible")

match layer["protocol"]:
match layer_config["protocol"].lower():
case "wms":
# handle wms special config options
extra_fields["wms:layers"] = layer["layers"]
if "styles" in layer:
extra_fields["wms:styles"] = layer["styles"]
# TODO: handle wms dimensions extra_fields["wms:dimensions"]
extra_fields["wms:layers"] = layer_config["layers"]
if "styles" in layer_config:
extra_fields["wms:styles"] = layer_config["styles"]
if "dimensions" in layer_config:
extra_fields["wms:dimensions"] = layer_config["dimensions"]
case "wmts":
extra_fields["wmts:layer"] = layer["layer"]
# TODO: handle wmts dimensions extra_fields["wmts:dimensions"]
extra_fields["wmts:layer"] = layer_config["layer"]
if "dimensions" in layer_config:
extra_fields["wmts:dimensions"] = layer_config["dimensions"]

wml = Link(
rel=layer["protocol"],
target=layer["url"],
media_type=layer.get("media_type", "image/png"),
title=layer["name"],
rel=layer_config["protocol"],
target=layer_config["url"],
media_type=layer_config.get("media_type", "image/png"),
title=layer_config["name"],
extra_fields=extra_fields,
)
add_projection_info(layer_config, wml)
return wml


Expand Down Expand Up @@ -321,25 +325,26 @@ def add_collection_information(
def add_base_overlay_info(
collection: Collection, catalog_config: dict, collection_config: dict
) -> None:
# check if default base layers defined
if "default_base_layers" in catalog_config:
# add custom baselayers specially for this indicator
if "BaseLayers" in collection_config:
for layer in collection_config["BaseLayers"]:
collection.add_link(create_web_map_link(layer, role="baselayer"))
# alternatively use default base layers defined
elif "default_base_layers" in catalog_config:
with open(f'{catalog_config["default_base_layers"]}.yaml') as f:
base_layers = yaml.load(f, Loader=SafeLoader)
for layer in base_layers:
collection.add_link(create_web_map_link(layer, role="baselayer"))
# add custom overlays just for this indicator
if "OverlayLayers" in collection_config:
for layer in collection_config["OverlayLayers"]:
collection.add_link(create_web_map_link(layer, role="overlay"))
# check if default overlay layers defined
if "default_overlay_layers" in catalog_config:
elif "default_overlay_layers" in catalog_config:
with open("{}.yaml".format(catalog_config["default_overlay_layers"])) as f:
overlay_layers = yaml.load(f, Loader=SafeLoader)
for layer in overlay_layers:
collection.add_link(create_web_map_link(layer, role="overlay"))
if "BaseLayers" in collection_config:
for layer in collection_config["BaseLayers"]:
collection.add_link(create_web_map_link(layer, role="baselayer"))
if "OverlayLayers" in collection_config:
for layer in collection_config["OverlayLayers"]:
collection.add_link(create_web_map_link(layer, role="overlay"))
# TODO: possibility to overwrite default base and overlay layers


def add_extra_fields(stac_object: Collection | Catalog | Link, collection_config: dict) -> None:
Expand Down Expand Up @@ -388,3 +393,29 @@ def get_collection_times_from_config(endpoint_config: dict) -> list[str]:
timedelta_config = endpoint_config["DateTimeInterval"].get("Timedelta", {"days": 1})
times = generateDateIsostringsFromInterval(start, end, timedelta_config)
return times


def add_projection_info(
endpoint_config: dict, stac_object: Item | Asset | Collection | Link
) -> None:
if proj := endpoint_config.get("DataProjection"):
if isinstance(proj, str):
if proj.lower().startswith("epsg"):
# consider input such as "EPSG:4326"
proj = proj.lower().split("EPSG:")[1]
# consider a number only
proj = int(proj)
if isinstance(proj, int):
# only set if not existing on source stac_object
if not stac_object.extra_fields.get("proj:epsg"):
# handling EPSG code for "proj:epsg"
stac_object.extra_fields["proj:epsg"] = proj
elif isinstance(proj, dict):
# custom handling due to incompatibility of proj4js supported syntax (WKT1)
# and STAC supported syntax (projjson or WKT2)
# so we are taking over the DataProjection as is and deal with it in the eodash client
# in a non-standard compliant way
# https://github.com/proj4js/proj4js/issues/400
stac_object.extra_fields["proj4_def"] = proj
else:
raise Exception(f"Incorrect type of proj definition {proj}")
19 changes: 18 additions & 1 deletion tests/test_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def test_geojson_dataset_handled(catalog_output_folder):
== "https://raw.githubusercontent.com/eodash/eodash_catalog/main/tests/test-data/regional_forecast.json"
)
# epsg code saved on collection
assert collection_json.get("proj:epsg", "") == 3035
assert collection_json["proj:epsg"] == 3035
with open(os.path.join(item_dir, item_paths[0])) as fp:
item_json = json.load(fp)
# mimetype saved correctly
Expand All @@ -168,3 +168,20 @@ def test_cog_dataset_handled(catalog_output_folder):
item_json = json.load(fp)
assert item_json["assets"]["solar_power"]["type"] == "image/tiff"
assert item_json["collection"] == collection_name


def test_baselayer_with_custom_projection_added(catalog_output_folder):
collection_name = "test_indicator_grouping_collections"
root_collection_path = os.path.join(catalog_output_folder, collection_name)
with open(os.path.join(root_collection_path, "collection.json")) as fp:
indicator_json = json.load(fp)
baselayer_links = [
link
for link in indicator_json["links"]
if link.get("roles") and "baselayer" in link["roles"]
]
# test that manual BaseLayers definition
# overwrites default_baselayers, so there is just 1
assert len(baselayer_links) == 1
# test that custom proj4 definition is added to link
assert baselayer_links[0]["proj4_def"]["name"] == "ORTHO:680500"
12 changes: 12 additions & 0 deletions tests/testing-indicators/test_indicator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,15 @@ MapProjection: 3035
Collections:
- test_tif_demo_1
- test_tif_demo_2
BaseLayers:
- id: sx-cat_ortho680500
name: Terrain Light Stereographic North
url: '//sxcat-demo.eox.at/sxcat_maps/wms'
layers: sx-cat_ortho680500
attribution: '{ Terrain light: Data &copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors and <a href="//maps.eox.at/#data" target="_blank">others</a>, Rendering &copy; <a href="http://eox.at" target="_blank">EOX</a> }'
visible: false
protocol: wms
DataProjection:
name: 'ORTHO:680500'
def: '+proj=ortho +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +units=m +no_defs'