org.h2gis.drivers.geojson.GeoJsonReaderDriver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of h2drivers Show documentation
Show all versions of h2drivers Show documentation
Add H2 read/write support for file formats such as ESRI shape file
The newest version!
/**
* H2GIS is a library that brings spatial support to the H2 Database Engine
* .
*
* H2GIS is distributed under GPL 3 license. It is produced by CNRS
* .
*
* H2GIS is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* H2GIS 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* H2GIS. If not, see .
*
* For more information, please consult:
* or contact directly: info_at_h2gis.org
*/
package org.h2gis.drivers.geojson;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import org.h2gis.drivers.utility.FileUtil;
import org.h2gis.h2spatialapi.EmptyProgressVisitor;
import org.h2gis.h2spatialapi.ProgressVisitor;
import org.h2gis.utilities.JDBCUtilities;
import org.h2gis.utilities.SFSUtilities;
import org.h2gis.utilities.TableLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Driver to import a GeoJSON file into a spatial table.
*
* Supported geometries are POINT, LINESTRING, POLYGON and GEOMETRYCOLLECTION.
*
* The driver requires all Feature objects in a collection to have the same
* schema of properties. To build the table schema the first feature of the
* FeatureCollection is parsed. If the GeoJSON format does not contain any
* properties, a default primary key is added.
*
* @author Erwan Bocher
*/
public class GeoJsonReaderDriver {
private final static ArrayList geomTypes;
private final File fileName;
private final Connection connection;
private static GeometryFactory GF ;
private PreparedStatement preparedStatement = null;
private JsonFactory jsFactory;
private boolean hasProperties = false;
private int featureCounter = 1;
private ProgressVisitor progress = new EmptyProgressVisitor();
private FileChannel fc;
private long fileSize = 0;
private long readFileSizeEachNode = 1;
private long nodeCountProgress = 0;
// For progression information return
private static final int AVERAGE_NODE_SIZE = 500;
boolean hasGeometryField = false;
int fieldIndex = 0;
private static final Logger log = LoggerFactory.getLogger(GeoJsonReaderDriver.class);
private int parsedSRID =0;
private boolean isH2;
private TableLocation tableLocation;
static {
geomTypes = new ArrayList();
geomTypes.add(GeoJsonField.POINT);
geomTypes.add(GeoJsonField.MULTIPOINT);
geomTypes.add(GeoJsonField.LINESTRING);
geomTypes.add(GeoJsonField.MULTILINESTRING);
geomTypes.add(GeoJsonField.POLYGON);
geomTypes.add(GeoJsonField.MULTIPOLYGON);
}
private String firstGeometryType;
private boolean mixedGeometries = false;
/**
* Driver to import a GeoJSON file into a spatial table.
*
* @param connection
* @param fileName
*/
public GeoJsonReaderDriver(Connection connection, File fileName) {
this.connection = connection;
this.fileName = fileName;
}
/**
* Read the GeoJSON file.
*
* @param progress
* @param tableReference
* @throws java.sql.SQLException
* @throws java.io.IOException
*/
public void read(ProgressVisitor progress,String tableReference) throws SQLException, IOException {
if (FileUtil.isFileImportable(fileName, "geojson")) {
this.isH2 =JDBCUtilities.isH2DataBase(connection.getMetaData());
this.tableLocation = TableLocation.parse(tableReference, isH2);
parseGeoJson(progress);
}
}
/**
* Parses a GeoJSON 1.0 file and writes it to a table.
*
* A GeoJSON file is structured as follows:
*
* { "type": "FeatureCollection", "features": [ { "type": "Feature",
* "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, "properties":
* {"prop0": "value0"} }, { "type": "Feature", "geometry": { "type":
* "LineString", "coordinates": [ [102.0, 0.0], [103.0, 1.0], [104.0, 0.0],
* [105.0, 1.0] ] }, "properties": { "prop0": "value0", "prop1": 0.0 } },
* {"type": "Feature", "geometry": { "type": "Polygon", "coordinates": [
* [[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
* ]}, "properties": { "prop0": "value0", "prop1": {"this": "that"} } } ] }
*
* Note: To include information on the coordinate range for geometries, a
* GeoJSON object may have a member named "bbox".
*
* Syntax:
*
* { "type": "FeatureCollection", "bbox": [100.0, 0.0, 105.0, 1.0],
* "features": [ ... ] }
*
*
* @param progress
*/
private void parseGeoJson(ProgressVisitor progress) throws SQLException, IOException {
this.progress = progress.subProcess(100);
init();
if (parseMetadata()) {
GF = new GeometryFactory(new PrecisionModel(), parsedSRID);
parseData();
setGeometryTypeConstraints();
} else {
throw new SQLException("Cannot create the table " + tableLocation + " to import the GeoJSON data");
}
}
/**
* Parses the first GeoJSON feature to create the PreparedStatement.
*
* @throws SQLException
* @throws IOException
*/
private boolean parseMetadata() throws SQLException, IOException {
FileInputStream fis = null;
StringBuilder metadataBuilder = new StringBuilder();
try {
fis = new FileInputStream(fileName);
this.fc = fis.getChannel();
this.fileSize = fc.size();
// Given the file size and an average node file size.
// Skip how many nodes in order to update progression at a step of 1%
readFileSizeEachNode = Math.max(1, (this.fileSize / AVERAGE_NODE_SIZE) / 100);
nodeCountProgress = 0;
JsonParser jp = jsFactory.createParser(fis);
metadataBuilder.append("CREATE TABLE ");
metadataBuilder.append(tableLocation);
metadataBuilder.append(" (");
jp.nextToken();//START_OBJECT
jp.nextToken(); // field_name (type)
jp.nextToken(); // value_string (FeatureCollection)
String geomType = jp.getText();
if (geomType.equalsIgnoreCase(GeoJsonField.FEATURECOLLECTION)) {
jp.nextToken(); // FIELD_NAME features
String firstParam = jp.getText();
//Read the CRS
if (firstParam.equalsIgnoreCase(GeoJsonField.CRS)) {
parsedSRID = readCRS(jp);
readFeatures(jp, geomType, metadataBuilder);
} else if (firstParam.equalsIgnoreCase(GeoJsonField.FEATURES)) {
readFeatures(jp, geomType, metadataBuilder);
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'features', found '" + firstParam + "'");
}
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'FeatureCollection', found '" + geomType + "'");
}
jp.close();
} catch (FileNotFoundException ex) {
throw new SQLException(ex);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ex) {
throw new IOException(ex);
}
}
// Now we create the table if there is at leat one geometry field.
if (hasGeometryField) {
Statement stmt = connection.createStatement();
stmt.execute(metadataBuilder.toString());
stmt.close();
if (fieldIndex > 0) {
StringBuilder insert = new StringBuilder("INSERT INTO ").append(tableLocation).append(" VALUES ( ?");
for (int i = 1; i < fieldIndex; i++) {
insert.append(",?");
}
insert.append(");");
preparedStatement = connection.prepareStatement(insert.toString());
return true;
}
} else {
throw new SQLException("The first feature must contains a geometry field.");
}
return false;
}
/**
* Read the first feature to create the table
* @param jp
*/
private void readFeatures(JsonParser jp, String geomType, StringBuilder metadataBuilder) throws IOException, SQLException {
jp.nextToken(); // START_ARRAY [
JsonToken token = jp.nextToken(); // START_OBJECT {
if (token != JsonToken.END_ARRAY) {
jp.nextToken(); // FIELD_NAME type"name"
jp.nextToken(); // VALUE_STRING Feature
geomType = jp.getText();
if (geomType.equalsIgnoreCase(GeoJsonField.FEATURE)) {
jp.nextToken(); // FIELD_NAME geometry
String firstField = jp.getText();
if (firstField.equalsIgnoreCase(GeoJsonField.GEOMETRY)) {
parseGeometryMetadata(jp, metadataBuilder);
hasGeometryField = true;
fieldIndex++;
jp.nextToken();//END_OBJECT } geometry
} else if (firstField.equalsIgnoreCase(GeoJsonField.PROPERTIES)) {
fieldIndex = parseMetadataProperties(jp, metadataBuilder, fieldIndex);
hasProperties = true;
}
// If there is only one geometry field in the feature them the next
// token corresponds to the end object of the feature.
jp.nextToken();
if (jp.getCurrentToken() != JsonToken.END_OBJECT) {
String secondParam = jp.getText();
if (secondParam.equalsIgnoreCase(GeoJsonField.GEOMETRY)) {
parseGeometryMetadata(jp, metadataBuilder);
hasGeometryField = true;
fieldIndex++;
jp.nextToken();//END_OBJECT } geometry;
} else if (secondParam.equalsIgnoreCase(GeoJsonField.PROPERTIES)) {
fieldIndex = parseMetadataProperties(jp, metadataBuilder, fieldIndex);
hasProperties = true;
}
jp.nextToken(); //END_OBJECT } feature
}
if (!hasProperties) {
metadataBuilder.append(",ID INT, PRIMARY KEY (ID)");
fieldIndex++;
}
metadataBuilder.append(")");
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'Feature', found '" + geomType + "'");
}
}
}
/**
* Parses geometry metadata.
*
* @param jp
* @param metadataBuilder
*/
private void parseGeometryMetadata(JsonParser jp, StringBuilder metadataBuilder) throws IOException, SQLException {
jp.nextToken(); //START_OBJECT {
jp.nextToken(); // FIELD_NAME type
jp.nextToken(); //VALUE_STRING Point
firstGeometryType = jp.getText().toLowerCase();
if(geomTypes.contains(firstGeometryType)){
checkCoordinates(jp, metadataBuilder);
}
else if (firstGeometryType.equals(GeoJsonField.GEOMETRYCOLLECTION)) {
jp.nextToken();//START geometries array
if (jp.getText().equalsIgnoreCase(GeoJsonField.GEOMETRIES)) {
jp.skipChildren();
if(isH2){
metadataBuilder.append("THE_GEOM GEOMETRY");
}
else{
metadataBuilder.append("THE_GEOM GEOMETRY(geometry,").append(parsedSRID).append(")");
}
firstGeometryType=GeoJsonField.GEOMETRY;
mixedGeometries=true;
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'geometries', found '" + jp.getText() + "'");
}
} else {
throw new SQLException("Unsupported geometry : " + firstGeometryType);
}
}
/**
* Check if the geometry is well formatted and contained an array of coordinates.
*
* @param jp
* @param metadataBuilder
* @throws SQLException
* @throws IOException
*/
private void checkCoordinates(JsonParser jp, StringBuilder metadataBuilder) throws SQLException, IOException {
jp.nextToken(); // FIELD_NAME coordinates
if (jp.getText().equalsIgnoreCase(GeoJsonField.COORDINATES)) {
jp.nextToken();//START coordinates array
jp.skipChildren();
if (isH2) {
metadataBuilder.append("THE_GEOM GEOMETRY");
} else {
metadataBuilder.append("THE_GEOM GEOMETRY(geometry,").append(parsedSRID).append(")");
}
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'coordinates', found '" + jp.getText() + "'");
}
}
/**
* Parses the metadata properties.
*
* @param jp
* @return index
*/
private int parseMetadataProperties(JsonParser jp, StringBuilder metadataBuilder, int fieldIndex) throws IOException {
jp.nextToken();//START_OBJECT {
while (jp.nextToken() != JsonToken.END_OBJECT) {
String fieldName = jp.getText().toUpperCase(); //FIELD_NAME columnName
JsonToken value = jp.nextToken();
if(fieldIndex>0){
metadataBuilder.append(",");
}
if (value == JsonToken.VALUE_STRING) {
metadataBuilder.append(fieldName).append(" VARCHAR");
fieldIndex++;
} else if (value == JsonToken.VALUE_TRUE) {
metadataBuilder.append(fieldName).append(" BOOLEAN");
fieldIndex++;
} else if (value == JsonToken.VALUE_FALSE) {
metadataBuilder.append(fieldName).append(" BOOLEAN");
fieldIndex++;
} else if (value == JsonToken.VALUE_NUMBER_FLOAT) {
metadataBuilder.append(fieldName).append(" FLOAT8");
fieldIndex++;
} else if (value == JsonToken.VALUE_NUMBER_INT) {
metadataBuilder.append(fieldName).append(" INT");
fieldIndex++;
} else if (value == JsonToken.VALUE_NULL) {
metadataBuilder.append(fieldName).append(" VARCHAR");
fieldIndex++;
} else {
}
}
return fieldIndex;
}
/**
* Creates the JsonFactory.
*/
private void init() {
jsFactory = new JsonFactory();
jsFactory.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
jsFactory.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
jsFactory.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
}
/**
* Get the PreparedStatement to set the values to the table.
*
* @return
*/
private PreparedStatement getPreparedStatement() {
return preparedStatement;
}
/**
* Features in GeoJSON contain a geometry object and additional properties
*
* Syntax:
*
* { "type": "Feature", "geometry":{"type": "Point", "coordinates": [102.0,
* 0.5]}, "properties": {"prop0": "value0"} }
*
* @param jsParser
*/
private void parseFeature(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME geometry
String firstField = jp.getText();
fieldIndex = 1;
if (firstField.equalsIgnoreCase(GeoJsonField.GEOMETRY)) {
setGeometry(jp);
} else if (firstField.equalsIgnoreCase(GeoJsonField.PROPERTIES)) {
parseProperties(jp, fieldIndex);
}
//If there is only one geometry field in the feature them the next
//token corresponds to the end object of the feature
jp.nextToken();
if (jp.getCurrentToken() != JsonToken.END_OBJECT) {
String secondParam = jp.getText();// field name
if (secondParam.equalsIgnoreCase(GeoJsonField.GEOMETRY)) {
setGeometry(jp);
} else if (secondParam.equalsIgnoreCase(GeoJsonField.PROPERTIES)) {
parseProperties(jp, fieldIndex);
}
jp.nextToken(); //END_OBJECT } feature
}
if (!hasProperties) {
getPreparedStatement().setObject(fieldIndex, featureCounter);
}
getPreparedStatement().execute();
}
/**
* Set the parsed geometry to the table
*
* @param jp
* @throws IOException
* @throws SQLException
*/
private void setGeometry(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); //START_OBJECT {
jp.nextToken(); // FIELD_NAME type
jp.nextToken(); //VALUE_STRING Point
String geometryType = jp.getText();
//Test if mixed or not
if (!mixedGeometries) {
if (!geometryType.equalsIgnoreCase(firstGeometryType)) {
mixedGeometries = true;
firstGeometryType = "geometry";
}
}
getPreparedStatement().setObject(fieldIndex, parseGeometry(jp, geometryType));
fieldIndex++;
}
/**
* Parses a GeoJSON geometry and returns its JTS representation.
*
* Syntax:
*
* "geometry":{"type": "Point", "coordinates": [102.0,0.5]}
*
* @param jsParser
* @throws IOException
* @return Geometry
*/
private Geometry parseGeometry(JsonParser jsParser, String geometryType) throws IOException, SQLException {
if (geometryType.equalsIgnoreCase(GeoJsonField.POINT)) {
return parsePoint(jsParser);
} else if (geometryType.equalsIgnoreCase(GeoJsonField.MULTIPOINT)) {
return parseMultiPoint(jsParser);
} else if (geometryType.equalsIgnoreCase(GeoJsonField.LINESTRING)) {
return parseLinestring(jsParser);
} else if (geometryType.equalsIgnoreCase(GeoJsonField.MULTILINESTRING)) {
return parseMultiLinestring(jsParser);
} else if (geometryType.equalsIgnoreCase(GeoJsonField.POLYGON)) {
return parsePolygon(jsParser);
} else if (geometryType.equalsIgnoreCase(GeoJsonField.MULTIPOLYGON)) {
return parseMultiPolygon(jsParser);
} else if (geometryType.equalsIgnoreCase(GeoJsonField.GEOMETRYCOLLECTION)) {
return parseGeometryCollection(jsParser);
} else {
throw new SQLException("Unsupported geometry : " + geometryType);
}
}
/**
* Parses the properties of a feature
*
* Syntax:
*
* "properties": {"prop0": "value0"}
*
* @param jsParser
*/
private void parseProperties(JsonParser jp, int fieldIndex) throws IOException, SQLException {
jp.nextToken();//START_OBJECT {
while (jp.nextToken() != JsonToken.END_OBJECT) {
JsonToken value = jp.nextToken();
if (value == JsonToken.VALUE_STRING) {
getPreparedStatement().setObject(fieldIndex, jp.getText());
fieldIndex++;
} else if (value == JsonToken.VALUE_TRUE) {
getPreparedStatement().setObject(fieldIndex, jp.getValueAsBoolean());
fieldIndex++;
} else if (value == JsonToken.VALUE_FALSE) {
getPreparedStatement().setObject(fieldIndex, jp.getValueAsBoolean());
fieldIndex++;
} else if (value == JsonToken.VALUE_NUMBER_FLOAT) {
getPreparedStatement().setObject(fieldIndex, jp.getValueAsDouble());
fieldIndex++;
} else if (value == JsonToken.VALUE_NUMBER_INT) {
getPreparedStatement().setObject(fieldIndex, jp.getValueAsInt());
fieldIndex++;
} else if (value == JsonToken.VALUE_NULL) {
getPreparedStatement().setObject(fieldIndex, null);
fieldIndex++;
} else {
//ignore other value
}
}
}
/**
* Parses the featureCollection
*
* @param jp
* @throws IOException
* @throws SQLException
*/
private void parseFeatures(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME features
String firstParam = jp.getText();
if(firstParam.equalsIgnoreCase(GeoJsonField.CRS)){
firstParam = skipCRS(jp);
}
if (firstParam.equalsIgnoreCase(GeoJsonField.FEATURES)) {
jp.nextToken(); // START_ARRAY [
JsonToken token = jp.nextToken(); // START_OBJECT {
while (token != JsonToken.END_ARRAY) {
jp.nextToken(); // FIELD_NAME type
jp.nextToken(); // VALUE_STRING Feature
String geomType = jp.getText();
if (geomType.equalsIgnoreCase(GeoJsonField.FEATURE)) {
if (progress.isCanceled()) {
throw new SQLException("Canceled by user");
}
parseFeature(jp);
token = jp.nextToken(); //START_OBJECT new feature
featureCounter++;
if (nodeCountProgress++ % readFileSizeEachNode == 0) {
// Update Progress
try {
progress.setStep((int) (((double) fc.position() / fileSize) * 100));
} catch (IOException ex) {
// Ignore
}
}
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'Feature', found '" + geomType + "'");
}
}
//LOOP END_ARRAY ]
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'features', found '" + firstParam + "'");
}
}
/**
* Parses one position
*
* Syntax:
*
* { "type": "Point", "coordinates": [100.0, 0.0] }
*
* @param jsParser
* @throws IOException
* @return Point
*/
private Point parsePoint(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME coordinates
String coordinatesField = jp.getText();
if (coordinatesField.equalsIgnoreCase(GeoJsonField.COORDINATES)) {
jp.nextToken(); // START_ARRAY [ to parse the coordinate
Point point = GF.createPoint(parseCoordinate(jp));
return point;
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'coordinates', found '" + coordinatesField + "'");
}
}
/**
* Parses an array of positions
*
* Syntax:
*
* { "type": "MultiPoint", "coordinates": [ [100.0, 0.0], [101.0, 1.0] ] }
*
* @param jsParser
* @throws IOException
* @return MultiPoint
*/
private MultiPoint parseMultiPoint(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME coordinates
String coordinatesField = jp.getText();
if (coordinatesField.equalsIgnoreCase(GeoJsonField.COORDINATES)) {
jp.nextToken(); // START_ARRAY [ coordinates
MultiPoint mPoint = GF.createMultiPoint(parseCoordinates(jp));
jp.nextToken();//END_OBJECT } geometry
return mPoint;
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'coordinates', found '" + coordinatesField + "'");
}
}
/**
*
* Parse the array of positions.
*
* Syntax:
*
* { "type": "LineString", "coordinates": [ [100.0, 0.0], [101.0, 1.0] ] }
*
* @param jsParser
*/
private LineString parseLinestring(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME coordinates
String coordinatesField = jp.getText();
if (coordinatesField.equalsIgnoreCase(GeoJsonField.COORDINATES)) {
jp.nextToken(); // START_ARRAY [ coordinates
LineString line = GF.createLineString(parseCoordinates(jp));
jp.nextToken();//END_OBJECT } geometry
return line;
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'coordinates', found '" + coordinatesField + "'");
}
}
/**
* Parses an array of positions defined as:
*
* { "type": "MultiLineString", "coordinates": [ [ [100.0, 0.0], [101.0,
* 1.0] ], [ [102.0, 2.0], [103.0, 3.0] ] ] }
*
* @param jsParser
* @return MultiLineString
*/
private MultiLineString parseMultiLinestring(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME coordinates
String coordinatesField = jp.getText();
if (coordinatesField.equalsIgnoreCase(GeoJsonField.COORDINATES)) {
ArrayList lineStrings = new ArrayList();
jp.nextToken();//START_ARRAY [ coordinates
jp.nextToken(); // START_ARRAY [ coordinates line
while (jp.getCurrentToken() != JsonToken.END_ARRAY) {
lineStrings.add(GF.createLineString(parseCoordinates(jp)));
jp.nextToken();
}
MultiLineString line = GF.createMultiLineString(lineStrings.toArray(new LineString[lineStrings.size()]));
jp.nextToken();//END_OBJECT } geometry
return line;
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'coordinates', found '" + coordinatesField + "'");
}
}
/**
* Coordinates of a Polygon are an array of LinearRing coordinate arrays.
* The first element in the array represents the exterior ring. Any
* subsequent elements represent interior rings (or holes).
*
* Syntax:
*
* No holes:
*
* { "type": "Polygon", "coordinates": [ [ [100.0, 0.0], [101.0, 0.0],
* [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ] }
*
* With holes:
*
* { "type": "Polygon", "coordinates": [ [ [100.0, 0.0], [101.0, 0.0],
* [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ], [ [100.2, 0.2], [100.8, 0.2],
* [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ] ] }
*
*
*
* @param jp
* @return Polygon
*/
private Polygon parsePolygon(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME coordinates
String coordinatesField = jp.getText();
if (coordinatesField.equalsIgnoreCase(GeoJsonField.COORDINATES)) {
jp.nextToken(); // START_ARRAY [ coordinates
jp.nextToken(); //Start the RING
int linesIndex = 0;
LinearRing linearRing = null;
ArrayList holes = new ArrayList();
while (jp.getCurrentToken() != JsonToken.END_ARRAY) {
if (linesIndex == 0) {
linearRing = GF.createLinearRing(parseCoordinates(jp));
} else {
holes.add(GF.createLinearRing(parseCoordinates(jp)));
}
jp.nextToken();//END RING
linesIndex++;
}
if (linesIndex > 1) {
jp.nextToken();//END_OBJECT } geometry
return GF.createPolygon(linearRing, holes.toArray(new LinearRing[holes.size()]));
} else {
jp.nextToken();//END_OBJECT } geometry
return GF.createPolygon(linearRing, null);
}
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'coordinates', found '" + coordinatesField + "'");
}
}
/**
* Coordinates of a MultiPolygon are an array of Polygon coordinate arrays:
*
* { "type": "MultiPolygon", "coordinates": [ [[[102.0, 2.0], [103.0, 2.0],
* [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[100.0, 0.0], [101.0, 0.0],
* [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], [[100.2, 0.2], [100.8, 0.2],
* [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]] ] }
*
* @param jp
* @throws IOException
* @throws SQLException
* @return MultiPolygon
*/
private MultiPolygon parseMultiPolygon(JsonParser jp) throws IOException, SQLException {
jp.nextToken(); // FIELD_NAME coordinates
String coordinatesField = jp.getText();
if (coordinatesField.equalsIgnoreCase(GeoJsonField.COORDINATES)) {
ArrayList polygons = new ArrayList();
jp.nextToken(); // START_ARRAY [ coordinates
jp.nextToken(); //Start the polygon
while (jp.getCurrentToken() != JsonToken.END_ARRAY) {
//Parse the polygon
jp.nextToken(); //Start the RING
int linesIndex = 0;
LinearRing linearRing = null;
ArrayList holes = new ArrayList();
while (jp.getCurrentToken() != JsonToken.END_ARRAY) {
if (linesIndex == 0) {
linearRing = GF.createLinearRing(parseCoordinates(jp));
} else {
holes.add(GF.createLinearRing(parseCoordinates(jp)));
}
jp.nextToken();//END RING
linesIndex++;
}
if (linesIndex > 1) {
jp.nextToken();//END_OBJECT
polygons.add(GF.createPolygon(linearRing, holes.toArray(new LinearRing[holes.size()])));
} else {
jp.nextToken();//END_OBJECT
polygons.add(GF.createPolygon(linearRing, null));
}
}
jp.nextToken();//END_OBJECT } geometry
return GF.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()]));
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'coordinates', found '" + coordinatesField + "'");
}
}
/**
* Each element in the geometries array of a GeometryCollection is one of
* the geometry objects described above:
*
* { "type": "GeometryCollection", "geometries": [ { "type": "Point",
* "coordinates": [100.0, 0.0] }, { "type": "LineString", "coordinates": [
* [101.0, 0.0], [102.0, 1.0] ] } ]
*
* @param jp
*
* @throws IOException
* @throws SQLException
* @return GeometryCollection
*/
private GeometryCollection parseGeometryCollection(JsonParser jp) throws IOException, SQLException {
firstGeometryType=GeoJsonField.GEOMETRY;
jp.nextToken(); // FIELD_NAME geometries
String coordinatesField = jp.getText();
if (coordinatesField.equalsIgnoreCase(GeoJsonField.GEOMETRIES)) {
jp.nextToken();//START array
jp.nextToken();//START object
ArrayList geometries = new ArrayList();
while (jp.getCurrentToken() != JsonToken.END_ARRAY) {
jp.nextToken(); // FIELD_NAME type
jp.nextToken(); //VALUE_STRING Point
String geometryType = jp.getText();
geometries.add(parseGeometry(jp, geometryType));
jp.nextToken();
}
jp.nextToken();//END_OBJECT } geometry
return GF.createGeometryCollection(geometries.toArray(new Geometry[geometries.size()]));
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'geometries', found '" + coordinatesField + "'");
}
}
/**
* Parses a sequence of coordinates array expressed as
*
* [ [100.0, 0.0], [101.0, 1.0] ]
*
* @param jp
* @throws IOException
* @throws SQLException
* @return Coordinate[]
*/
private Coordinate[] parseCoordinates(JsonParser jp) throws IOException {
jp.nextToken(); // START_ARRAY [ to parse the each positions
ArrayList coords = new ArrayList();
while (jp.getCurrentToken() != JsonToken.END_ARRAY) {
coords.add(parseCoordinate(jp));
}
return coords.toArray(new Coordinate[coords.size()]);
}
/**
* Parses a GeoJSON coordinate array and returns a JTS coordinate. The first
* token corresponds to the first X value. The last token correponds to the
* end of the coordinate array "]".
*
* Parsed syntax:
*
* 100.0, 0.0]
*
* @param jp
* @throws IOException
* @return Coordinate
*/
private Coordinate parseCoordinate(JsonParser jp) throws IOException {
jp.nextToken();
double x = jp.getDoubleValue();// VALUE_NUMBER_FLOAT
jp.nextToken(); // second value
double y = jp.getDoubleValue();
Coordinate coord;
//We look for a z value
jp.nextToken();
if (jp.getCurrentToken() == JsonToken.END_ARRAY) {
coord = new Coordinate(x, y);
} else {
double z = jp.getDoubleValue();
jp.nextToken(); // exit array
coord = new Coordinate(x, y, z);
}
jp.nextToken();
return coord;
}
/**
* Parses the GeoJSON data and set the values to the table.
*
* @throws IOException
* @throws SQLException
*/
private void parseData() throws IOException, SQLException {
FileInputStream fis = null;
try {
fis = new FileInputStream(fileName);
JsonParser jp = jsFactory.createParser(fis);
jp.nextToken();//START_OBJECT
jp.nextToken(); // field_name (type)
jp.nextToken(); // value_string (FeatureCollection)
String geomType = jp.getText();
if (geomType.equalsIgnoreCase(GeoJsonField.FEATURECOLLECTION)) {
parseFeatures(jp);
} else {
throw new SQLException("Malformed GeoJSON file. Expected 'FeatureCollection', found '" + geomType + "'");
}
jp.close();
} catch (FileNotFoundException ex) {
throw new SQLException(ex);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ex) {
throw new SQLException(ex);
}
}
}
/**
* Read the CRS element and return the database SRID.
*
* Parsed syntax:
*
* "crs":{
* "type":"name",
* "properties":
* {"name":"urn:ogc:def:crs:EPSG::4326"
* }
* }
*
* @param jp
* @return
*/
private int readCRS(JsonParser jp) throws IOException, SQLException {
int srid = 0;
jp.nextToken(); //START_OBJECT {
jp.nextToken();// crs type
jp.nextToken(); // crs name
String firstField = jp.getText();
if(firstField.equalsIgnoreCase(GeoJsonField.NAME)){
jp.nextToken(); // crs properties
jp.nextToken(); //START_OBJECT {
jp.nextToken(); // crs name
jp.nextToken(); // crs value
String crsURI = jp.getText();
String[] split = crsURI.toLowerCase().split(GeoJsonField.CRS_URN_EPSG);
if (split != null) {
srid = Integer.valueOf(split[1]);
} else {
log.warn("The CRS URN " + crsURI + " is not supported.");
}
jp.nextToken(); //END_OBJECT }
jp.nextToken(); //END_OBJECT }
jp.nextToken(); //Go to features
}
else if (firstField.equalsIgnoreCase(GeoJsonField.LINK)) {
log.warn("Linked CRS is not supported.");
jp.nextToken();
jp.nextToken();
jp.nextToken(); //END_OBJECT }
jp.nextToken(); //END_OBJECT }
jp.nextToken(); //Go to features
}
else{
throw new SQLException("Malformed GeoJSON CRS element.");
}
return srid;
}
/**
* We skip the CRS because it has been already parsed.
*
*
* @param jp
*/
private String skipCRS(JsonParser jp) throws IOException {
jp.nextToken(); //START_OBJECT {
jp.skipChildren();
jp.nextToken(); //Go to features
return jp.getText();
}
/**
* Add the geometry type constraint and the SRID
*/
private void setGeometryTypeConstraints() throws SQLException {
if(isH2){
connection.createStatement().execute(String.format("ALTER TABLE %s ALTER COLUMN the_geom %s", tableLocation.toString(), firstGeometryType));
SFSUtilities.addTableSRIDConstraint(connection, tableLocation, parsedSRID);
}
else{
connection.createStatement().execute(String.format("ALTER TABLE %s ALTER COLUMN the_geom SET DATA TYPE geometry(%s,%d)", tableLocation.toString(), firstGeometryType, parsedSRID));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy