Skip to content

Commit

Permalink
[GEOT-6243] Issue with reading GeoJSON feature with regular geometry …
Browse files Browse the repository at this point in the history
…attribute when geometry comes after properties (geotools#3033)

* handle Geometries as properties of the GeoJSON
* add static parseFilter method
  • Loading branch information
ianturton committed Jul 3, 2020
1 parent e06313a commit 94377f6
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 14 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.qix
*.db
.project
.classpath
Expand All @@ -15,4 +16,4 @@ nb-configuration.xml
workspace
*.db
.metadata
.gradle
.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
import com.bedatadriven.jackson.datatype.jts.parsers.GenericGeometryParser;
import com.bedatadriven.jackson.datatype.jts.parsers.GeometryParser;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
Expand All @@ -44,6 +46,7 @@
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.Geometries;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.util.logging.Logging;
import org.locationtech.jts.geom.Geometry;
Expand All @@ -66,40 +69,62 @@ public class GeoJSONReader implements AutoCloseable {

private JsonParser parser;

private JsonFactory factory;
private static JsonFactory factory = new JsonFactory();;

private SimpleFeatureType schema;

SimpleFeatureTypeBuilder typeBuilder = null;

private SimpleFeatureBuilder builder;

private int nextID = 0;

private String baseName = "features";

private boolean schemaChanged = false;

private GeometryFactory gFac = new GeometryFactory();

private URL url;

public GeoJSONReader(URL url) throws IOException {
this.url = url;
factory = new JsonFactory();
parser = factory.createParser(url);
baseName = FilenameUtils.getBaseName(url.getPath());
}

public GeoJSONReader(InputStream is) throws IOException {
parser = factory.createParser(is);
}

public boolean isConnected() {
if (url != null) {
try (InputStream inputStream = url.openStream()) {
if (inputStream != null && inputStream.available() > 0) {
return true;
}
url = new URL(url.toExternalForm());
try (InputStream inputStream2 = url.openStream()) {
return inputStream2 != null && inputStream2.available() > 0;
}

try (InputStream inputStream = url.openStream()) {
if (inputStream != null && inputStream.available() > 0) {
return true;
} catch (IOException e) {
LOGGER.log(Level.FINE, "Failure trying to determine if connected", e);
return false;
}
url = new URL(url.toExternalForm());
try (InputStream inputStream2 = url.openStream()) {
return inputStream2 != null && inputStream2.available() > 0;
}
return true;
}

public static SimpleFeature parseFeature(String json) throws JsonParseException, IOException {
try (JsonParser lParser = factory.createParser(new ByteArrayInputStream(json.getBytes()))) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JtsModule());
ObjectNode node = mapper.readTree(lParser);
try (GeoJSONReader reader = new GeoJSONReader((InputStream) null)) {
SimpleFeature feature = reader.getNextFeature(node);
return feature;
}
} catch (IOException e) {
LOGGER.log(Level.FINE, "Failure trying to determine if connected", e);
return false;
}
}

Expand Down Expand Up @@ -160,6 +185,8 @@ private SimpleFeature getNextFeature(ObjectNode node) throws IOException {
builder = getBuilder(props);
}
boolean restart = true;
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JtsModule());
SimpleFeature feature = null;
while (restart) {
restart = false;
Expand All @@ -185,13 +212,16 @@ private SimpleFeature getNextFeature(ObjectNode node) throws IOException {
builder.set(n.getKey(), n.getValue().asDouble());
} else if (binding == String.class) {
builder.set(n.getKey(), n.getValue().textValue());
} else if (binding.isAssignableFrom(Geometry.class)) {
GeometryParser<Geometry> gParser = new GenericGeometryParser(gFac);
Geometry g = gParser.geometryFromJson(n.getValue());
builder.set(n.getKey(), g);
} else {
LOGGER.warning("Unable to parse object of type " + binding);
builder.set(n.getKey(), n.getValue().toString());
}
}
JsonNode geom = node.get("geometry");
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JtsModule());
GeometryParser<Geometry> gParser = new GenericGeometryParser(gFac);
Geometry g = gParser.geometryFromJson(geom);
builder.set(GEOMETRY_NAME, g);
Expand Down Expand Up @@ -224,6 +254,12 @@ private SimpleFeatureBuilder getBuilder(JsonNode props) {
typeBuilder.add(n.getKey(), Integer.class);
} else if (value instanceof DoubleNode) {
typeBuilder.add(n.getKey(), Double.class);
} else if (value instanceof ObjectNode) {
String type = value.get("type").asText();
Geometries namedType = Geometries.getForName(type);
if (namedType != null) {
typeBuilder.add(n.getKey(), Geometry.class, DefaultGeographicCRS.WGS84);
}
} else {
typeBuilder.defaultValue("");
typeBuilder.add(n.getKey(), String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package org.geotools.data.geojson;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
Expand All @@ -29,8 +31,10 @@
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.Feature;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;

/** @author ian */
public class GeoJSONReaderTest {
Expand Down Expand Up @@ -72,4 +76,102 @@ public void testGetChangingSchema() throws IOException, ParseException {
}
}
}

@Test
public void testReadFromInputStream() throws Exception {
String input =
"{\n"
+ "\"type\": \"FeatureCollection\",\n"
+ "\"features\": [\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": 46.066667, \"LON\": 11.116667, \"CITY\": \"Trento\", \"NUMBER\": 140, \"YEAR\": 2002 }, \"bbox\": [ 11.117, 46.067, 11.117, 46.067 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 11.117, 46.067 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": 44.9441, \"LON\": -93.0852, \"CITY\": \"St Paul\", \"NUMBER\": 125, \"YEAR\": 2003 }, \"bbox\": [ -93.085, 44.944, -93.085, 44.944 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -93.085, 44.944 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": 13.752222, \"LON\": 100.493889, \"CITY\": \"Bangkok\", \"NUMBER\": 150, \"YEAR\": 2004 }, \"bbox\": [ 100.494, 13.752, 100.494, 13.752 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 100.494, 13.752 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": 45.420833, \"LON\": -75.69, \"CITY\": \"Ottawa\", \"NUMBER\": 200, \"YEAR\": 2004 }, \"bbox\": [ -75.69, 45.421, -75.69, 45.421 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -75.69, 45.421 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": 44.9801, \"LON\": -93.251867, \"CITY\": \"Minneapolis\", \"NUMBER\": 350, \"YEAR\": 2005 }, \"bbox\": [ -93.252, 44.98, -93.252, 44.98 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -93.252, 44.98 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": 46.519833, \"LON\": 6.6335, \"CITY\": \"Lausanne\", \"NUMBER\": 560, \"YEAR\": 2006 }, \"bbox\": [ 6.633, 46.52, 6.633, 46.52 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 6.633, 46.52 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": 48.428611, \"LON\": -123.365556, \"CITY\": \"Victoria\", \"NUMBER\": 721, \"YEAR\": 2007 }, \"bbox\": [ -123.366, 48.429, -123.366, 48.429 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -123.366, 48.429 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": -33.925278, \"LON\": 18.423889, \"CITY\": \"Cape Town\", \"NUMBER\": 550, \"YEAR\": 2008 }, \"bbox\": [ 18.424, -33.925, 18.424, -33.925 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 18.424, -33.925 ] } },\n"
+ "{ \"type\": \"Feature\", \"properties\": { \"LAT\": -33.859972, \"LON\": 151.21111, \"CITY\": \"Sydney\", \"NUMBER\": 436, \"YEAR\": 2009 }, \"bbox\": [ 151.211, -33.86, 151.211, -33.86 ], \"geometry\": { \"type\": \"Point\", \"coordinates\": [ 151.211, -33.86 ] } }\n"
+ "]\n"
+ "}";

try (GeoJSONReader reader = new GeoJSONReader(new ByteArrayInputStream(input.getBytes()))) {
FeatureCollection features = reader.getFeatures();
assertNotNull(features);
assertEquals("wrong number of features read", 9, features.size());
}
}

@SuppressWarnings("unchecked")
@Test
public void testFeatureCollectionWithRegularGeometryAttributeReadAndGeometryAfterProperties()
throws Exception {
String geojson1 =
"{"
+ "'type': 'FeatureCollection',"
+ "'features': "
+ "[{"
+ " 'type': 'Feature',"
+ " 'id': 'feature.0',"
+ " 'properties': {"
+ " 'otherGeometry': {"
+ " 'type': 'LineString',"
+ " 'coordinates': [[1.1, 1.2], [1.3, 1.4]]"
+ " }"
+ " },"
+ " 'geometry': {"
+ " 'type': 'Point',"
+ " 'coordinates': [0.1, 0.1]"
+ " }"
+ "}"
+ "]"
+ "}";
geojson1 = geojson1.replace('\'', '"');

SimpleFeature f = null;
try (GeoJSONReader reader =
new GeoJSONReader(new ByteArrayInputStream(geojson1.getBytes()))) {
FeatureCollection features = reader.getFeatures();
assertNotNull(features);
assertFalse(features.isEmpty());
f = (SimpleFeature) DataUtilities.first(features);
} catch (IOException e) {
e.printStackTrace();
}
assertNotNull(f);
assertEquals("features.0", f.getID());
WKTReader wkt = new WKTReader();
assertEquals(wkt.read("POINT (0.1 0.1)"), f.getDefaultGeometry());
assertEquals(wkt.read("LINESTRING (1.1 1.2, 1.3 1.4)"), f.getAttribute("otherGeometry"));
}

@Test
public void testFeatureWithRegularGeometryAttributeReadAndGeometryAfterProperties()
throws Exception {
String geojson1 =
"{"
+ " 'type': 'Feature',"
+ " 'id': 'feature.0',"
+ " 'properties': {"
+ " 'otherGeometry': {"
+ " 'type': 'LineString',"
+ " 'coordinates': [[1.1, 1.2], [1.3, 1.4]]"
+ " }"
+ " },"
+ " 'geometry': {"
+ " 'type': 'Point',"
+ " 'coordinates': [0.1, 0.1]"
+ " }"
+ "}";
geojson1 = geojson1.replace('\'', '"');

SimpleFeature f = GeoJSONReader.parseFeature(geojson1);
assertNotNull(f);

assertNotNull(f);
assertEquals("features.0", f.getID());
WKTReader wkt = new WKTReader();
assertEquals(wkt.read("POINT (0.1 0.1)"), f.getDefaultGeometry());
assertEquals(wkt.read("LINESTRING (1.1 1.2, 1.3 1.4)"), f.getAttribute("otherGeometry"));
}
}

0 comments on commit 94377f6

Please sign in to comment.