Skip to content

Commit

Permalink
Merge pull request #1765 from stempler/fix/govserv-geojson
Browse files Browse the repository at this point in the history
Improvements for writing GeoJson for GML with nested geometries
  • Loading branch information
stephanr authored Jan 8, 2025
2 parents 883b9eb + 13b1865 commit cbb9ceb
Show file tree
Hide file tree
Showing 4 changed files with 371 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.deegree.cs.exceptions.TransformationException;
import org.deegree.cs.exceptions.UnknownCRSException;
import org.deegree.feature.Feature;
import org.deegree.feature.GenericFeature;
import org.deegree.feature.property.GenericProperty;
import org.deegree.feature.property.SimpleProperty;
import org.deegree.feature.types.property.CustomPropertyType;
Expand All @@ -26,6 +27,7 @@
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -135,7 +137,7 @@ public void writeSingleFeature(Feature feature) throws IOException, UnknownCRSEx
private void writeGeometry(Feature feature) throws IOException, UnknownCRSException, TransformationException {
if (geoJsonGeometryWriter == null)
return;
List<Property> geometryProperties = feature.getGeometryProperties();
List<Property> geometryProperties = findGeometryProperties(feature);
if (geometryProperties.isEmpty()) {
name("geometry").nullValue();
}
Expand All @@ -151,6 +153,66 @@ else if (geometryProperties.size() == 1) {
}
}

private List<Property> findGeometryProperties(Feature feature) {
// try default geometry properties
List<Property> result = feature.getGeometryProperties();
if (!result.isEmpty()) {
return result;
}

// search for geometries in the properties recursively
result = new ArrayList<>();

// XXX would be nice to have a nice visitor pattern implementation for this model
// instead of implementing traversal logic everywhere
for (Property property : feature.getProperties()) {
findGeometryPropertiesInProperty(property, result);
}

return result;
}

private void findGeometryPropertiesInProperty(Property property, List<Property> result) {
if (property.getValue() instanceof Geometry) {
result.add(property);
}
else {
PropertyType propertyType = property.getType();
if (propertyType instanceof CustomPropertyType) {
findGeometryPropertiesInChildren(property, result);
}
else if (property instanceof GenericProperty) {
findGeometryProperties(property.getValue(), result);
}
}
}

private void findGeometryProperties(TypedObjectNode node, List<Property> result) {
if (node == null) {
return;
}
if (node instanceof Property) {
findGeometryPropertiesInProperty((Property) node, result);
}
else if (node instanceof GenericXMLElement) {
findGeometryPropertiesInChildren((GenericXMLElement) node, result);
}
else if (node instanceof GenericFeature) {
result.addAll(findGeometryProperties((GenericFeature) node));
}
}

private void findGeometryPropertiesInChildren(ElementNode property, List<Property> result) {
for (TypedObjectNode child : property.getChildren()) {
if (child instanceof Property) {
findGeometryPropertiesInProperty((Property) child, result);
}
else if (child instanceof GenericXMLElement) {
findGeometryPropertiesInChildren((GenericXMLElement) child, result);
}
}
}

private void writeProperties(Feature feature) throws IOException, TransformationException, UnknownCRSException {
List<Property> properties = feature.getProperties();
name("properties");
Expand Down Expand Up @@ -219,7 +281,10 @@ else if (propertyType instanceof FeaturePropertyType) {
exportFeaturePropertyType(property);
}
else if (propertyType instanceof GeometryPropertyType) {
// Do nothing as geometry was exported before.
// geometry was exported before
// need to throw an exception here, because if we do nothing the Json is
// invalid (condition should be handled before)
throw new IOException("Geometry should not be rendered as part of GeoJson properties.");
}
else if (property instanceof GenericProperty) {
exportGenericProperty((GenericProperty) property);
Expand Down Expand Up @@ -360,8 +425,16 @@ private void export(TypedObjectNode node) throws IOException, UnknownCRSExceptio
exportValue(primitiveValue);
}
else if (node instanceof Property) {
name(((Property) node).getName().getLocalPart());
export((Property) node);
Property prop = (Property) node;
PropertyType type = prop.getType();

if (type instanceof GeometryPropertyType) {
// don't write geometry properties, geometry is written before
}
else {
name(prop.getName().getLocalPart());
export(prop);
}
}
else if (node instanceof GenericXMLElement) {
name(((GenericXMLElement) node).getName().getLocalPart());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import org.deegree.gml.GMLVersion;
import org.junit.Test;

import java.io.IOException;
import java.io.StringWriter;
import java.net.URL;

import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasNoJsonPath;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

/**
* @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a>
Expand Down Expand Up @@ -90,6 +90,78 @@ public void testWrite_skipGeometries() throws Exception {

}

@Test
public void testWrite_GovServ_skipGeometries() throws Exception {
StringWriter featureAsJson = new StringWriter();
GeoJsonWriter geoJsonFeatureWriter = new GeoJsonWriter(featureAsJson, null, true);
Feature govserv = parseFeature("govserv.xml");

geoJsonFeatureWriter.startFeatureCollection();
geoJsonFeatureWriter.write(govserv);
geoJsonFeatureWriter.endFeatureCollection();

String featureCollection = featureAsJson.toString();

assertThat(featureCollection, JsonPathMatchers.isJson());
assertThat(featureCollection, hasJsonPath("$.type", is("FeatureCollection")));
assertThat(featureCollection, hasJsonPath("$.features.length()", is(1)));
assertThat(featureCollection, hasJsonPath("$.features[0].type", is("Feature")));
assertThat(featureCollection, hasNoJsonPath("$.features[0].srsName"));
assertThat(featureCollection, hasJsonPath("$.features[0].id", is("schule_3600")));

assertThat(featureCollection, not(hasJsonPath("$.features[0].geometry")));

assertThat(featureCollection, hasJsonPath("$.features[0].properties.name", is("Carl-Weyprecht-Schule")));
}

@Test
public void testWrite_GovServ_MultiGeom() throws Exception {
StringWriter featureAsJson = new StringWriter();
GeoJsonWriter geoJsonFeatureWriter = new GeoJsonWriter(featureAsJson, null, false);
Feature govserv = parseFeature("govserv.xml");

geoJsonFeatureWriter.startFeatureCollection();
try {
geoJsonFeatureWriter.write(govserv);
}
catch (IOException e) {
// currently expected to fail if there are multiple geometries
assertThat(e.getMessage(), containsString("more than one geometry"));
return;
}
geoJsonFeatureWriter.endFeatureCollection();

String featureCollection = featureAsJson.toString();

fail("Expected to fail because of multiple geometries");
}

@Test
public void testWrite_GovServ_SingleGeom() throws Exception {
StringWriter featureAsJson = new StringWriter();
GeoJsonWriter geoJsonFeatureWriter = new GeoJsonWriter(featureAsJson, null, false);
Feature govserv = parseFeature("govserv_single-geom.xml");

geoJsonFeatureWriter.startFeatureCollection();
geoJsonFeatureWriter.write(govserv);
geoJsonFeatureWriter.endFeatureCollection();

String featureCollection = featureAsJson.toString();

assertThat(featureCollection, JsonPathMatchers.isJson());
assertThat(featureCollection, hasJsonPath("$.type", is("FeatureCollection")));
assertThat(featureCollection, hasJsonPath("$.features.length()", is(1)));
assertThat(featureCollection, hasJsonPath("$.features[0].type", is("Feature")));
assertThat(featureCollection, hasNoJsonPath("$.features[0].srsName"));
assertThat(featureCollection, hasJsonPath("$.features[0].id", is("schule_3600")));

assertThat(featureCollection, hasJsonPath("$.features[0].geometry"));
assertThat(featureCollection, hasJsonPath("$.features[0].geometry.type", is("Point")));
assertThat(featureCollection, hasJsonPath("$.features[0].geometry.coordinates"));

assertThat(featureCollection, hasJsonPath("$.features[0].properties.name", is("Carl-Weyprecht-Schule")));
}

@Test
public void testWrite_SingleFeature() throws Exception {
StringWriter featureAsJson = new StringWriter();
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version='1.0' encoding='UTF-8'?>
<us-govserv:GovernmentalService gml:id="schule_3600" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd http://inspire.ec.europa.eu/schemas/us-govserv/4.0 http://172.17.0.1:8329/services/8f6a_wfs?SERVICE=WFS&amp;VERSION=2.0.0&amp;REQUEST=DescribeFeatureType&amp;OUTPUTFORMAT=application%2Fgml%2Bxml%3B+version%3D3.2&amp;TYPENAME=us-govserv:GovernmentalService&amp;NAMESPACES=xmlns(us-govserv,http%3A%2F%2Finspire.ec.europa.eu%2Fschemas%2Fus-govserv%2F4.0)" xmlns:wfs="http://www.opengis.net/wfs/2.0" timeStamp="2024-12-03T14:50:31Z" xmlns:ad="http://inspire.ec.europa.eu/schemas/ad/4.0" xmlns:gss="http://www.isotc211.org/2005/gss" xmlns:gn="http://inspire.ec.europa.eu/schemas/gn/4.0" xmlns:gsr="http://www.isotc211.org/2005/gsr" xmlns:gts="http://www.isotc211.org/2005/gts" xmlns:ogc="http://www.opengis.net/ogc" xmlns:cp="http://inspire.ec.europa.eu/schemas/cp/4.0" xmlns:act-core="http://inspire.ec.europa.eu/schemas/act-core/4.0" xmlns:sc="http://www.interactive-instruments.de/ShapeChange/AppInfo" xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:base2="http://inspire.ec.europa.eu/schemas/base2/2.0" xmlns:au="http://inspire.ec.europa.eu/schemas/au/4.0" xmlns:us-govserv="http://inspire.ec.europa.eu/schemas/us-govserv/4.0" xmlns:tn="http://inspire.ec.europa.eu/schemas/tn/4.0" xmlns:us-net-common="http://inspire.ec.europa.eu/schemas/us-net-common/4.0" xmlns:bu-base="http://inspire.ec.europa.eu/schemas/bu-base/4.0" xmlns:net="http://inspire.ec.europa.eu/schemas/net/4.0" xmlns:bu-core2d="http://inspire.ec.europa.eu/schemas/bu-core2d/4.0" xmlns:base="http://inspire.ec.europa.eu/schemas/base/3.3" xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:gml="http://www.opengis.net/gml/3.2">
<gml:description>Für rechtsverbindliche Auskünfte wenden Sie sich bitte an den zuständigen Schulträger. Es besteht kein Anspruch auf Vollständigkeit.</gml:description>
<gml:name>Carl-Weyprecht-Schule</gml:name>
<us-govserv:beginLifespanVersion xsi:nil="true"/>
<us-govserv:inspireId>
<base:Identifier>
<base:localId>3600</base:localId>
<base:namespace>http://www.inspire-umsetzer.de/schulstandorte/</base:namespace>
</base:Identifier>
</us-govserv:inspireId>
<us-govserv:pointOfContact>
<base2:Contact>
<base2:address>
<ad:AddressRepresentation>
<ad:adminUnit>
<gn:GeographicalName>
<gn:language xsi:nil="true"/>
<gn:nativeness xsi:nil="true"/>
<gn:nameStatus xsi:nil="true"/>
<gn:sourceOfName xsi:nil="true"/>
<gn:pronunciation xsi:nil="true"/>
<gn:spelling>
<gn:SpellingOfName>
<gn:text>Bad König</gn:text>
<gn:script xsi:nil="true"/>
</gn:SpellingOfName>
</gn:spelling>
</gn:GeographicalName>
</ad:adminUnit>
<ad:postCode>64732</ad:postCode>
</ad:AddressRepresentation>
</base2:address>
<base2:website>http://www.odenwaldkreis.de/index.php?id=132</base2:website>
</base2:Contact>
</us-govserv:pointOfContact>
<us-govserv:serviceLocation>
<us-govserv:ServiceLocationType>
<us-govserv:serviceLocationByGeometry>
<!--Inlined geometry '_3eefa123-71f6-409c-baf5-44a1d81c8beb'-->
<gml:Point gml:id="_3eefa123-71f6-409c-baf5-44a1d81c8beb" srsName="http://www.opengis.net/def/crs/EPSG/0/4326">
<gml:pos>49.746634 9.015993</gml:pos>
</gml:Point>
</us-govserv:serviceLocationByGeometry>
</us-govserv:ServiceLocationType>
</us-govserv:serviceLocation>
<us-govserv:serviceType xlink:href="http://inspire.ec.europa.eu/codelist/ServiceTypeValue/lowerSecondaryEducation"/>
</us-govserv:GovernmentalService>

0 comments on commit cbb9ceb

Please sign in to comment.