Skip to content
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
2 changes: 1 addition & 1 deletion .devcontainer/dev_container.dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Base image for the development container
ARG BASE_URL=python:3.11-slim
ARG BASE_URL=python:3.12-slim
FROM ${BASE_URL}

USER root
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/github-python-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.11"
python-version: "3.12"

- name: Install dependencies
run: |
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.11"
python-version: "3.12"

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion ogc/edr/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"admin": false,
"map": {
"url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
"attribution": "'&copy; <a href='https://openstreetmap.org/copyright'>OpenStreetMap contributors</a>'"
"attribution": "&copy; <a href=\"https://openstreetmap.org/copyright\">OpenStreetMap contributors</a>"
}
},
"logging": {
Expand Down
10 changes: 5 additions & 5 deletions ogc/edr/edr_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ def _resources_definition(base_url: str, layers: List[pogc.Layer]) -> Dict[str,
"data": group_name,
"base_url": base_url,
"crs": [
"https://www.opengis.net/def/crs/OGC/1.3/CRS84",
"https://www.opengis.net/def/crs/EPSG/0/4326",
settings.crs_84_uri_format,
settings.epsg_4326_uri_format,
],
"format": {
"name": "geotiff",
Expand Down Expand Up @@ -163,14 +163,14 @@ def _generate_extents(layers: List[pogc.Layer]) -> Dict[str, Any]:
return {
"spatial": {
"bbox": [llc_lon, llc_lat, urc_lon, urc_lat], # minx, miny, maxx, maxy
"crs": "https://www.opengis.net/def/crs/OGC/1.3/CRS84",
"crs": settings.crs_84_uri_format,
},
**(
{
"temporal": {
"begin": datetime.fromisoformat(time_range[0]), # start datetime in RFC3339
"end": datetime.fromisoformat(time_range[-1]), # end datetime in RFC3339
"trs": "https://www.opengis.net/def/uom/ISO-8601/0/Gregorian",
"trs": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian",
}
}
if time_range is not None
Expand Down Expand Up @@ -200,5 +200,5 @@ def _wgs84_bounding_box(layer: pogc.Layer) -> Tuple[float, float, float, float]:
layer.grid_coordinates.URC.lat,
)
except Exception:
crs_extents = settings.EDR_CRS["crs:84"]
crs_extents = settings.EDR_CRS[settings.crs_84_uri_format]
return (crs_extents["minx"], crs_extents["miny"], crs_extents["maxx"], crs_extents["maxy"])
4 changes: 2 additions & 2 deletions ogc/edr/edr_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,8 @@ def interpret_crs(crs: str | None) -> str:
ProviderInvalidQueryError
Raised if the provided CRS string is unknown.
"""
if crs is None or crs.lower() == "crs:84":
return settings.crs_84_pyproj_format # Pyproj acceptable format
if crs is None:
return settings.crs_84_uri_format # Pyproj acceptable format

if crs.lower() not in [key.lower() for key in settings.EDR_CRS.keys()]:
msg = f"Invalid CRS provided, expected one of {', '.join(settings.EDR_CRS.keys())}"
Expand Down
42 changes: 39 additions & 3 deletions ogc/edr/edr_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
class EdrRoutes(tl.HasTraits):
"""Class responsible for routing EDR requests to the appropriate pygeoapi API method."""

base_url = tl.Unicode(default_value="http://127.0.0.1:5000/")
base_url = tl.Unicode(default_value="http://127.0.0.1:5000/edr")
layers = tl.List(trait=tl.Instance(pogc.Layer))

def __init__(self, **kwargs):
Expand All @@ -40,6 +40,17 @@ def layers_change(self, change: Dict[str, Any]):
"""
self.api = self.create_api()

@tl.observe("base_url")
def base_url_change(self, change: Dict[str, Any]):
"""Monitor the base url and update the API when a change occurs.

Parameters
----------
change : Dict[str, Any]
Dictionary holding type of modification and name of the attribute that triggered it.
"""
self.api = self.create_api()

def create_api(self) -> pygeoapi.api.API:
"""Create the pygeoapi API using a custom configuration.

Expand All @@ -50,8 +61,8 @@ def create_api(self) -> pygeoapi.api.API:
"""
# Allow specifying GeoTiff or CoverageJSON in the format argument.
# This is a bypass which is needed to get by a conditional check in pygeoapi.
pygeoapi.plugin.PLUGINS["formatter"]["GeoTiff"] = ""
pygeoapi.plugin.PLUGINS["formatter"]["CoverageJSON"] = ""
pygeoapi.plugin.PLUGINS["formatter"]["geotiff"] = ""
pygeoapi.plugin.PLUGINS["formatter"]["coveragejson"] = ""
EdrProvider.set_layers(self.base_url, self.layers)
config = EdrConfig.get_configuration(self.base_url, self.layers)
open_api = get_oas(config, fail_on_invalid_collection=False)
Expand All @@ -61,6 +72,24 @@ def clean_configuration_cache(self):
"""Clean a pygeoapi internal translation cache so that multiple configurations can be used simultaneously."""
pygeoapi.l10n._cfg_cache = {}

def update_configuration_base_url(self, request: pygeoapi.api.APIRequest):
"""Update the EDR configuration base URL based on the provided request.
The EDR configuration base URL does not necessarily match the full path of the request base URL.

Parameters
----------
request : pygeoapi.api.APIRequest
The API request containing a base URL.
The base URL should include scheme, host, and path for the request.
"""
request_base_url = request.params.get("base_url", "")
# Limit the base URL from the request to the EDR subdirectory
base_url_partitioned = request_base_url.partition("/edr")
if len(base_url_partitioned[0]) > 0:
configuration_base_url = base_url_partitioned[0] + base_url_partitioned[1]
if configuration_base_url != self.base_url:
self.base_url = configuration_base_url

def static_files(self, request: pygeoapi.api.APIRequest, file_path: str) -> Tuple[dict, int, str | bytes]:
"""Handle static file requests using the custom static file folder or the pygeoapi default folder.

Expand All @@ -75,6 +104,7 @@ def static_files(self, request: pygeoapi.api.APIRequest, file_path: str) -> Tupl
Headers, HTTP Status, and Content returned as a tuple to make the server response.
"""
self.clean_configuration_cache()
self.update_configuration_base_url(request)
static_path = os.path.join(os.path.dirname(pygeoapi.__file__), "static")
if "templates" in self.api.config["server"]:
static_path = self.api.config["server"]["templates"].get("static", static_path)
Expand Down Expand Up @@ -102,6 +132,7 @@ def landing_page(self, request: pygeoapi.api.APIRequest) -> Tuple[dict, int, str
Headers, HTTP Status, and Content returned as a tuple to make the server response.
"""
self.clean_configuration_cache()
self.update_configuration_base_url(request)
return pygeoapi.api.landing_page(self.api, request)

def openapi(self, request: pygeoapi.api.APIRequest) -> Tuple[dict, int, str | bytes]:
Expand All @@ -118,6 +149,7 @@ def openapi(self, request: pygeoapi.api.APIRequest) -> Tuple[dict, int, str | by
Headers, HTTP Status, and Content returned as a tuple to make the server response.
"""
self.clean_configuration_cache()
self.update_configuration_base_url(request)
return pygeoapi.api.openapi_(self.api, request)

def conformance(self, request: pygeoapi.api.APIRequest) -> Tuple[dict, int, str | bytes]:
Expand All @@ -134,6 +166,7 @@ def conformance(self, request: pygeoapi.api.APIRequest) -> Tuple[dict, int, str
Headers, HTTP Status, and Content returned as a tuple to make the server response.
"""
self.clean_configuration_cache()
self.update_configuration_base_url(request)
return pygeoapi.api.conformance(self.api, request)

def describe_collections(
Expand All @@ -156,6 +189,7 @@ def describe_collections(
Headers, HTTP Status, and Content returned as a tuple to make the server response.
"""
self.clean_configuration_cache()
self.update_configuration_base_url(request)
return pygeoapi.api.describe_collections(self.api, request, collection_id)

def describe_instances(
Expand All @@ -181,6 +215,7 @@ def describe_instances(
Headers, HTTP Status, and Content returned as a tuple to make the server response.
"""
self.clean_configuration_cache()
self.update_configuration_base_url(request)
return pygeoedr.get_collection_edr_instances(self.api, request, collection_id, instance_id=instance_id)

def collection_query(
Expand Down Expand Up @@ -209,6 +244,7 @@ def collection_query(
Headers, HTTP Status, and Content returned as a tuple to make the server response.
"""
self.clean_configuration_cache()
self.update_configuration_base_url(request)
headers, http_status, content = pygeoedr.get_collection_edr_query(
self.api, request, collection_id, instance_id, query_type=query_type, location_id=None
)
Expand Down
86 changes: 86 additions & 0 deletions ogc/edr/static/css/default.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.flat {
border: 0px;
}

header {
display: inline-block;
}

main {
background-color: white;
}

.crumbs {
background-color: rgb(230, 230, 230);
padding: 6px;
}

.crumbs a {
padding: 0px 6px;
color: black;
text-decoration: none;
/* text-transform: capitalize;*/
}

#items-map,
#collection-map {
width: 100%;
height: 400px;
}

#coverages-map {
width: 100%;
height: 80vh;
}

.c3-tooltip-container {
z-index: 300;
}

/* cancel mini-css header>button uppercase */
header button,
header [type="button"],
header .button,
header [role="button"] {
text-transform: none;
}

html {
background-color: #fff;
}

body {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #fff;
}

footer.sticky-bottom {
margin-top: auto;
}

main {
padding-bottom: 65px;
/* prevent from falling under the footer */
}

table:not(.horizontal) {
max-height: none;
}

mark.successful {
background-color: green;
}

mark.accepted {
background-color: default;
}

mark.failed {
background-color: red;
}

mark.running {
background-color: orange;
}
7 changes: 4 additions & 3 deletions ogc/edr/test/test_edr_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def get_provider_definition(base_url: str) -> Dict[str, Any]:
"name": "ogc.edr.edr_provider.EdrProvider",
"data": "Layers",
"base_url": base_url,
"crs": ["https://www.opengis.net/def/crs/OGC/1.3/CRS84", "https://www.opengis.net/def/crs/EPSG/0/4326"],
"crs": ["http://www.opengis.net/def/crs/OGC/1.3/CRS84", "http://www.opengis.net/def/crs/EPSG/0/4326"],
"format": {"name": "GeoJSON", "mimetype": "application/json"},
}

Expand Down Expand Up @@ -573,12 +573,13 @@ def test_edr_provider_altitude_invalid_string():

def test_edr_provider_crs_interpreter_default_value():
"""Test the CRS interpretation returns a default value when the argument is None."""
assert EdrProvider.interpret_crs(None) == "urn:ogc:def:crs:OGC:1.3:CRS84"
assert EdrProvider.interpret_crs(None) == "http://www.opengis.net/def/crs/OGC/1.3/CRS84"


def test_edr_provider_crs_interpreter_valid_value():
"""Test the CRS interpretation returns a valid value when the argument is acceptable."""
assert EdrProvider.interpret_crs("epsg:4326") == "epsg:4326"
crs = "http://www.opengis.net/def/crs/EPSG/0/4326"
assert EdrProvider.interpret_crs(crs) == crs


def test_edr_provider_crs_interpreter_invalid_value():
Expand Down
15 changes: 14 additions & 1 deletion ogc/edr/test/test_edr_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def mock_request(request_args: Dict[str, Any] = {}) -> APIRequest:
APIRequest
Mock API request for route testing.
"""
environ = create_environ(base_url="http://127.0.0.1:5000/ogc/")
environ = create_environ(base_url="http://127.0.0.1:5000/ogc/edr")
request = Request(environ)
request.args = ImmutableMultiDict(request_args.items())
return APIRequest(request, ["en"])
Expand Down Expand Up @@ -336,3 +336,16 @@ def test_edr_routes_collection_query_missing_parameter(
)

assert status == HTTPStatus.BAD_REQUEST


def test_edr_routes_request_url_updates_configuration_url():
"""Test the EDR routes request base URL updates the configuration URL."""
request_url = "http://test:5000/ogc/edr/static/img/logo.png"
expected_config_url = "http://test:5000/ogc/edr"
request = mock_request({"base_url": request_url})
edr_routes = EdrRoutes(layers=[])

_, status, _ = edr_routes.static_files(request, "img/logo.png")

assert status == HTTPStatus.OK
assert edr_routes.api.config["server"]["url"] == expected_config_url
10 changes: 10 additions & 0 deletions ogc/servers.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ def home(endpoint):
<li><a href="?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetLegendGraphic&LAYER={test_layer}&STYLE=default&FORMAT=image/png">WMS GetLegend Example (PNG)</a> <i>(v1.3.0)</i></li>
</ul>
</li>
<li> EDR: Open Geospatial Consortium (OGC) Environmental Data Retrieval (EDR) <i>(v1.0.1)</i>
<ul>
<li><a href="{endpoint}/edr?f=html">EDR Landing Page (HTML)</a> <i>(v1.0.1)</i></li>
<li><a href="{endpoint}/edr/conformance?f=json">EDR Conformance (JSON)</a> <i>(v1.0.1)</i></li>
<li><a href="{endpoint}/edr/collections?f=json">EDR Collections (JSON)</a> <i>(v1.0.1)</i></li>
</ul>
</li>
</ul>
"""

Expand Down Expand Up @@ -297,6 +304,9 @@ def wrapper(*args, **kwargs) -> Response:
# Replace format with its lowercase version to match pygeoapi expectations
if filtered_args.get("f", None):
filtered_args["f"] = filtered_args["f"].lower()

filtered_args["base_url"] = request.base_url

# Replace the arguments with the filtered option
request.args = ImmutableMultiDict(filtered_args)
pygeoapi_request = APIRequest.from_flask(request, ["en"])
Expand Down
8 changes: 4 additions & 4 deletions ogc/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

# Settings applied around the OGC server package.
crs_84 = "crs:84"
crs_84_pyproj_format = "urn:ogc:def:crs:OGC:1.3:CRS84"
epsg_4326 = "epsg:4326"
crs_84_uri_format = "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
epsg_4326_uri_format = "http://www.opengis.net/def/crs/EPSG/0/4326"

# Default/Supported WMS CRS/SRS
WMS_CRS = {
Expand All @@ -32,9 +33,8 @@
crs_84: {"minx": -180, "miny": -90, "maxx": 180, "maxy": 90},
}
EDR_CRS = {
epsg_4326: {"minx": -90.0, "miny": -180.0, "maxx": 90.0, "maxy": 180.0},
crs_84: {"minx": -180.0, "miny": -90.0, "maxx": 180.0, "maxy": 90.0},
crs_84_pyproj_format: {"minx": -180.0, "miny": -90.0, "maxx": 180.0, "maxy": 90.0},
epsg_4326_uri_format: {"minx": -90.0, "miny": -180.0, "maxx": 90.0, "maxy": 180.0},
crs_84_uri_format: {"minx": -180.0, "miny": -90.0, "maxx": 180.0, "maxy": 90.0},
}

# WMS Capabilities timestamp format
Expand Down
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
sonar.projectKey=creare-com_ogc
sonar.organization=creare-com
sonar.qualitygate.wait=true
sonar.python.version=3.11
sonar.python.version=3.12
sonar.python.coverage.reportPaths=coverage.xml
Loading