Skip to content

Commit

Permalink
custom projection handling
Browse files Browse the repository at this point in the history
  • Loading branch information
lubojr committed Jul 12, 2024
1 parent 453abe5 commit 9468d87
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 36 deletions.
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}")
18 changes: 17 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,19 @@ 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 it did overwrite 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'

0 comments on commit 9468d87

Please sign in to comment.