Skip to content

Commit

Permalink
[GEOS-8953] WFS shapefile and GeoJSON output can't handle geometries …
Browse files Browse the repository at this point in the history
…with measures (#3124)
  • Loading branch information
Nuno Oliveira committed Oct 2, 2018
1 parent a9340b9 commit 11a5155
Show file tree
Hide file tree
Showing 12 changed files with 415 additions and 11 deletions.
71 changes: 61 additions & 10 deletions src/wfs/src/main/java/org/geoserver/wfs/json/GeoJSONBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.geotools.geometry.jts.coordinatesequence.CoordinateSequences;
import org.geotools.referencing.CRS;
import org.geotools.util.Converters;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
Expand All @@ -26,7 +25,6 @@
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;

/**
* This class extends the JSONBuilder to be able to write out geometric types. It is coded against
Expand All @@ -43,6 +41,8 @@ public class GeoJSONBuilder extends JSONBuilder {

private int numDecimals = 6;

private boolean encodeMeasures = false;

public GeoJSONBuilder(Writer w) {
super(w);
}
Expand All @@ -68,14 +68,17 @@ public JSONBuilder writeGeom(Geometry geometry) throws JSONException {
switch (geometryType) {
case POINT:
Point point = (Point) geometry;
Coordinate c = point.getCoordinate();
writeCoordinate(c.x, c.y, c.z);
writeCoordinate(point);
break;
case LINESTRING:
writeCoordinates(((LineString) geometry).getCoordinateSequence());
break;
case MULTIPOINT:
writeCoordinates(geometry.getCoordinates());
this.array();
for (int i = 0, n = geometry.getNumGeometries(); i < n; i++) {
writeCoordinate((Point) geometry.getGeometryN(i));
}
this.endArray();
break;
case POLYGON:
writePolygon((Polygon) geometry);
Expand Down Expand Up @@ -123,8 +126,21 @@ private JSONBuilder writeGeomCollection(GeometryCollection collection) {
return this.endArray();
}

private JSONBuilder writeCoordinates(Coordinate[] coords) throws JSONException {
return writeCoordinates(new CoordinateArraySequence(coords));
private JSONBuilder writeCoordinate(Point point) throws JSONException {
CoordinateSequence seq = point.getCoordinateSequence();
if (!seq.hasM() || !encodeMeasures) {
if (seq.hasZ()) {
return writeCoordinate(seq.getX(0), seq.getY(0), seq.getZ(0));
} else {
return writeCoordinate(seq.getX(0), seq.getY(0));
}
} else {
if (seq.hasZ()) {
return writeCoordinate(seq.getX(0), seq.getY(0), seq.getZ(0), seq.getM(0));
} else {
return writeCoordinate(seq.getX(0), seq.getY(0), 0, seq.getM(0));
}
}
}

/**
Expand All @@ -139,13 +155,31 @@ private JSONBuilder writeCoordinates(CoordinateSequence coords) throws JSONExcep

// guess the dimension of the coordinate sequence
int dim = CoordinateSequences.coordinateDimension(coords);
// measure
int measures = coords.getMeasures();
int dimension = coords.getDimension();

final int coordCount = coords.size();
for (int i = 0; i < coordCount; i++) {
if (dim > 2) {
writeCoordinate(coords.getX(i), coords.getY(i), coords.getOrdinate(i, 2));
// if has not measures coordinate
if (measures == 0 || !encodeMeasures) {
if (dim > 2) {
writeCoordinate(coords.getX(i), coords.getY(i), coords.getOrdinate(i, 2));
} else {
writeCoordinate(coords.getX(i), coords.getY(i));
}
} else {
writeCoordinate(coords.getX(i), coords.getY(i));
// if is XYZM
if (dimension - measures > 2) {
writeCoordinate(
coords.getX(i),
coords.getY(i),
coords.getOrdinate(i, 2),
coords.getOrdinate(i, 3));
} else {
// if is XYM -> fill Z with 0
writeCoordinate(coords.getX(i), coords.getY(i), 0, coords.getOrdinate(i, 2));
}
}
}

Expand All @@ -157,6 +191,10 @@ private JSONBuilder writeCoordinate(double x, double y) {
}

private JSONBuilder writeCoordinate(double x, double y, double z) {
return writeCoordinate(x, y, z, Double.NaN);
}

private JSONBuilder writeCoordinate(double x, double y, double z, double m) {
this.array();
if (axisOrder == CRS.AxisOrder.NORTH_EAST) {
roundedValue(y);
Expand All @@ -167,6 +205,10 @@ private JSONBuilder writeCoordinate(double x, double y, double z) {
}
if (!Double.isNaN(z)) {
roundedValue(z);
// measure value
if (!Double.isNaN(m)) {
roundedValue(m);
}
}

return this.endArray();
Expand Down Expand Up @@ -364,4 +406,13 @@ public void setAxisOrder(CRS.AxisOrder axisOrder) {
public void setNumberOfDecimals(int numberOfDecimals) {
this.numDecimals = numberOfDecimals;
}

/**
* Sets if coordinates measures (M) should be encoded.
*
* @param encodeMeasures TRUE if coordinates measures should be encoded, otherwise FALSE
*/
public void setEncodeMeasures(boolean encodeMeasures) {
this.encodeMeasures = encodeMeasures;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ protected GeoJSONBuilder getGeoJSONBuilder(
final GeoJSONBuilder jsonWriter = new GeoJSONBuilder(outWriter);
int numDecimals = getNumDecimals(featureCollection.getFeature(), gs, gs.getCatalog());
jsonWriter.setNumberOfDecimals(numDecimals);
jsonWriter.setEncodeMeasures(
encodeMeasures(featureCollection.getFeature(), gs.getCatalog()));
return jsonWriter;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public String getAttachmentFileName(Object value, Operation operation) {
return filename + (filename.endsWith(".zip") ? "" : ".zip");
}

protected void write(
public void write(
FeatureCollectionResponse featureCollection, OutputStream output, Operation getFeature)
throws IOException, ServiceException {
List<SimpleFeatureCollection> collections = new ArrayList<SimpleFeatureCollection>();
Expand Down
22 changes: 22 additions & 0 deletions src/wfs/src/test/java/org/geoserver/wfs/WFSTestSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import java.util.Objects;
import org.custommonkey.xmlunit.SimpleNamespaceContext;
import org.custommonkey.xmlunit.XMLUnit;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.data.test.CiteTestData;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.platform.GeoServerExtensions;
Expand Down Expand Up @@ -117,4 +119,24 @@ public void resetCiteCompliant() {
getGeoServer().save(wfs);
}
}

/**
* Helper method that activates or deactivates geometries measures encoding for the feature type
* matching the provided name.
*/
protected static void setMeasuresEncoding(
Catalog catalog, String featureTypeName, boolean encodeMeasures) {
// get the feature type from the catalog
FeatureTypeInfo featureTypeInfo = catalog.getFeatureTypeByName(featureTypeName);
if (featureTypeInfo == null) {
// ouch, feature type not found
throw new RuntimeException(
String.format(
"No feature type matching the provided name '%s' found.",
featureTypeName));
}
// set encode measures and save
featureTypeInfo.setEncodeMeasures(encodeMeasures);
catalog.save(featureTypeInfo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class GeoJSONBuilderTest {
public void setUp() {
writer = new StringWriter();
builder = new GeoJSONBuilder(writer);
builder.setEncodeMeasures(true);
}

@Test
Expand Down Expand Up @@ -390,4 +391,96 @@ public void testWriteListOfMaps() throws Exception {
assertEquals(u2.toString(), o2.get("b"));
assertEquals("object2", o2.get("c"));
}

@Test
public void testWritePointZM() throws Exception {
Geometry g = new WKTReader().read("POINT ZM (2 0 20 2)");
builder.writeGeom(g);
assertEquals("{\"type\":\"Point\",\"coordinates\":[2,0,20,2]}", writer.toString());
}

@Test
public void testWritePointM() throws Exception {
Geometry g = new WKTReader().read("POINT M (2 0 20)");
builder.writeGeom(g);
assertEquals("{\"type\":\"Point\",\"coordinates\":[2,0,0,20]}", writer.toString());
}

@Test
public void testWriteMultiPointZM() throws Exception {
Geometry g = new WKTReader().read("MULTIPOINT ZM (2 0 20 2, 1 1 1 1)");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"MultiPoint\",\"coordinates\":[[2,0,20,2],[1,1,1,1]]}",
writer.toString());
}

@Test
public void testWriteMultiPointM() throws Exception {
Geometry g = new WKTReader().read("MULTIPOINT M (2 0 20, 1 1 1)");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"MultiPoint\",\"coordinates\":[[2,0,0,20],[1,1,0,1]]}",
writer.toString());
}

@Test
public void testWriteLineZM() throws Exception {
Geometry g = new WKTReader().read("LINESTRING ZM (0 0 0 0, 0 10 1 1, 10 10 2 2)");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"LineString\",\"coordinates\":[[0,0,0,0],[0,10,1,1],[10,10,2,2]]}",
writer.toString());
}

@Test
public void testWriteMultiLineZM() throws Exception {
Geometry g = new WKTReader().read("MULTILINESTRING ZM ((1 2 3 4,5 6 7 8))");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"MultiLineString\",\"coordinates\":[[[1,2,3,4],[5,6,7,8]]]}",
writer.toString());
}

@Test
public void testWriteMultiLineM() throws Exception {
Geometry g = new WKTReader().read("MULTILINESTRING M ((1 2 4,5 6 8))");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"MultiLineString\",\"coordinates\":[[[1,2,0,4],[5,6,0,8]]]}",
writer.toString());
}

@Test
public void testWritePolygonZM() throws Exception {
Geometry g =
new WKTReader()
.read(
"POLYGON ZM "
+ "((0 0 0 3, 0 10 1 3, 10 10 2 3, 10 0 3 3, 0 0 0 3),"
+ "(1 1 4 3, 1 2 5 3, 2 2 6 3, 2 1 7 3, 1 1 4 3))");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"Polygon\",\"coordinates\":[[[0,0,0,3],[0,10,1,3],[10,10,2,3],[10,0,3,3],[0,0,0,3]]"
+ ",[[1,1,4,3],[1,2,5,3],[2,2,6,3],[2,1,7,3],[1,1,4,3]]]}",
writer.toString());
}

@Test
public void testWriteMultiPolygonZM() throws Exception {
Geometry g = new WKTReader().read("MULTIPOLYGON ZM (((0 0 3 1,1 1 7 2,1 0 7 3,0 0 3 1)))");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0,3,1],[1,1,7,2],[1,0,7,3],[0,0,3,1]]]]}",
writer.toString());
}

@Test
public void testWriteMultiPolygonM() throws Exception {
Geometry g = new WKTReader().read("MULTIPOLYGON M (((0 0 1,1 1 2,1 0 3,0 0 1)))");
builder.writeGeom(g);
assertEquals(
"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,0,0,1],[1,1,0,2],[1,0,0,3],[0,0,0,1]]]]}",
writer.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* (c) 2018 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wfs.response;

import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import java.util.Collections;
import javax.xml.namespace.QName;
import org.geoserver.data.test.MockData;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.wfs.WFSTestSupport;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;

/** Contains tests related with GeoJSON output format when requested through WFS. */
public final class GeoJsonOutputFormatTest extends WFSTestSupport {

private static final QName LINESTRING_ZM =
new QName(MockData.DEFAULT_URI, "lineStringZm", MockData.DEFAULT_PREFIX);

@Override
protected void onSetUp(SystemTestData testData) throws Exception {
super.onSetUp(testData);
// create in memory layer containing XYZM lines geometries
testData.addVectorLayer(
LINESTRING_ZM,
Collections.emptyMap(),
"lineStringZm.properties",
GeoJsonOutputFormatTest.class,
getCatalog());
}

@Before
public void beforeTest() {
// deactivate measures encoding, default behavior
setMeasuresEncoding(getCatalog(), LINESTRING_ZM.getLocalPart(), false);
}

@Test
public void testMeasuresEncoding() throws Exception {
// execute the WFS request asking for a GeoJSON output
MockHttpServletResponse response =
getAsServletResponse(
"wfs?request=GetFeature&typenames=gs:lineStringZm&version=2.0.0"
+ "&service=wfs&outputFormat=application/json");
// check that measures where not encoded
assertThat(response.getContentAsString(), notNullValue());
assertThat(
response.getContentAsString(), Matchers.containsString("[[120,50,20],[90,80,35]]"));
// activate measures encoding
setMeasuresEncoding(getCatalog(), LINESTRING_ZM.getLocalPart(), true);
response =
getAsServletResponse(
"wfs?request=GetFeature&typenames=gs:lineStringZm&version=2.0.0"
+ "&service=wfs&outputFormat=application/json");
// check that measures where encoded
assertThat(response.getContentAsString(), notNullValue());
assertThat(
response.getContentAsString(),
Matchers.containsString("[[120,50,20,15],[90,80,35,5]]"));
}
}

0 comments on commit 11a5155

Please sign in to comment.