diff --git a/deegree-core/deegree-core-base/src/main/java/org/deegree/geojson/GeoJsonWriter.java b/deegree-core/deegree-core-base/src/main/java/org/deegree/geojson/GeoJsonWriter.java index 8519da285f..4a5a593f4e 100644 --- a/deegree-core/deegree-core-base/src/main/java/org/deegree/geojson/GeoJsonWriter.java +++ b/deegree-core/deegree-core-base/src/main/java/org/deegree/geojson/GeoJsonWriter.java @@ -29,7 +29,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; /** @@ -44,7 +43,7 @@ public class GeoJsonWriter extends JsonWriter implements GeoJsonFeatureWriter, G private static final Logger LOG = LoggerFactory.getLogger(GeoJsonWriter.class); - private final GeoJsonGeometryWriter geoJsonGeometryWriter; + private GeoJsonGeometryWriter geoJsonGeometryWriter; private final ICRS crs; @@ -58,10 +57,25 @@ public class GeoJsonWriter extends JsonWriter implements GeoJsonFeatureWriter, G * @throws UnknownCRSException if "crs:84" is not known as CRS (should never happen) */ public GeoJsonWriter(Writer writer, ICRS crs) throws UnknownCRSException { + this(writer, crs, false); + } + + /** + * Instantiates a new {@link GeoJsonWriter}. + * @param writer the writer to write the GeoJSON into, never null + * @param crs the target crs of the geometries, may be null, then + * "EPSG:4326" will be used + * @param skipGeometries true if geometries should not be exported, + * false otherwise + * @throws UnknownCRSException if "crs:84" is not known as CRS (should never happen) + */ + public GeoJsonWriter(Writer writer, ICRS crs, boolean skipGeometries) throws UnknownCRSException { super(writer); setIndent(" "); setHtmlSafe(true); - this.geoJsonGeometryWriter = new GeoJsonGeometryWriter(this, crs); + if (!skipGeometries) { + this.geoJsonGeometryWriter = new GeoJsonGeometryWriter(this, crs); + } this.crs = crs; } @@ -119,6 +133,8 @@ public void writeSingleFeature(Feature feature) throws IOException, UnknownCRSEx } private void writeGeometry(Feature feature) throws IOException, UnknownCRSException, TransformationException { + if (geoJsonGeometryWriter == null) + return; List geometryProperties = feature.getGeometryProperties(); if (geometryProperties.isEmpty()) { name("geometry").nullValue(); diff --git a/deegree-core/deegree-core-base/src/test/java/org/deegree/geojson/GeoJsonFeatureWriterTest.java b/deegree-core/deegree-core-base/src/test/java/org/deegree/geojson/GeoJsonFeatureWriterTest.java index 818e1e1f93..f9029054ba 100644 --- a/deegree-core/deegree-core-base/src/test/java/org/deegree/geojson/GeoJsonFeatureWriterTest.java +++ b/deegree-core/deegree-core-base/src/test/java/org/deegree/geojson/GeoJsonFeatureWriterTest.java @@ -16,6 +16,7 @@ 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.junit.Assert.assertThat; /** @@ -56,6 +57,39 @@ public void testWrite() throws Exception { } + @Test + public void testWrite_skipGeometries() throws Exception { + StringWriter featureAsJson = new StringWriter(); + GeoJsonWriter geoJsonFeatureWriter = new GeoJsonWriter(featureAsJson, null, true); + Feature cadastralZoning = parseFeature("CadastralZoning.gml"); + + geoJsonFeatureWriter.startFeatureCollection(); + geoJsonFeatureWriter.write(cadastralZoning); + 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("CP_CADASTRALZONING_Bundesland_02"))); + assertThat(featureCollection, not(hasJsonPath("$.features[0].geometry"))); + assertThat(featureCollection, hasJsonPath("$.features[0].properties.label", is("02"))); + assertThat(featureCollection, hasJsonPath("$.features[0].properties.originalMapScaleDenominator", is(10))); + assertThat(featureCollection, + hasJsonPath("$.features[0].properties.beginLifespanVersion", is("2009-12-15T08:04:54Z"))); + assertThat(featureCollection, hasJsonPath("$.features[0].properties.estimatedAccuracy.uom", is("m"))); + assertThat(featureCollection, hasJsonPath("$.features[0].properties.inspireId.Identifier.localId", + is("urn:adv:oid:DEHHALKA10000005"))); + assertThat(featureCollection, hasJsonPath( + "$.features[0].properties.name.GeographicalName.spelling.SpellingOfName.text", is("Hamburg"))); + assertThat(featureCollection, + hasJsonPath("$.features[0].properties.levelName.LocalisedCharacterString.value", is("Bundesland"))); + + } + @Test public void testWrite_SingleFeature() throws Exception { StringWriter featureAsJson = new StringWriter(); diff --git a/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoContext.java b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoContext.java index af701a69f1..bf993f5db0 100644 --- a/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoContext.java +++ b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoContext.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.io.Writer; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -52,6 +53,8 @@ public interface FeatureInfoContext { XMLStreamWriter getXmlWriter() throws IOException, XMLStreamException; + Writer getWriter() throws IOException; + void sendRedirect(String location) throws IOException; } diff --git a/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoManager.java b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoManager.java index 142727e708..a45f7dec64 100644 --- a/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoManager.java +++ b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoManager.java @@ -40,18 +40,9 @@ Occam Labs UG (haftungsbeschränkt) ----------------------------------------------------------------------------*/ package org.deegree.featureinfo; -import static org.slf4j.LoggerFactory.getLogger; - -import java.io.IOException; -import java.net.URL; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import javax.xml.stream.XMLStreamException; - import org.deegree.featureinfo.serializing.FeatureInfoGmlWriter; import org.deegree.featureinfo.serializing.FeatureInfoSerializer; +import org.deegree.featureinfo.serializing.GeoJsonFeatureInfoSerializer; import org.deegree.featureinfo.serializing.PlainTextFeatureInfoSerializer; import org.deegree.featureinfo.serializing.TemplateFeatureInfoSerializer; import org.deegree.featureinfo.serializing.XsltFeatureInfoSerializer; @@ -59,6 +50,15 @@ Occam Labs UG (haftungsbeschränkt) import org.deegree.workspace.Workspace; import org.slf4j.Logger; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + /** * Responsible for managing feature info output formats and their serializers. * @@ -112,6 +112,14 @@ public void addOrReplaceXsltFormat(String format, URL xsltUrl, GMLVersion versio featureInfoSerializers.put(format, xslt); } + public void addOrReplaceGeoJsonFormat(String format, boolean allowOtherCrsThanWGS84, + boolean allowExportOfGeometries) { + LOG.debug("Adding GeoJson feature info format"); + GeoJsonFeatureInfoSerializer geoJsonSerializer = new GeoJsonFeatureInfoSerializer(allowOtherCrsThanWGS84, + allowExportOfGeometries); + featureInfoSerializers.put(format, geoJsonSerializer); + } + public Set getSupportedFormats() { return featureInfoSerializers.keySet(); } diff --git a/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoParams.java b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoParams.java index fe01d2f422..8f8b80015d 100644 --- a/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoParams.java +++ b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/FeatureInfoParams.java @@ -68,8 +68,10 @@ public class FeatureInfoParams { private ICRS crs; + private final ICRS infoCrs; + public FeatureInfoParams(Map nsBindings, FeatureCollection col, String format, - boolean withGeometries, String schemaLocation, FeatureType type, ICRS crs) { + boolean withGeometries, String schemaLocation, FeatureType type, ICRS crs, ICRS infoCrs) { this.nsBindings = nsBindings; this.featureCollection = col; this.format = format; @@ -77,6 +79,7 @@ public FeatureInfoParams(Map nsBindings, FeatureCollection col, this.schemaLocation = schemaLocation; this.featureType = type; this.crs = crs; + this.infoCrs = infoCrs; } /** @@ -128,4 +131,11 @@ public ICRS getCrs() { return crs; } + /** + * @return the CRS of the values to return + */ + public ICRS getInfoCrs() { + return infoCrs; + } + } diff --git a/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/serializing/GeoJsonFeatureInfoSerializer.java b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/serializing/GeoJsonFeatureInfoSerializer.java new file mode 100644 index 0000000000..eeadbc261b --- /dev/null +++ b/deegree-core/deegree-core-featureinfo/src/main/java/org/deegree/featureinfo/serializing/GeoJsonFeatureInfoSerializer.java @@ -0,0 +1,91 @@ +/*---------------------------------------------------------------------------- + This file is part of deegree + Copyright (C) 2001-2024 by: + - Department of Geography, University of Bonn - + and + - lat/lon GmbH - + and others + + This library is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation; either version 2.1 of the License, or (at your option) + any later version. + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, Inc., + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Contact information: + + e-mail: info@deegree.org + website: http://www.deegree.org/ +----------------------------------------------------------------------------*/ +package org.deegree.featureinfo.serializing; + +import org.deegree.cs.coordinatesystems.ICRS; +import org.deegree.cs.exceptions.TransformationException; +import org.deegree.cs.exceptions.UnknownCRSException; +import org.deegree.feature.Feature; +import org.deegree.feature.FeatureCollection; +import org.deegree.featureinfo.FeatureInfoContext; +import org.deegree.featureinfo.FeatureInfoParams; +import org.deegree.geojson.GeoJsonFeatureWriter; +import org.deegree.geojson.GeoJsonWriter; +import org.slf4j.Logger; + +import java.io.IOException; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * {@link FeatureInfoSerializer} to serialize feature info result as GeoJson. + * + * @author Lyn Goltz + */ +public class GeoJsonFeatureInfoSerializer implements FeatureInfoSerializer { + + private static final Logger LOG = getLogger(GeoJsonFeatureInfoSerializer.class); + + private final boolean allowOtherCrsThanWGS84; + + private final boolean allowExportOfGeometries; + + public GeoJsonFeatureInfoSerializer(boolean allowOtherCrsThanWGS84, boolean allowExportOfGeometries) { + this.allowExportOfGeometries = allowExportOfGeometries; + this.allowOtherCrsThanWGS84 = allowOtherCrsThanWGS84; + } + + @Override + public void serialize(FeatureInfoParams params, FeatureInfoContext context) { + ICRS crs = detectCrs(params); + boolean skipGeometries = detectSkipGeometries(params); + try (GeoJsonFeatureWriter geoJsonStreamWriter = new GeoJsonWriter(context.getWriter(), crs, skipGeometries)) { + geoJsonStreamWriter.startFeatureCollection(); + FeatureCollection featureCollection = params.getFeatureCollection(); + for (Feature feature : featureCollection) { + geoJsonStreamWriter.write(feature); + } + geoJsonStreamWriter.endFeatureCollection(); + } + catch (IOException | TransformationException | UnknownCRSException e) { + LOG.error("GeoJson GFI response could not be written", e); + } + } + + private boolean detectSkipGeometries(FeatureInfoParams params) { + if (allowExportOfGeometries && params.isWithGeometries()) + return false; + return true; + } + + private ICRS detectCrs(FeatureInfoParams params) { + if (allowOtherCrsThanWGS84 && params.getInfoCrs() != null) { + return params.getInfoCrs(); + } + return null; + } + +} diff --git a/deegree-core/deegree-core-featureinfo/src/test/java/org/deegree/featureinfo/serializing/GeoJsonFeatureInfoSerializerTest.java b/deegree-core/deegree-core-featureinfo/src/test/java/org/deegree/featureinfo/serializing/GeoJsonFeatureInfoSerializerTest.java new file mode 100644 index 0000000000..41250b4acd --- /dev/null +++ b/deegree-core/deegree-core-featureinfo/src/test/java/org/deegree/featureinfo/serializing/GeoJsonFeatureInfoSerializerTest.java @@ -0,0 +1,149 @@ +/*---------------------------------------------------------------------------- + This file is part of deegree + Copyright (C) 2001-2024 by: + - Department of Geography, University of Bonn - + and + - lat/lon GmbH - + and others + + This library is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation; either version 2.1 of the License, or (at your option) + any later version. + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, Inc., + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Contact information: + + e-mail: info@deegree.org + website: http://www.deegree.org/ +----------------------------------------------------------------------------*/ +package org.deegree.featureinfo.serializing; + +import org.deegree.cs.coordinatesystems.ICRS; +import org.deegree.cs.persistence.CRSManager; +import org.deegree.feature.FeatureCollection; +import org.deegree.featureinfo.FeatureInfoContext; +import org.deegree.featureinfo.FeatureInfoParams; +import org.deegree.gml.GMLInputFactory; +import org.deegree.gml.GMLStreamReader; +import org.deegree.gml.GMLVersion; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Lyn Goltz + */ +public class GeoJsonFeatureInfoSerializerTest { + + @Test + public void testSerialize() throws Exception { + StringWriter writer = new StringWriter(); + GeoJsonFeatureInfoSerializer serializer = new GeoJsonFeatureInfoSerializer(false, true); + FeatureInfoParams params = createParams(); + FeatureInfoContext context = mockContext(writer); + serializer.serialize(params, context); + + String geoJson = writer.toString(); + assertTrue(geoJson.contains("\"type\":")); + assertTrue(geoJson.contains("\"FeatureCollection\"")); + assertTrue(geoJson.contains("AdministrativeUnit_10044117")); + assertTrue(geoJson.contains("6.910811280489623")); + } + + @Test + public void testSerialize_withInfoCrsNotAllowed() throws Exception { + StringWriter writer = new StringWriter(); + GeoJsonFeatureInfoSerializer serializer = new GeoJsonFeatureInfoSerializer(false, true); + FeatureInfoParams params = createParams("EPSG:25832"); + FeatureInfoContext context = mockContext(writer); + serializer.serialize(params, context); + + String geoJson = writer.toString(); + assertTrue(geoJson.contains("\"type\":")); + assertTrue(geoJson.contains("\"FeatureCollection\"")); + assertTrue(geoJson.contains("AdministrativeUnit_10044117")); + assertTrue(geoJson.contains("6.910811280489623")); + } + + @Test + public void testSerialize_allowOtherCrs_InfoCrs() throws Exception { + StringWriter writer = new StringWriter(); + GeoJsonFeatureInfoSerializer serializer = new GeoJsonFeatureInfoSerializer(true, true); + FeatureInfoParams params = createParams("EPSG:25832"); + FeatureInfoContext context = mockContext(writer); + serializer.serialize(params, context); + + String geoJson = writer.toString(); + assertTrue(geoJson.contains("\"type\":")); + assertTrue(geoJson.contains("\"FeatureCollection\"")); + assertTrue(geoJson.contains("AdministrativeUnit_10044117")); + assertTrue(geoJson.contains("348736.888")); + } + + @Test + public void testSerialize_allowOtherCrs_SkipGeometries() throws Exception { + StringWriter writer = new StringWriter(); + GeoJsonFeatureInfoSerializer serializer = new GeoJsonFeatureInfoSerializer(false, false); + FeatureInfoParams params = createParams(); + FeatureInfoContext context = mockContext(writer); + serializer.serialize(params, context); + + String geoJson = writer.toString(); + assertTrue(geoJson.contains("\"type\":")); + assertTrue(geoJson.contains("\"FeatureCollection\"")); + assertTrue(geoJson.contains("AdministrativeUnit_10044117")); + assertFalse(geoJson.contains("geometries")); + } + + @Test + public void testSerialize_allowOtherCrs_InfoCrsAndSkipGeometries() throws Exception { + StringWriter writer = new StringWriter(); + GeoJsonFeatureInfoSerializer serializer = new GeoJsonFeatureInfoSerializer(true, false); + FeatureInfoParams params = createParams("EPSG:25832"); + FeatureInfoContext context = mockContext(writer); + serializer.serialize(params, context); + + String geoJson = writer.toString(); + assertTrue(geoJson.contains("\"type\":")); + assertTrue(geoJson.contains("\"FeatureCollection\"")); + assertTrue(geoJson.contains("AdministrativeUnit_10044117")); + assertFalse(geoJson.contains("geometries")); + } + + private FeatureInfoParams createParams() throws Exception { + return createParams(null); + } + + private FeatureInfoParams createParams(String crs) throws Exception { + URL resource = TemplateFeatureInfoSerializer.class.getResource("featurecollection.gml"); + GMLStreamReader gmlStreamReader = GMLInputFactory.createGMLStreamReader(GMLVersion.GML_32, resource); + Map nsBindings = new HashMap<>(); + FeatureCollection col = gmlStreamReader.readFeatureCollection(); + ICRS infoCrs = crs != null ? CRSManager.lookup(crs) : null; + return new FeatureInfoParams(nsBindings, col, "text/html", true, null, null, null, infoCrs); + } + + private FeatureInfoContext mockContext(Writer writer) throws IOException { + FeatureInfoContext mock = mock(FeatureInfoContext.class); + when(mock.getWriter()).thenReturn(writer); + return mock; + } + +} diff --git a/deegree-core/deegree-core-featureinfo/src/test/java/org/deegree/featureinfo/serializing/TemplateFeatureInfoSerializerTest.java b/deegree-core/deegree-core-featureinfo/src/test/java/org/deegree/featureinfo/serializing/TemplateFeatureInfoSerializerTest.java index c8fe4c7776..1c622eb748 100644 --- a/deegree-core/deegree-core-featureinfo/src/test/java/org/deegree/featureinfo/serializing/TemplateFeatureInfoSerializerTest.java +++ b/deegree-core/deegree-core-featureinfo/src/test/java/org/deegree/featureinfo/serializing/TemplateFeatureInfoSerializerTest.java @@ -44,7 +44,7 @@ private FeatureInfoParams createParams() throws Exception { GMLStreamReader gmlStreamReader = GMLInputFactory.createGMLStreamReader(GMLVersion.GML_32, resource); Map nsBindings = new HashMap<>(); FeatureCollection col = gmlStreamReader.readFeatureCollection(); - return new FeatureInfoParams(nsBindings, col, "text/html", true, null, null, null); + return new FeatureInfoParams(nsBindings, col, "text/html", true, null, null, null, null); } private FeatureInfoContext mockContext(OutputStream os) throws IOException { diff --git a/deegree-core/deegree-core-featureinfo/src/test/resources/org/deegree/featureinfo/serializing/featurecollection.gml b/deegree-core/deegree-core-featureinfo/src/test/resources/org/deegree/featureinfo/serializing/featurecollection.gml index c5e330ecb1..aba355900f 100644 --- a/deegree-core/deegree-core-featureinfo/src/test/resources/org/deegree/featureinfo/serializing/featurecollection.gml +++ b/deegree-core/deegree-core-featureinfo/src/test/resources/org/deegree/featureinfo/serializing/featurecollection.gml @@ -168,7 +168,7 @@ - + diff --git a/deegree-core/deegree-core-protocol/deegree-protocol-wms/src/main/java/org/deegree/protocol/wms/ops/GetFeatureInfo.java b/deegree-core/deegree-core-protocol/deegree-protocol-wms/src/main/java/org/deegree/protocol/wms/ops/GetFeatureInfo.java index 8ecdcebd51..904a27d868 100644 --- a/deegree-core/deegree-core-protocol/deegree-protocol-wms/src/main/java/org/deegree/protocol/wms/ops/GetFeatureInfo.java +++ b/deegree-core/deegree-core-protocol/deegree-protocol-wms/src/main/java/org/deegree/protocol/wms/ops/GetFeatureInfo.java @@ -35,25 +35,6 @@ package org.deegree.protocol.wms.ops; -import static java.lang.Integer.parseInt; -import static java.util.Arrays.asList; -import static org.deegree.commons.ows.exception.OWSException.INVALID_PARAMETER_VALUE; -import static org.deegree.commons.ows.exception.OWSException.INVALID_POINT; -import static org.deegree.commons.ows.exception.OWSException.MISSING_PARAMETER_VALUE; -import static org.deegree.commons.utils.ArrayUtils.splitAsDoubles; -import static org.deegree.commons.utils.CollectionUtils.map; -import static org.deegree.commons.utils.MapUtils.DEFAULT_PIXEL_SIZE; -import static org.deegree.layer.LayerRef.FROM_NAMES; -import static org.deegree.protocol.wms.WMSConstants.VERSION_111; -import static org.deegree.protocol.wms.WMSConstants.VERSION_130; -import static org.slf4j.LoggerFactory.getLogger; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; - import org.deegree.commons.ows.exception.OWSException; import org.deegree.commons.tom.ows.Version; import org.deegree.cs.CRSUtils; @@ -67,6 +48,24 @@ import org.deegree.style.StyleRef; import org.slf4j.Logger; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import static java.lang.Integer.parseInt; +import static java.util.Arrays.asList; +import static org.deegree.commons.ows.exception.OWSException.INVALID_PARAMETER_VALUE; +import static org.deegree.commons.ows.exception.OWSException.INVALID_POINT; +import static org.deegree.commons.ows.exception.OWSException.MISSING_PARAMETER_VALUE; +import static org.deegree.commons.utils.ArrayUtils.splitAsDoubles; +import static org.deegree.commons.utils.CollectionUtils.map; +import static org.deegree.layer.LayerRef.FROM_NAMES; +import static org.deegree.protocol.wms.WMSConstants.VERSION_111; +import static org.deegree.protocol.wms.WMSConstants.VERSION_130; +import static org.slf4j.LoggerFactory.getLogger; + /** * GetFeatureInfo * @@ -96,6 +95,8 @@ public class GetFeatureInfo extends RequestBase { private String infoFormat; + private ICRS infoCrs; + private int featureCount = 1; private boolean returnGeometries; @@ -330,6 +331,7 @@ private double[] handleCommon(Map map) throws OWSException { } returnGeometries = map.get("GEOMETRIES") != null && map.get("GEOMETRIES").equalsIgnoreCase("true"); + infoCrs = map.get("INFO_CRS") != null ? CRSManager.getCRSRef(map.get("INFO_CRS")) : null; return vals; } @@ -363,6 +365,13 @@ public String getInfoFormat() { return infoFormat; } + /** + * @return the CRS of the values to return + */ + public ICRS getInfoCrs() { + return infoCrs; + } + /** * @return the max feature count */ diff --git a/deegree-services/deegree-services-commons/src/main/java/org/deegree/services/controller/utils/StandardFeatureInfoContext.java b/deegree-services/deegree-services-commons/src/main/java/org/deegree/services/controller/utils/StandardFeatureInfoContext.java index d2dd6aa51a..b2f5a1efef 100644 --- a/deegree-services/deegree-services-commons/src/main/java/org/deegree/services/controller/utils/StandardFeatureInfoContext.java +++ b/deegree-services/deegree-services-commons/src/main/java/org/deegree/services/controller/utils/StandardFeatureInfoContext.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.io.Writer; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; @@ -56,6 +57,8 @@ public class StandardFeatureInfoContext implements FeatureInfoContext { private XMLStreamWriter xmlWriter = null; + private Writer writer = null; + private boolean redirected = false; public StandardFeatureInfoContext(HttpResponseBuffer response) { @@ -98,6 +101,20 @@ public XMLStreamWriter getXmlWriter() throws IOException, XMLStreamException { return xmlWriter = response.getXMLWriter(); } + @Override + public Writer getWriter() throws IOException { + if (writer != null) { + return writer; + } + if (redirected) { + throw new IllegalStateException("sendRedirect() already called for FeatureInfoContext"); + } + if (outputStream != null) { + throw new IllegalStateException("getOutputStream() already called for FeatureInfoContext"); + } + return writer = response.getWriter(); + } + @Override public void sendRedirect(String location) throws IOException { diff --git a/deegree-services/deegree-services-wms/src/main/java/org/deegree/services/wms/controller/WMSController.java b/deegree-services/deegree-services-wms/src/main/java/org/deegree/services/wms/controller/WMSController.java index 92eb6683d5..78d6f29afd 100644 --- a/deegree-services/deegree-services-wms/src/main/java/org/deegree/services/wms/controller/WMSController.java +++ b/deegree-services/deegree-services-wms/src/main/java/org/deegree/services/wms/controller/WMSController.java @@ -35,21 +35,6 @@ package org.deegree.services.wms.controller; -import static javax.imageio.ImageIO.write; -import static org.deegree.commons.ows.exception.OWSException.NO_APPLICABLE_CODE; -import static org.deegree.commons.ows.exception.OWSException.OPERATION_NOT_SUPPORTED; -import static org.deegree.commons.utils.ArrayUtils.join; -import static org.deegree.commons.utils.CollectionUtils.getStringJoiner; -import static org.deegree.commons.utils.CollectionUtils.map; -import static org.deegree.commons.utils.CollectionUtils.reduce; -import static org.deegree.commons.xml.stax.XMLStreamUtils.nextElement; -import static org.deegree.protocol.wms.WMSConstants.VERSION_111; -import static org.deegree.protocol.wms.WMSConstants.VERSION_130; -import static org.deegree.services.controller.OGCFrontController.getHttpGetURL; -import static org.deegree.services.i18n.Messages.get; -import static org.deegree.services.metadata.MetadataUtils.convertFromJAXB; -import static org.slf4j.LoggerFactory.getLogger; - import jakarta.activation.DataHandler; import jakarta.activation.DataSource; import jakarta.mail.util.ByteArrayDataSource; @@ -63,29 +48,6 @@ import jakarta.xml.soap.SOAPException; import jakarta.xml.soap.SOAPMessage; import jakarta.xml.soap.SOAPPart; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.ServiceLoader; -import java.util.TreeMap; -import java.util.UUID; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.XMLStreamWriter; -import javax.xml.transform.dom.DOMSource; import org.apache.axiom.om.OMElement; import org.apache.axiom.soap.SOAP11Version; import org.apache.axiom.soap.SOAPVersion; @@ -103,6 +65,7 @@ import org.deegree.commons.xml.XMLAdapter; import org.deegree.commons.xml.stax.XMLStreamUtils; import org.deegree.cs.coordinatesystems.ICRS; +import org.deegree.cs.exceptions.UnknownCRSException; import org.deegree.cs.refs.coordinatesystem.CRSRef; import org.deegree.feature.FeatureCollection; import org.deegree.feature.types.FeatureType; @@ -170,6 +133,45 @@ import org.deegree.workspace.Workspace; import org.slf4j.Logger; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.dom.DOMSource; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.TreeMap; +import java.util.UUID; + +import static javax.imageio.ImageIO.write; +import static org.deegree.commons.ows.exception.OWSException.NO_APPLICABLE_CODE; +import static org.deegree.commons.ows.exception.OWSException.OPERATION_NOT_SUPPORTED; +import static org.deegree.commons.utils.ArrayUtils.join; +import static org.deegree.commons.utils.CollectionUtils.getStringJoiner; +import static org.deegree.commons.utils.CollectionUtils.map; +import static org.deegree.commons.utils.CollectionUtils.reduce; +import static org.deegree.commons.xml.stax.XMLStreamUtils.nextElement; +import static org.deegree.protocol.wms.WMSConstants.VERSION_111; +import static org.deegree.protocol.wms.WMSConstants.VERSION_130; +import static org.deegree.services.controller.OGCFrontController.getHttpGetURL; +import static org.deegree.services.i18n.Messages.get; +import static org.deegree.services.metadata.MetadataUtils.convertFromJAXB; +import static org.slf4j.LoggerFactory.getLogger; + /** * WMSController handles the protocol and map service globally. * @@ -523,7 +525,7 @@ private void getLegendGraphic(Map map, HttpResponseBuffer respon private void getFeatureInfo(Map map, final HttpResponseBuffer response, Version version) throws OWSException, IOException, MissingDimensionValue, InvalidDimensionValue { org.deegree.protocol.wms.ops.GetFeatureInfo fi = new org.deegree.protocol.wms.ops.GetFeatureInfo(map, version); - doGetFeatureInfo(map, response, version, fi); + doGetFeatureInfo(response, version, fi); } private void getFeatureInfoSchema(Map map, HttpResponseBuffer response) throws IOException { @@ -682,8 +684,7 @@ public void doXML(XMLStreamReader xmlStream, HttpServletRequest request, HttpRes case GetFeatureInfo: GetFeatureInfoParser getFeatureInfoParser = new GetFeatureInfoParser(); GetFeatureInfo getFeatureInfo = getFeatureInfoParser.parse(xmlStream); - Map gfiMap = new HashMap(); - doGetFeatureInfo(gfiMap, response, VERSION_130, getFeatureInfo); + doGetFeatureInfo(response, VERSION_130, getFeatureInfo); break; default: String msg = "XML request handling is currently not supported for operation " + requestName; @@ -738,8 +739,7 @@ public void doSOAP(org.apache.axiom.soap.SOAPEnvelope soapDoc, HttpServletReques case GetFeatureInfo: GetFeatureInfoParser getFeatureInfoParser = new GetFeatureInfoParser(); GetFeatureInfo getFeatureInfo = getFeatureInfoParser.parse(xmlStream); - Map gfiMap = new HashMap(); - doGetFeatureInfo(gfiMap, response, VERSION_130, getFeatureInfo); + doGetFeatureInfo(response, VERSION_130, getFeatureInfo); break; default: String msg = "SOAP request handling is currently not supported for operation " + requestName; @@ -923,7 +923,7 @@ private LinkedList doGetMap(GetMap getMap, Map map, Vers return headers; } - private void doGetFeatureInfo(Map map, final HttpResponseBuffer response, Version version, + private void doGetFeatureInfo(final HttpResponseBuffer response, Version version, org.deegree.protocol.wms.ops.GetFeatureInfo fi) throws OWSException, IOException { checkGetFeatureInfo(version, fi); ICRS crs = fi.getCoordinateSystem(); @@ -931,6 +931,7 @@ private void doGetFeatureInfo(Map map, final HttpResponseBuffer List queryLayers = map(fi.getQueryLayers(), CollectionUtils.getToStringMapper()); String format = fi.getInfoFormat(); + ICRS infoCrs = fi.getInfoCrs(); LinkedList headers = new LinkedList(); FeatureCollection col = getFeatureInfoProvider.query(this, service, fi, queryLayers, headers); addHeaders(response, headers); @@ -955,7 +956,8 @@ private void doGetFeatureInfo(Map map, final HttpResponseBuffer String loc = getHttpGetURL() + "request=GetFeatureInfoSchema&layers=" + join(",", queryLayers); try { - FeatureInfoParams params = new FeatureInfoParams(nsBindings, col, format, geometries, loc, type, crs); + FeatureInfoParams params = new FeatureInfoParams(nsBindings, col, format, geometries, loc, type, crs, + infoCrs); featureInfoManager.serializeFeatureInfo(params, new StandardFeatureInfoContext(response)); response.flushBuffer(); } @@ -1012,7 +1014,8 @@ private void addSupportedImageFormats(DeegreeWMS conf) { } } - private void addSupportedFeatureInfoFormats(DeegreeWMS conf) throws InstantiationException, IllegalAccessException { + private void addSupportedFeatureInfoFormats(DeegreeWMS conf) + throws InstantiationException, IllegalAccessException, UnknownCRSException { if (conf.getFeatureInfoFormats() != null) { for (GetFeatureInfoFormat t : conf.getFeatureInfoFormats().getGetFeatureInfoFormat()) { if (t.getFile() != null) { @@ -1025,6 +1028,10 @@ else if (t.getXSLTFile() != null) { featureInfoManager.addOrReplaceXsltFormat(t.getFormat(), metadata.getLocation().resolveToUrl(xsltFile.getValue()), version, workspace); } + else if (t.getGeoJSON() != null) { + featureInfoManager.addOrReplaceGeoJsonFormat(t.getFormat(), + t.getGeoJSON().isAllowOtherCrsThanWGS84(), t.getGeoJSON().isAllowExportOfGeometries()); + } else if (t.getSerializer() != null) { Serializer serializer = t.getSerializer(); diff --git a/deegree-services/deegree-services-wms/src/main/resources/META-INF/schemas/services/wms/wms_configuration.xsd b/deegree-services/deegree-services-wms/src/main/resources/META-INF/schemas/services/wms/wms_configuration.xsd index 11551ad834..1be7a74fba 100644 --- a/deegree-services/deegree-services-wms/src/main/resources/META-INF/schemas/services/wms/wms_configuration.xsd +++ b/deegree-services/deegree-services-wms/src/main/resources/META-INF/schemas/services/wms/wms_configuration.xsd @@ -1,7 +1,8 @@ - + @@ -109,6 +110,12 @@ + + + + + + @@ -145,7 +152,7 @@ - + diff --git a/deegree-services/deegree-services-wmts/src/main/java/org/deegree/services/wmts/controller/FeatureInfoFetcher.java b/deegree-services/deegree-services-wmts/src/main/java/org/deegree/services/wmts/controller/FeatureInfoFetcher.java index 8ec366699d..ad41d8f67a 100644 --- a/deegree-services/deegree-services-wmts/src/main/java/org/deegree/services/wmts/controller/FeatureInfoFetcher.java +++ b/deegree-services/deegree-services-wmts/src/main/java/org/deegree/services/wmts/controller/FeatureInfoFetcher.java @@ -40,14 +40,6 @@ Occam Labs UG (haftungsbeschränkt) ----------------------------------------------------------------------------*/ package org.deegree.services.wmts.controller; -import static org.deegree.commons.ows.exception.OWSException.INVALID_PARAMETER_VALUE; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; - -import javax.xml.stream.XMLStreamException; - import org.deegree.commons.ows.exception.OWSException; import org.deegree.cs.coordinatesystems.ICRS; import org.deegree.feature.FeatureCollection; @@ -61,6 +53,13 @@ Occam Labs UG (haftungsbeschränkt) import org.deegree.tile.TileDataLevel; import org.deegree.tile.TileDataSet; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; + +import static org.deegree.commons.ows.exception.OWSException.INVALID_PARAMETER_VALUE; + /** * Responsible for fetching features from tile layers, prepared to immediately be * serialized. @@ -105,7 +104,8 @@ void fetch(FeatureInfoManager featureInfoManager, HttpResponseBuffer response) ICRS crs = tds.getTileMatrixSet().getSpatialMetadata().getEnvelope().getCoordinateSystem(); HashMap nsBindings = new HashMap(); - FeatureInfoParams params = new FeatureInfoParams(nsBindings, col, gfi.getInfoFormat(), false, null, null, crs); + FeatureInfoParams params = new FeatureInfoParams(nsBindings, col, gfi.getInfoFormat(), false, null, null, crs, + null); featureInfoManager.serializeFeatureInfo(params, new StandardFeatureInfoContext(response)); } diff --git a/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc b/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc index e3efdeb724..6e72f5c610 100644 --- a/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc +++ b/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc @@ -665,13 +665,13 @@ would be generated by the WFS: ==== Adding GeoJSON output formats -Using option element _GeoJSONFormat, it possible to enable GeoJSON as GetFeature output format. +Using element _GeoJSONFormat_ enables GeoJSON as GetFeature output format. The _GeoJSON_ option has the following sub-options: [width="100%",cols="15%,15%,10%,60%",options="header",] |=== |Option |Cardinality |Value |Description -| @allowOtherCrsThanWGS84 | 0..1 | Boolean | GeoJson only allows geometries in WGS84. With this option the default behaviour of a WFS can be enabled: the CRS of the requested geometries are written in the requested CRS of the DefaultCRS of the WFS. Default: false +| @allowOtherCrsThanWGS84 | 0..1 | Boolean | GeoJSON only allows geometries in WGS84. With this option the default behaviour of a WFS can be enabled: the CRS of the requested geometries are written in the requested CRS of the DefaultCRS of the WFS. Default: false | MimeType | 1..n | String | Mime types associated with this format configuration |=== @@ -1268,6 +1268,31 @@ _GML_32_. If you want to learn more about the templating format, read the following sections. +[[geojson-featureinfo-configuration]] +==== GeoJSON feature info format + +Besides XML, Text and HTML, deegree supports GeoJSON as output format: + +[source,xml] +---- + + + + application/geo+json + + +---- + +Using the element _GeoJSON_ enables GeoJSON as GetFeatureInfo output format. +The _GeoJSON_ option has the following sub-options: + +[width="100%",cols="15%,15%,10%,60%",options="header",] +|=== +|Option |Cardinality |Value |Description +| @allowExportOfGeometries| 0..1 | Boolean | Per default, geometries are not written. With this option, the geometries are written if the vendor-specific parameter _GEOMETRIES_ is set to true in the request. Default: false +| @allowOtherCrsThanWGS84 | 0..1 | Boolean | GeoJSON only allows geometries in WGS84. With this option, the geometries are written in the requested CRS. The vendor-specific parameter _INFO_CRS_ can be used in the request to control the CRS of the geometries in the response. Default: false +|=== + ==== FeatureInfo templating format The templating format can be used to create text based output formats @@ -1661,6 +1686,11 @@ total). With the two vendorspecific parameter FILTERPROPERTY and FILTERVALUE you can request rendering just a defined list of features. Each feature to be rendered will be identified by the value of a given property. The name of the property is defined by the parameter filterproperty. The name of the property is not qualified so all properties with the given local name will be considered. A list of valid property values will be defined using parameter filtervalue, multiple values must be comma separated. Each layer - or better its underlying data source - requested by a GeMap will evaluated for having a feature with a property with given name and one of the defined values. Just the features matching this filter condition will be rendered. It's quite natural that only layer with an underlying Feature-DataSource can be filtered. The other parameters addressed in the GetMap (e.g. the style) request are not effected by this parameters. If the filter cannot be applied to the layer, e.g. cause it is a raster layer or the data source does not have a matching property, the filter will be ignored. If one the parameters is missing or the value empty, the filter is not applied. Example: FILTERPROPERTY=type&FILTERVALUE=stone,wood + + +In a GetFeatureInfo request the parameter GEOMETRIES can be used to return the geometries of a feature in GML and GeoJSON output. The default is false. The parameter INFO_CRS can be used in the GetFeatureInfo request to control the CRS of the geometries in the GeoJSON response. Default is WGS84. For GeoJSON output both parameters applies only if it is enabled in the configuration (<>). + + [[anchor-xml-request-encoding]] ==== XML request encoding