Skip to content
Open
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
50 changes: 50 additions & 0 deletions core/feature_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@

import logging

from geoalchemy2.functions import (
ST_Intersects,
ST_MakeEnvelope,
ST_MakePoint,
ST_SetSRID,
)
from pygeoapi.provider.sql import PostgreSQLProvider
from shapely.geometry import Point, mapping

from core.ogc_field_metadata import describe_fields

Expand All @@ -36,6 +43,49 @@
class DescribedPostgreSQLProvider(PostgreSQLProvider):
"""PostgreSQLProvider that annotates reflected columns with prose."""

def __init__(self, provider_def):
self.x_field = provider_def.get("x_field")
self.y_field = provider_def.get("y_field")
super().__init__(provider_def)

def _select_properties_clause(self, select_properties, skip_geometry):
if self.geom is None:
# Include coordinates even when the caller selects other properties.
if select_properties and not skip_geometry:
select_properties = list(
dict.fromkeys([*select_properties, self.x_field, self.y_field])
)
skip_geometry = True
return super()._select_properties_clause(select_properties, skip_geometry)

def _sqlalchemy_to_feature(
self, item, crs_transform_out=None, select_properties=None
):
feature = super()._sqlalchemy_to_feature(
item, crs_transform_out, select_properties
)
if self.geom is None:
x = item.__dict__.get(self.x_field)
y = item.__dict__.get(self.y_field)
if x is not None and y is not None:
point = Point(x, y)
if crs_transform_out is not None:
point = crs_transform_out(point)
feature["geometry"] = mapping(point)
return feature

def _get_bbox_filter(self, bbox):
if self.geom is not None or not bbox:
return super()._get_bbox_filter(bbox)
point = ST_SetSRID(
ST_MakePoint(
getattr(self.table_model, self.x_field),
getattr(self.table_model, self.y_field),
),
4326,
)
return ST_Intersects(ST_MakeEnvelope(*bbox, 4326), point)

def get_fields(self):
"""Reflect the table, then annotate the result.

Expand Down
22 changes: 19 additions & 3 deletions core/pygeoapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ def _edr_collections_block(
# "ogc_" so table_prefix (here, "ogc_internal_" for the internal
# mount) still applies, the same way _thing_collections_block does.
table_name = table_prefix + collection["table"].removeprefix("ogc_")
provider = {
water_edr_provider = {
"type": "edr",
"name": "core.edr_provider.WaterEDRProvider",
"data": {
Expand All @@ -486,7 +486,23 @@ def _edr_collections_block(
"table": table_name,
}
if collection["instance_field"]:
provider["instance_field"] = collection["instance_field"]
water_edr_provider["instance_field"] = collection["instance_field"]

described_postgresql_provider = {
"type": "feature",
"name": "core.feature_provider.DescribedPostgreSQLProvider",
"data": {
**water_edr_provider["data"],
"search_path": ["public"],
},
"id_field": "id",
"table": table_name,
# These views expose coordinates, not a geometry column.
"geom_field": None,
"x_field": "longitude",
"y_field": "latitude",
"time_field": "datetime",
}

resources[collection["id"]] = {
"type": "collection",
Expand All @@ -500,7 +516,7 @@ def _edr_collections_block(
},
"temporal": {"begin": None, "end": None},
},
"providers": [provider],
"providers": [water_edr_provider, described_postgresql_provider],
}

block = yaml.safe_dump(
Expand Down
Loading