Improve Linked Data Proxy interoperability for WFS 1.1.0 and MultiCur… - #110
Conversation
…ve geometries
This change hardens the Linked Data Proxy and GML parsing pipeline to work reliably
with stricter and non-uniform WFS implementations (especially QGIS/GeoServer-like
WFS 1.1.0 services), while keeping behavior generic across services.
Key improvements:
Added robust geometry parsing in GML 3 factory:
namespace-tolerant/local-name based geometry detection
support for Curve and MultiCurve variants
support for embedded LineStringSegment paths in MultiCurve structures
improved fallback geometry discovery when geometry elements are nested or prefixed differently
Improved feature serialization:
output GeoJSON geometry as null (instead of empty string) when no geometry is available
keeps GeoJSON output standards-compliant and avoids downstream parsing issues
Fixed WFS request construction for strict servers:
version-specific request parameters:
WFS 2.x uses TYPENAMES + COUNT
WFS 1.1.0 uses TYPENAME + MAXFEATURES
normalized request key casing (SERVICE/REQUEST/VERSION)
removed duplicate max-features parameter in paging GET requests
Added request-version fallback strategy:
detect service version and retry with compatible versions when needed
improved handling of WFS exception responses before deciding fallback
Improved proxy paging/filter compatibility:
support STARTINDEX as alias parameter
convert FES filter namespace/elements to OGC equivalents for non-2.x services
Reworked GeoJSON bbox handling:
generic recursive bbox calculation for Point/Line/Polygon/Multi*/GeometryCollection
consistent feature-collection bbox accumulation
reduced reliance on brittle geometry-type-specific bbox code
Hardened cached count behavior:
detect implausible cached counts
trigger recount and refresh cache when cached values are stale/invalid
Prevented accidental feature loss in conversion paths:
do not drop features simply because geometry is null
guard geometry-dependent logic (bbox/type extraction) accordingly
Result:
The proxy now handles a broader range of real-world WFS services more reliably,
including WFS 1.1.0 endpoints and MultiCurve-based datasets, with improved stability
for paging, filtering, geometry conversion, and bbox generation.
There was a problem hiding this comment.
Pull request overview
This pull request hardens the Linked Data Proxy’s WFS request/response handling and the GML→GeoJSON pipeline to interoperate better with stricter/non-uniform WFS implementations (notably WFS 1.1.0) and broader GML geometry variants (Curve/MultiCurve), while improving bbox computation and GeoJSON geometry null-handling.
Changes:
- Adds namespace-tolerant geometry discovery/parsing in the GML 3 factory, including Curve/MultiCurve variants and nested geometry fallback.
- Reworks bbox calculation to be recursive/generic across GeoJSON geometry types and introduces WFS-version fallback chains for count/getFeature calls.
- Normalizes WFS KVP request parameter casing and corrects version-specific KVP parameters (TYPENAME(S), MAXFEATURES/COUNT), plus outputs GeoJSON
geometry: nullwhen geometry is missing.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| http/php/mod_linkedDataProxy.php | Adds WFS version fallback logic, filter compatibility conversions, and new generic bbox computation utilities used during proxy responses. |
| http/classes/class_wfs.php | Normalizes/adjusts WFS KVP construction across versions (parameter names/casing; removes duplicate max-features param). |
| http/classes/class_gml_feature.php | Makes GeoJSON serialization standards-compliant by emitting geometry: null instead of an empty string. |
| http/classes/class_gml_3_factory.php | Improves geometry detection/parsing to be prefix/namespace-tolerant and supports Curve/MultiCurve structures. |
Comments suppressed due to low confidence (1)
http/php/mod_linkedDataProxy.php:2539
- In the Mapbender-GML->GeoJSON path, features with
geometry: nullare still added to$geojsonList->features, but no corresponding$geojsonBbox[$geojsonIndex]entry is created because bbox calculation is skipped unlessgeometryis an object with atype. Downstream code (e.g., HTML zoom links) assumes$geojsonBbox[$i]exists for each feature, so this can trigger undefined-index/property errors.
if (is_object($featureGeoJson->geometry) && isset($featureGeoJson->geometry->type)) {
$featureBbox = calculateBboxFromGeojsonGeometry($featureGeoJson->geometry);
if ($featureBbox !== false) {
$geojsonBbox [$geojsonIndex]->minx = $featureBbox->minx;
$geojsonBbox [$geojsonIndex]->miny = $featureBbox->miny;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| foreach ( $geojsonList->features as $feature ) { | ||
| $minxF = 90; | ||
| $minyF = 180; | ||
| $maxxF = - 90; | ||
| $maxyF = - 180; | ||
| switch ($feature->geometry->type) { | ||
| case "Polygon" : | ||
| foreach ( $feature->geometry->coordinates [0] as $lonLat ) { | ||
| $lon = $lonLat [0]; | ||
| $lat = $lonLat [1]; | ||
| if ($minxF > $lat) { | ||
| $minxF = $lat; | ||
| } | ||
| if ($minyF > $lon) { | ||
| $minyF = $lon; | ||
| } | ||
| if ($maxxF < $lat) { | ||
| $maxxF = $lat; | ||
| } | ||
| if ($maxyF < $lon) { | ||
| $maxyF = $lon; | ||
| } | ||
| } | ||
| break; | ||
| case "Point" : | ||
| $lon = $feature->geometry->coordinates [0]; | ||
| $lat = $feature->geometry->coordinates [1]; | ||
| if ($minxF > $lat) { | ||
| $minxF = $lat; | ||
| } | ||
| if ($minyF > $lon) { | ||
| $minyF = $lon; | ||
| } | ||
| if ($maxxF < $lat) { | ||
| $maxxF = $lat; | ||
| } | ||
| if ($maxyF < $lon) { | ||
| $maxyF = $lon; | ||
| } | ||
| break; | ||
| case "LineString" : | ||
| foreach ( $feature->geometry->coordinates as $lonLat ) { | ||
| $lon = $lonLat [0]; | ||
| $lat = $lonLat [1]; | ||
| if ($minxF > $lat) { | ||
| $minxF = $lat; | ||
| } | ||
| if ($minyF > $lon) { | ||
| $minyF = $lon; | ||
| } | ||
| if ($maxxF < $lat) { | ||
| $maxxF = $lat; | ||
| } | ||
| if ($maxyF < $lon) { | ||
| $maxyF = $lon; | ||
| } | ||
| } | ||
| break; | ||
| $featureBbox = calculateBboxFromGeojsonGeometry($feature->geometry); | ||
| if ($featureBbox === false) { | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Implemented in commit 5a73017. The bbox array now stays index-aligned with features even when geometry is null/invalid, and HTML zoom links are only rendered when a valid bbox exists.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… parent: window.parent.scrollTo(0,0); is working fine
Code Review by GeminiBug 1: Swapped X/Y (Longitude/Latitude) Coordinates in GeoJSON BBOX CalculationIn $bbox = new stdClass();
$bbox->minx = $minLat;
$bbox->miny = $minLon;
$bbox->maxx = $maxLat;
$bbox->maxy = $maxLon;
Additionally, Suggested Fix:In function calculateBboxFromGeojsonGeometry($geometry) {
if (!is_object($geometry) || !isset($geometry->type)) {
return false;
}
$minLat = 90;
$minLon = 180;
$maxLat = -90;
$maxLon = -180;
if ($geometry->type === "GeometryCollection" && isset($geometry->geometries) && is_array($geometry->geometries)) {
foreach ($geometry->geometries as $geometryPart) {
$bboxPart = calculateBboxFromGeojsonGeometry($geometryPart);
if ($bboxPart !== false) {
if ($minLon > $bboxPart->minx) { $minLon = $bboxPart->minx; }
if ($minLat > $bboxPart->miny) { $minLat = $bboxPart->miny; }
if ($maxLon < $bboxPart->maxx) { $maxLon = $bboxPart->maxx; }
if ($maxLat < $bboxPart->maxy) { $maxLat = $bboxPart->maxy; }
}
}
}
else if (isset($geometry->coordinates)) {
$coords = json_decode(json_encode($geometry->coordinates), true);
updateBboxAccumulatorFromCoordinates($coords, $minLat, $minLon, $maxLat, $maxLon);
}
if ($minLat > $maxLat || $minLon > $maxLon) {
return false;
}
$bbox = new stdClass();
$bbox->minx = $minLon;
$bbox->miny = $minLat;
$bbox->maxx = $maxLon;
$bbox->maxy = $maxLat;
return $bbox;
}
function calculateBboxFromGeojsonFcObject($geojsonList) {
$minxFC = 180;
$minyFC = 90;
$maxxFC = -180;
$maxyFC = -90;
foreach ( $geojsonList->features as $feature ) {
if (isset($feature->geometry)) {
$bboxFeature = calculateBboxFromGeojsonGeometry($feature->geometry);
if ($bboxFeature !== false) {
extendFeatureCollectionBbox($minxFC, $minyFC, $maxxFC, $maxyFC, $bboxFeature);
}
}
}
if ($minxFC > $maxxFC || $minyFC > $maxyFC) {
return false;
}
return array($minxFC, $minyFC, $maxxFC, $maxyFC);
}Bug 2: Unsafe
|
…m to layer in mapviewer while loading geojson from linked open data I changed it here directly so that it will be pushed all at once :)
Code Review by GeminiHere is the code review for the submitted pull request: 1. Critical Bug: Variable Name Typo in
|
Code Review by GeminiHere is a code review of the pull request changes: 1. Fatal Error on Null/Missing Geometry SerializationFile: if ($this->geometry) {
$str .= "{\"type\":\"name\", \"properties\":{\"name\":\"" . $this->geometry->srs . "\"}}, ";
}
else {
$str .= "{\"type\":\"name\", \"properties\":{\"name\":\"" . $this->geometry->srs . "\"}}, ";
}IssueWhen a feature has no geometry ( Suggested FixDo not attempt to access if ($this->geometry && !empty($this->geometry->srs)) {
$str .= "{\"type\":\"name\", \"properties\":{\"name\":\"" . $this->geometry->srs . "\"}}, ";
}
else {
$str .= "{\"type\":\"name\", \"properties\":{\"name\":\"EPSG:4326\"}}, ";
}2. Wrong Key Used for Unsetting Array Elements in Memory CleanupFile: foreach ( $gml3Object->featureCollection->featureArray as $mbFeature ) {
...
if (!is_object($featureGeoJson) || !property_exists($featureGeoJson, 'geometry')) {
unset ( $gml3Object->featureCollection->featureArray [$geojsonIndex] );
continue;
}
...
unset ( $gml3Object->featureCollection->featureArray [$geojsonIndex] );
$geojsonIndex ++;
}IssueInside the
Suggested FixUse the loop key foreach ( $gml3Object->featureCollection->featureArray as $key => $mbFeature ) {
$featureGeoJson = json_decode($mbFeature->toGeoJSON());
if (!is_object($featureGeoJson) || !property_exists($featureGeoJson, 'geometry')) {
unset ( $gml3Object->featureCollection->featureArray [$key] );
continue;
}
...
$geojsonList->features [] = $featureGeoJson;
// free memory
unset ( $gml3Object->featureCollection->featureArray [$key] );
$geojsonIndex ++;
}3. Performance Overhead with unnecessary
|
…ve geometries
This change hardens the Linked Data Proxy and GML parsing pipeline to work reliably with stricter and non-uniform WFS implementations (especially QGIS/GeoServer-like WFS 1.1.0 services), while keeping behavior generic across services.
Key improvements:
Added robust geometry parsing in GML 3 factory:
namespace-tolerant/local-name based geometry detection
support for Curve and MultiCurve variants
support for embedded LineStringSegment paths in MultiCurve structures
improved fallback geometry discovery when geometry elements are nested or prefixed differently
Improved feature serialization:
output GeoJSON geometry as null (instead of empty string) when no geometry is available
keeps GeoJSON output standards-compliant and avoids downstream parsing issues
Fixed WFS request construction for strict servers:
version-specific request parameters:
WFS 2.x uses TYPENAMES + COUNT
WFS 1.1.0 uses TYPENAME + MAXFEATURES
normalized request key casing (SERVICE/REQUEST/VERSION)
removed duplicate max-features parameter in paging GET requests
Added request-version fallback strategy:
detect service version and retry with compatible versions when needed
improved handling of WFS exception responses before deciding fallback
Improved proxy paging/filter compatibility:
support STARTINDEX as alias parameter
convert FES filter namespace/elements to OGC equivalents for non-2.x services
Reworked GeoJSON bbox handling:
generic recursive bbox calculation for Point/Line/Polygon/Multi*/GeometryCollection
consistent feature-collection bbox accumulation
reduced reliance on brittle geometry-type-specific bbox code
Hardened cached count behavior:
detect implausible cached counts
trigger recount and refresh cache when cached values are stale/invalid
Prevented accidental feature loss in conversion paths:
do not drop features simply because geometry is null
guard geometry-dependent logic (bbox/type extraction) accordingly
Result:
The proxy now handles a broader range of real-world WFS services more reliably, including WFS 1.1.0 endpoints and MultiCurve-based datasets, with improved stability for paging, filtering, geometry conversion, and bbox generation.