All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.h2gis.functions.io.geojson.GeoJsonWriteDriver Maven / Gradle / Ivy

There is a newer version: 1.3.2
Show newest version
/**
 * H2GIS is a library that brings spatial support to the H2 Database Engine
 * . H2GIS is developed by CNRS
 * .
 *
 * This code is part of the H2GIS project. H2GIS 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;
 * version 3.0 of the License.
 *
 * 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 Lesser General Public License
 * for more details .
 *
 *
 * For more information, please consult: 
 * or contact directly: info_at_h2gis.org
 */

package org.h2gis.functions.io.geojson;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.vividsolutions.jts.geom.*;
import org.h2gis.api.ProgressVisitor;
import org.h2gis.utilities.JDBCUtilities;
import org.h2gis.utilities.SFSUtilities;
import org.h2gis.utilities.TableLocation;

import java.io.*;
import java.sql.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.h2gis.functions.io.utility.FileUtil;

/**
 * A simple GeoJSON driver to write a spatial table to a GeoJSON file.
 *
 * GeoJSON is a format for encoding a variety of geographic data structures. A
 * GeoJSON object may represent a geometry, a feature, or a collection of
 * features. GeoJSON supports the following geometry types: POINT, LINESTRING,
 * POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, and GEOMETRYCOLLECTION.
 *
 * Syntax:
 *
 * { "type": "FeatureCollection", "features": [ { "type": "Feature",
 * "geometry":{"type": "Point", "coordinates": [102.0, 0.5]}, "properties":
 * {"prop0": "value0"} } ]}
 *
 * @author Erwan Bocher
 */
public class GeoJsonWriteDriver {

    private final String tableName;
    private final File fileName;
    private final Connection connection;
    private Map cachedColumnNames;
    private int columnCountProperties = -1;

    /**
     * A simple GeoJSON driver to write a spatial table to a GeoJSON file.
     *
     * @param connection
     * @param tableName
     * @param fileName
     */
    public GeoJsonWriteDriver(Connection connection, String tableName, File fileName) {
        this.connection = connection;
        this.tableName = tableName;
        this.fileName = fileName;
    }

    /**
     * Write the spatial table to GeoJSON format.
     *
     * @param progress
     * @throws SQLException
     * @throws java.io.IOException
     */
    public void write(ProgressVisitor progress) throws SQLException, IOException {        
        if (FileUtil.isExtensionWellFormated(fileName, "geojson")) {
            writeGeoJson(progress);
        } else {
            throw new SQLException("Only .geojson extension is supported");
        }
    }

    /**
     * Write the spatial table to GeoJSON format.
     *
     * @param progress
     * @throws SQLException
     */
    private void writeGeoJson(ProgressVisitor progress) throws SQLException, IOException {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(fileName);
            // Read Geometry Index and type
            final TableLocation parse =  TableLocation.parse(tableName, JDBCUtilities.isH2DataBase(connection.getMetaData()));
            List spatialFieldNames = SFSUtilities.getGeometryFields(connection,parse);
            if (spatialFieldNames.isEmpty()) {
                throw new SQLException(String.format("The table %s does not contain a geometry field", tableName));
            }

            // Read table content
            Statement st = connection.createStatement();
            try {                
                JsonFactory jsonFactory = new JsonFactory();
                JsonGenerator jsonGenerator = jsonFactory.createGenerator(new BufferedOutputStream(fos), JsonEncoding.UTF8);

                // header of the GeoJSON file
                jsonGenerator.writeStartObject();
                jsonGenerator.writeStringField("type", "FeatureCollection");
                writeCRS(jsonGenerator,SFSUtilities.getAuthorityAndSRID(connection, parse, spatialFieldNames.get(0)));
                jsonGenerator.writeArrayFieldStart("features");
                
                ResultSet rs = st.executeQuery(String.format("select * from %s", tableName));

                try {
                    ResultSetMetaData resultSetMetaData = rs.getMetaData();
                    int geoFieldIndex = JDBCUtilities.getFieldIndex(resultSetMetaData, spatialFieldNames.get(0));

                    cacheMetadata(resultSetMetaData);
                    while (rs.next()) {
                        writeFeature(jsonGenerator, rs, geoFieldIndex);
                    }
                    progress.endStep();
                    // footer
                    jsonGenerator.writeEndArray();
                    jsonGenerator.writeEndObject();
                    jsonGenerator.flush();
                    jsonGenerator.close();

                } finally {
                    rs.close();
                }
            } finally {
                st.close();
            }
        } catch (FileNotFoundException ex) {
            throw new SQLException(ex);

        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException ex) {
                throw new SQLException(ex);
            }
        }
    }

    /**
     * Write a GeoJSON feature.
     *
     * Features in GeoJSON contain a geometry object and additional properties,
     * and a feature collection represents a list of features.
     *
     * A complete GeoJSON data structure is always an object (in JSON terms). In
     * GeoJSON, an object consists of a collection of name/value pairs -- also
     * called members. For each member, the name is always a string. Member
     * values are either a string, number, object, array or one of the literals:
     * true, false, and null. An array consists of elements where each element
     * is a value as described above.
     *
     * Syntax:
     *
     * { "type": "Feature", "geometry":{"type": "Point", "coordinates": [102.0,
     * 0.5]}, "properties": {"prop0": "value0"} }
     *
     * @param writer
     * @param resultSetMetaData
     * @param geoFieldIndex
     */
    private void writeFeature(JsonGenerator jsonGenerator, ResultSet rs, int geoFieldIndex) throws IOException, SQLException {
        // feature header
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("type", "Feature");
        //Write the first geometry
        writeGeometry((Geometry) rs.getObject(geoFieldIndex), jsonGenerator);
        //Write the properties
        writeProperties(jsonGenerator, rs);
        // feature footer
        jsonGenerator.writeEndObject();
    }

    /**
     * Cache the column name and its index.
     *
     * @param resultSetMetaData
     * @throws SQLException
     */
    private void cacheMetadata(ResultSetMetaData resultSetMetaData) throws SQLException {
        cachedColumnNames = new LinkedHashMap();
        for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
            final String fieldTypeName = resultSetMetaData.getColumnTypeName(i);
            if (!fieldTypeName.equalsIgnoreCase("geometry")
                    && isSupportedPropertyType(resultSetMetaData.getColumnType(i), fieldTypeName)) {
                cachedColumnNames.put(resultSetMetaData.getColumnName(i).toUpperCase(), i);
                columnCountProperties++;
            }
        }
    }

    /**
     * Write a JTS geometry to its GeoJSON geometry representation.
     * 
     * Syntax:
     *
     * "geometry":{"type": "Point", "coordinates": [102.0, 0.5]}
     *
     * @param jsonGenerator
     * @param geometry
     */
    private void writeGeometry(Geometry geom, JsonGenerator gen) throws IOException {
        gen.writeObjectFieldStart("geometry");
        if (geom instanceof Point) {
            write((Point) geom, gen);
        } else if (geom instanceof MultiPoint) {
            write((MultiPoint) geom, gen);
        } else if (geom instanceof LineString) {
            write((LineString) geom, gen);
        } else if (geom instanceof MultiLineString) {
            write((MultiLineString) geom, gen);
        } else if (geom instanceof Polygon) {
            write((Polygon) geom, gen);
        } else if (geom instanceof MultiPolygon) {
            write((MultiPolygon) geom, gen);
        } else if (geom instanceof GeometryCollection) {
            write((GeometryCollection) geom, gen);
        } else {
            throw new RuntimeException("Unsupported Geomery type");
        }
        gen.writeEndObject();
    }

    /**
     * Point coordinates are in x, y order (easting, northing for projected
     * coordinates, longitude, latitude for geographic coordinates):
     *
     * { "type": "Point", "coordinates": [100.0, 0.0] }
     *
     *
     * @param point
     * @param gen
     * @throws IOException
     */
    private void write(Point point, JsonGenerator gen) throws IOException {
        gen.writeStringField("type", "Point");
        gen.writeFieldName("coordinates");
        writeCoordinate(point.getCoordinate(), gen);
    }

    /**
     * Coordinates of a MultiPoint are an array of positions:
     *
     * { "type": "MultiPoint", "coordinates": [ [100.0, 0.0], [101.0, 1.0] ] }
     *
     *
     * @param points
     * @param gen
     * @throws IOException
     */
    private void write(MultiPoint points, JsonGenerator gen) throws IOException {
        gen.writeStringField("type", "MultiPoint");
        gen.writeFieldName("coordinates");
        writeCoordinates(points.getCoordinates(), gen);
    }

    /**
     * Coordinates of LineString are an array of positions :
     *
     * { "type": "LineString", "coordinates": [ [100.0, 0.0], [101.0, 1.0] ] }
     *
     * @param geom
     * @param gen
     * @throws IOException
     */
    private void write(LineString geom, JsonGenerator gen) throws IOException {
        gen.writeStringField("type", "LineString");
        gen.writeFieldName("coordinates");
        writeCoordinates(geom.getCoordinates(), gen);
    }

    /**
     * Coordinates of a MultiLineString are an array of LineString coordinate
     * arrays:
     *
     * { "type": "MultiLineString", "coordinates": [ [ [100.0, 0.0], [101.0,
     * 1.0] ], [ [102.0, 2.0], [103.0, 3.0] ] ] }
     *
     * @param geom
     * @param gen
     * @throws IOException
     */
    private void write(MultiLineString geom, JsonGenerator gen) throws IOException {
        gen.writeStringField("type", "MultiLineString");
        gen.writeFieldName("coordinates");
        gen.writeStartArray();
        for (int i = 0; i < geom.getNumGeometries(); ++i) {
            writeCoordinates(geom.getGeometryN(i).getCoordinates(), gen);
        }
        gen.writeEndArray();
    }

    /**
     * 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 coll
     * @param gen
     * @throws IOException
     */
    private void write(GeometryCollection coll, JsonGenerator gen) throws IOException {
        gen.writeStringField("type", "GeometryCollection");
        gen.writeArrayFieldStart("geometries");
        for (int i = 0; i < coll.getNumGeometries(); ++i) {
            Geometry geom = coll.getGeometryN(i);
            gen.writeStartObject();
            if (geom instanceof Point) {
                write((Point) geom, gen);
            } else if (geom instanceof MultiPoint) {
                write((MultiPoint) geom, gen);
            } else if (geom instanceof LineString) {
                write((LineString) geom, gen);
            } else if (geom instanceof MultiLineString) {
                write((MultiLineString) geom, gen);
            } else if (geom instanceof Polygon) {
                write((Polygon) geom, gen);
            } else if (geom instanceof MultiPolygon) {
                write((MultiPolygon) geom, gen);
            } else if (geom instanceof GeometryCollection) {
                write((GeometryCollection) geom, gen);
            } else {
                throw new RuntimeException("Unsupported Geomery type");
            }
             gen.writeEndObject();
        }
        gen.writeEndArray();
    }

    /**
     * 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).
     *
     * 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 geom
     * @param gen
     * @throws IOException
     */
    private void write(Polygon geom, JsonGenerator gen) throws IOException {
        gen.writeStringField("type", "Polygon");
        gen.writeFieldName("coordinates");
        gen.writeStartArray();
        writeCoordinates(geom.getExteriorRing().getCoordinates(), gen);
        for (int i = 0; i < geom.getNumInteriorRing(); ++i) {
            writeCoordinates(geom.getInteriorRingN(i).getCoordinates(), gen);
        }
        gen.writeEndArray();
    }

    /**
     *
     *
     * 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 geom
     * @param gen
     * @throws IOException
     */
    private void write(MultiPolygon geom, JsonGenerator gen) throws IOException {
        gen.writeStringField("type", "MultiPolygon");
        gen.writeFieldName("coordinates");
        gen.writeStartArray();
        for (int i = 0; i < geom.getNumGeometries(); ++i) {
            Polygon p = (Polygon) geom.getGeometryN(i);
            gen.writeStartArray();
            writeCoordinates(p.getExteriorRing().getCoordinates(), gen);
            for (int j = 0; j < p.getNumInteriorRing(); ++j) {
                writeCoordinates(p.getInteriorRingN(j).getCoordinates(), gen);
            }
            gen.writeEndArray();
        }
        gen.writeEndArray();
    }

    /**
     * Write coordinate positions.
     *
     * @param coordinate
     * @param gen
     * @throws IOException
     */
    private void writeCoordinate(Coordinate coordinate, JsonGenerator gen) throws IOException {
        gen.writeStartArray();
        gen.writeNumber(coordinate.x);
        gen.writeNumber(coordinate.y);
        if (!Double.isNaN(coordinate.z)) {
            gen.writeNumber(coordinate.z);
        }
        gen.writeEndArray();
    }

    /**
     * Write coordinate array.
     *
     * @param coordinates
     * @param gen
     * @throws IOException
     */
    private void writeCoordinates(Coordinate[] coordinates, JsonGenerator gen) throws IOException {
        gen.writeStartArray();
        for (Coordinate coord : coordinates) {
            writeCoordinate(coord, gen);
        }
        gen.writeEndArray();
    }

    /**
     * Write the GeoJSON properties.
     *
     * @param jsonGenerator
     * @param rs
     * @throws IOException
     */
    private void writeProperties(JsonGenerator jsonGenerator, ResultSet rs) throws IOException, SQLException {
        if (columnCountProperties != -1) {
            jsonGenerator.writeObjectFieldStart("properties");
            for (Map.Entry entry : cachedColumnNames.entrySet()) {
                String string = entry.getKey();
                Integer fieldId = entry.getValue();
                jsonGenerator.writeObjectField(string, rs.getObject(fieldId));
            }
            jsonGenerator.writeEndObject();
        }
    }

    /**
     * Return true is the SQL type is supported by the GeoJSON driver.
     *
     * @param sqlTypeId
     * @param sqlTypeName
     * @return
     * @throws SQLException
     */
    public boolean isSupportedPropertyType(int sqlTypeId, String sqlTypeName) throws SQLException {
        switch (sqlTypeId) {
            case Types.BOOLEAN:
            case Types.DOUBLE:
            case Types.FLOAT:
            case Types.INTEGER:
            case Types.BIGINT:
            case Types.SMALLINT:
            case Types.DATE:
            case Types.VARCHAR:
            case Types.NCHAR:
            case Types.CHAR:
                return true;
            default:
                throw new SQLException("Field type not supported by GeoJSON driver: " + sqlTypeName);
        }
    }

    /**
     * Write the CRS in the geojson
     *
     * @param jsonGenerator
     * @param authorityAndSRID
     * @throws IOException
     */
    private void writeCRS(JsonGenerator jsonGenerator, String[] authorityAndSRID) throws IOException {
        if (authorityAndSRID[1] != null) {
            jsonGenerator.writeObjectFieldStart("crs");
            jsonGenerator.writeStringField("type", "name");
            jsonGenerator.writeObjectFieldStart("properties");
            StringBuilder sb = new StringBuilder("urn:ogc:def:crs:");
            sb.append(authorityAndSRID[0]).append("::").append(authorityAndSRID[1]);
            jsonGenerator.writeStringField("name", sb.toString());
            jsonGenerator.writeEndObject();
            jsonGenerator.writeEndObject();
        }
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy