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
14 changes: 10 additions & 4 deletions orchestration/assets/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,12 @@ def _combine_asset(
return _combine_asset


def _geojson_to_geopackage(geojson_path: Path, layer_name: str, out_dir: Path) -> Path:
def _geojson_to_geopackage(geojson_path: Path, layer_name: str, out_dir: Path):
"""Convert a GeoJSON file to a GeoPackage whose layer (table) is named
*layer_name* (so the published GeoServer layer is named *layer_name*).
GeoPackage is a single file with no field-name length limit, unlike the
zipped ESRI Shapefile this replaces. Returns the path to the .gpkg."""
zipped ESRI Shapefile this replaces. Returns (gpkg_path, bbox) where bbox is
(minx, miny, maxx, maxy) in EPSG:4326."""
gdf = gpd.read_file(geojson_path)
if gdf.empty:
raise ValueError(f"{layer_name}: GeoJSON has no features; nothing to publish")
Expand All @@ -177,9 +178,13 @@ def _geojson_to_geopackage(geojson_path: Path, layer_name: str, out_dir: Path) -
# computing bounds, so flatten to 2D — elevation remains an attribute.
gdf["geometry"] = gdf.geometry.force_2d()

# Bounds are passed to GeoServer explicitly so it never calls getBounds on
# the gpkg store (which still trips the 3D-CRS bug at publish time).
minx, miny, maxx, maxy = (float(v) for v in gdf.total_bounds)

gpkg_path = out_dir / f"{layer_name}.gpkg"
gdf.to_file(gpkg_path, driver="GPKG", layer=layer_name)
return gpkg_path
return gpkg_path, (minx, miny, maxx, maxy)


def _build_geoserver_asset(product: dict, group: str):
Expand All @@ -203,12 +208,13 @@ def _geoserver_asset(
with tempfile.TemporaryDirectory() as tmpdir:
geojson = Path(tmpdir) / f"{pid}.geojson"
gcs.download_latest(pid, str(geojson))
gpkg_path = _geojson_to_geopackage(geojson, pid, Path(tmpdir))
gpkg_path, bbox = _geojson_to_geopackage(geojson, pid, Path(tmpdir))
actions = geoserver.publish_geopackage(
pid,
str(gpkg_path),
title=product.get("title", pid),
abstract=product.get("description", ""),
bbox=bbox,
)
except Exception:
error = traceback.format_exc()
Expand Down
35 changes: 25 additions & 10 deletions orchestration/resources/geoserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def publish_geopackage(
gpkg_path: str,
title: Optional[str] = None,
abstract: Optional[str] = None,
bbox: Optional[tuple] = None,
) -> dict:
"""Create-or-update a native GeoPackage datastore named *layer_name*
from the GeoPackage at *gpkg_path* and publish its layer. Idempotent —
Expand Down Expand Up @@ -122,19 +123,33 @@ def publish_geopackage(
# written by geopandas (== layer_name); name is the published layer
# name. The store was recreated above, so this POST always creates a
# fresh featuretype. srs is set explicitly — the data is WGS84.
ft_url = f"{base}/rest/workspaces/{ws}/datastores/{layer_name}/featuretypes"
feature_type: dict = {
"name": layer_name,
"nativeName": layer_name,
"title": title or layer_name,
"abstract": abstract or "",
"srs": "EPSG:4326",
"nativeCRS": "EPSG:4326",
}
# Supply the bounding boxes so GeoServer does not call getBounds on the
# gpkg store — that path still trips a 3D-CRS dimension error at publish.
# recalculate= (empty) tells GeoServer to keep the supplied boxes as-is.
recalc = ""
if bbox is not None:
minx, miny, maxx, maxy = bbox
box = {
"minx": minx, "miny": miny, "maxx": maxx, "maxy": maxy,
"crs": "EPSG:4326",
}
feature_type["nativeBoundingBox"] = box
feature_type["latLonBoundingBox"] = dict(box)
recalc = "?recalculate="

ft_url = f"{base}/rest/workspaces/{ws}/datastores/{layer_name}/featuretypes{recalc}"
r = requests.post(
ft_url,
auth=auth,
json={
"featureType": {
"name": layer_name,
"nativeName": layer_name,
"title": title or layer_name,
"abstract": abstract or "",
"srs": "EPSG:4326",
}
},
json={"featureType": feature_type},
headers={"Content-Type": "application/json"},
timeout=self.timeout,
)
Expand Down
Loading