/* * This file is part of the GeoLatte project. * * GeoLatte 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 3 of the License, or * (at your option) any later version. * * GeoLatte 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 GeoLatte. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) 2010 - 2011 and Ownership of code is shared by: * Qmino bvba - Esperantolaan 4 - 3001 Heverlee (http://www.qmino.com) * Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com) */ package org.geolatte.common.dataformats.json.to; import org.geolatte.geom.*; import org.geolatte.geom.crs.CrsId; /** * Factory which allows conversion between GeoJsonTo objects on the one hand, and Geolatte-geometries or JTS * geometries on the other hand. * * @author Yves Vandewoude * @author <a href="http://www.qmino.com">Qmino bvba</a> */ public class GeoJsonToAssembler { //region fromTransferObject methods /** * Creates the correct TO starting from any geolatte geometry. * * @param geometry the geometry to convert * @return a TO that, once serialized, results in a valid geoJSON representation of the geometry */ public GeoJsonTo toTransferObject(Geometry geometry) { if (geometry instanceof Point) { return toTransferObject((Point) geometry); } else if (geometry instanceof LineString) { return toTransferObject((LineString) geometry); } else if (geometry instanceof MultiPoint) { return toTransferObject((MultiPoint) geometry); } else if (geometry instanceof MultiLineString) { return toTransferObject((MultiLineString) geometry); } else if (geometry instanceof Polygon) { return toTransferObject((Polygon) geometry); } else if (geometry instanceof MultiPolygon) { return toTransferObject((MultiPolygon) geometry); } else if (geometry instanceof GeometryCollection) { return toTransferObject((GeometryCollection) geometry); } return null; } /** * Converts a polygon to its corresponding Transfer Object * * @param input a polygon object * @return a transfer object for the polygon */ public PolygonTo toTransferObject(Polygon input) { PolygonTo result = new PolygonTo(); result.setCrs(GeoJsonTo.createCrsTo("EPSG:" + input.getSRID())); double[][][] rings = new double[input.getNumInteriorRing() + 1][][]; // Exterior ring: rings[0] = getPoints(input.getExteriorRing()); // Interior rings! for (int i = 0; i < input.getNumInteriorRing(); i++) { rings[i + 1] = getPoints(input.getInteriorRingN(i)); } result.setCoordinates(rings); return result; } /** * Converts a multilinestring to its corresponding Transfer Object * * @param input the multilinestring * @return a transfer object for the multilinestring */ public MultiLineStringTo toTransferObject(MultiLineString input) { MultiLineStringTo result = new MultiLineStringTo(); double[][][] resultCoordinates = new double[input.getNumGeometries()][][]; for (int i = 0; i < input.getNumGeometries(); i++) { resultCoordinates[i] = getPoints(input.getGeometryN(i)); } result.setCoordinates(resultCoordinates); result.setCrs(GeoJsonTo.createCrsTo("EPSG:" + input.getSRID())); return result; } /** * Converts a multipoint to its corresponding Transfer Object * * @param input the multipoint * @return a transfer object for the multipoint */ public MultiPointTo toTransferObject(MultiPoint input) { MultiPointTo result = new MultiPointTo(); result.setCrs(GeoJsonTo.createCrsTo("EPSG:" + input.getSRID())); result.setCoordinates(getPoints(input)); return result; } /** * Converts a point to its corresponding Transfer Object * * @param input the point object * @return the transfer object for the point */ public PointTo toTransferObject(Point input) { PointTo result = new PointTo(); result.setCrs(GeoJsonTo.createCrsTo("EPSG:" + input.getSRID())); result.setCoordinates(getPoints(input)[0]); return result; } /** * Converts a linestring to its corresponding Transfer Object * * @param input the linestring object to convert * @return the transfer object for the linestring */ public LineStringTo toTransferObject(LineString input) { LineStringTo result = new LineStringTo(); result.setCrs(GeoJsonTo.createCrsTo("EPSG:" + input.getSRID())); result.setCoordinates(getPoints(input)); return result; } /** * Converts a multipolygon to its corresponding Transfer Object * * @param input the multipolygon * @return the transfer object for the multipolygon */ public MultiPolygonTo toTransferObject(MultiPolygon input) { MultiPolygonTo result = new MultiPolygonTo(); double[][][][] coordinates = new double[input.getNumGeometries()][][][]; for (int i = 0; i < input.getNumGeometries(); i++) { coordinates[i] = toTransferObject(input.getGeometryN(i)).getCoordinates(); } result.setCoordinates(coordinates); result.setCrs(GeoJsonTo.createCrsTo("EPSG:" + input.getSRID())); return result; } /** * Converts a multipolygon to its corresponding Transfer Object * * @param input the multipolygon * @return the transfer object for the geometrycollection */ public GeometryCollectionTo toTransferObject(GeometryCollection input) { GeometryCollectionTo result = new GeometryCollectionTo(); GeoJsonTo[] tos = new GeoJsonTo[input.getNumGeometries()]; for (int i = 0; i < input.getNumGeometries(); i++) { tos[i] = toTransferObject(input.getGeometryN(i)); tos[i].setCrs(null); // Crs may not be repeated } result.setGeometries(tos); result.setCrs(GeoJsonTo.createCrsTo("EPSG:" + input.getSRID())); return result; } //endregion //region fromTransferObject methods /** * Creates a geolatte geometry object starting from a geojsonto. * * @param input the geojson to to start from * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid * GeoJsonTo */ public Geometry fromTransferObject(GeoJsonTo input) throws IllegalArgumentException { return fromTransferObject(input, null); } /** * Creates a geolatte geometry object starting from a GeoJsonTo. * * @param input the geojson to to start from * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid * geojsonto */ public Geometry fromTransferObject(GeoJsonTo input, CrsId crsId) throws IllegalArgumentException { if (input instanceof PointTo) { return fromTransferObject((PointTo) input, crsId); } else if (input instanceof MultiPointTo) { return fromTransferObject((MultiPointTo) input, crsId); } else if (input instanceof LineStringTo) { return fromTransferObject((LineStringTo) input, crsId); } else if (input instanceof MultiLineStringTo) { return fromTransferObject((MultiLineStringTo) input, crsId); } else if (input instanceof PolygonTo) { return fromTransferObject((PolygonTo) input, crsId); } else if (input instanceof MultiPolygonTo) { return fromTransferObject((MultiPolygonTo) input, crsId); } else if (input instanceof GeometryCollectionTo) { return fromTransferObject((GeometryCollectionTo) input, crsId); } return null; } /** * Creates a polygon starting from a transfer object. * * @param input the transfer object * @return a polygon corresponding to the transfer object * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public Polygon fromTransferObject(PolygonTo input) { return fromTransferObject(input, null); } /** * Creates a polygon object starting from a transfer object. * * @param input the polygon transfer object * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public Polygon fromTransferObject(PolygonTo input, CrsId crsId) { if (input == null) { return null; } crsId = getCrsId(input, crsId); isValid(input); return createPolygon(input.getCoordinates(), crsId); } /** * Creates a geometrycollection starting from a transfer object. * * @param input the transfer object * @return a geometrycollection corresponding to the transfer object * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public GeometryCollection fromTransferObject(GeometryCollectionTo input) { return fromTransferObject(input, null); } /** * Creates a geometrycollection object starting from a transfer object. * * @param input the geometry collection transfer object * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public GeometryCollection fromTransferObject(GeometryCollectionTo input, CrsId crsId) { if (input == null) { return null; } crsId = getCrsId(input, crsId); isValid(input); Geometry[] geoms = new Geometry[input.getGeometries().length]; for (int i = 0; i < geoms.length; i++) { geoms[i] = fromTransferObject(input.getGeometries()[i], crsId); } return new GeometryCollection(geoms); } /** * Creates a multipolygon starting from a transfer object. * * @param input the transfer object * @return a multipolygon corresponding to the transfer object * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public MultiPolygon fromTransferObject(MultiPolygonTo input) { return fromTransferObject(input, null); } /** * Creates a multipolygon object starting from a transfer object. * * @param input the multipolygon transfer object * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public MultiPolygon fromTransferObject(MultiPolygonTo input, CrsId crsId) { if (input == null) { return null; } crsId = getCrsId(input, crsId); isValid(input); Polygon[] polygons = new Polygon[input.getCoordinates().length]; for (int i = 0; i < polygons.length; i++) { polygons[i] = createPolygon(input.getCoordinates()[i], crsId); } return new MultiPolygon(polygons); } /** * Creates a multilinestring starting from a transfer object. * * @param input the transfer object * @return a multilinestring corresponding to the transfer object * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public MultiLineString fromTransferObject(MultiLineStringTo input) { return fromTransferObject(input, null); } /** * Creates a multilinestring object starting from a transfer object. * * @param input the multilinestring transfer object * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public MultiLineString fromTransferObject(MultiLineStringTo input, CrsId crsId) { if (input == null) { return null; } crsId = getCrsId(input, crsId); isValid(input); LineString[] lineStrings = new LineString[input.getCoordinates().length]; for (int i = 0; i < lineStrings.length; i++) { lineStrings[i] = new LineString(createPointSequence(input.getCoordinates()[i], crsId)); } return new MultiLineString(lineStrings); } /** * Creates a linestring starting from a transfer object. * * @param input the transfer object * @return a linestring corresponding to the transfer object * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public LineString fromTransferObject(LineStringTo input) { return fromTransferObject(input, null); } /** * Creates a linestring object starting from a transfer object. * * @param input the linestring transfer object * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public LineString fromTransferObject(LineStringTo input, CrsId crsId) { if (input == null) { return null; } crsId = getCrsId(input, crsId); isValid(input); return new LineString(createPointSequence(input.getCoordinates(), crsId)); } /** * Creates a multipoint starting from a transfer object. * * @param input the transfer object * @return a multipoint corresponding to the transfer object * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public MultiPoint fromTransferObject(MultiPointTo input) { return fromTransferObject(input, null); } /** * Creates a multipoint object starting from a transfer object. * * @param input the multipoint transfer object * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public MultiPoint fromTransferObject(MultiPointTo input, CrsId crsId) { if (input == null) { return null; } crsId = getCrsId(input, crsId); isValid(input); Point[] points = new Point[input.getCoordinates().length]; for (int i = 0; i < points.length; i++) { points[i] = createPoint(input.getCoordinates()[i], crsId); } return new MultiPoint(points); } /** * Creates a point starting from a transfer object. * * @param input the transfer object * @return a point corresponding to the transfer object * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public Point fromTransferObject(PointTo input) { return fromTransferObject(input, null); } /** * Creates a point object starting from a transfer object. * * @param input the point transfer object * @param crsId the crs id to use (ignores the crs of the input). If null, uses the crs of the input. * @return the corresponding geometry * @throws IllegalArgumentException If the geometry could not be constructed due to an invalid transfer object */ public Point fromTransferObject(PointTo input, CrsId crsId) { if (input == null) { return null; } crsId = getCrsId(input, crsId); isValid(input); return createPoint(input.getCoordinates(), crsId); } //endregion /** * Creates a polygon starting from its geojson coordinate array * * @param coordinates the geojson coordinate array * @param crsId the srid of the crs to use * @return a geolatte polygon instance */ private Polygon createPolygon(double[][][] coordinates, CrsId crsId) { LinearRing[] rings = new LinearRing[coordinates.length]; for (int i = 0; i < coordinates.length; i++) { rings[i] = new LinearRing(createPointSequence(coordinates[i], crsId)); } return new Polygon(rings); } /** * Helpermethod that creates a geolatte pointsequence starting from an array containing coordinate arrays * * @param coordinates an array containing coordinate arrays * @return a geolatte pointsequence or null if the coordinatesequence was null */ private PointSequence createPointSequence(double[][] coordinates, CrsId crsId) { if (coordinates == null) { return null; } else if (coordinates.length == 0) { return PointCollectionFactory.createEmpty(); } DimensionalFlag df = coordinates[0].length == 4 ? DimensionalFlag.d3DM : coordinates[0].length == 3 ? DimensionalFlag.d3D : DimensionalFlag.d2D; PointSequenceBuilder psb = PointSequenceBuilders.variableSized(df, crsId); for (double[] point : coordinates) { psb.add(point); } return psb.toPointSequence(); } /** * Helpermethod that creates a point starting from its geojsonto coordinate array * * @param input the coordinate array to convert to a point * @param crsIdValue the sridvalue of the crs in which the point is defined * @return an instance of a geolatte point corresponding to the given to or null if the given array is null */ private Point createPoint(double[] input, CrsId crsIdValue) { if (input == null) { return null; } if (input.length == 2) { return Points.create2D(input[0], input[1], crsIdValue); } else if(input.length == 3){ return Points.create3D(input[0], input[1], input[2], crsIdValue); } else { double z = input[2]; double m = input[3]; if(Double.isNaN(z)) { return Points.create2DM(input[0],input[1],m,crsIdValue); } else { return Points.create3DM(input[0], input[1], z, m, crsIdValue); } } } /** * Serializes all points of the input into a list of their coordinates * * @param input a geometry whose points are to be converted to a list of coordinates * @return an array containing arrays with x,y and optionally z and m values. */ private double[][] getPoints(Geometry input) { double[][] result = new double[input.getNumPoints()][]; for (int i = 0; i < input.getPoints().size(); i++) { Point p = input.getPointN(i); if(p.isMeasured() && p.is3D()) { result[i] = new double[]{p.getX(), p.getY(), p.getZ(), p.getM()}; } else if(p.isMeasured()) { // ideally we'd use something like Double.Nan, but JSON doesn't support that. result[i] = new double[]{p.getX(), p.getY(), 0, p.getM()}; } else if(p.is3D()) { result[i] = new double[]{p.getX(), p.getY(), p.getZ()}; } else { result[i] = new double[]{p.getX(), p.getY()}; } } return result; } /** * If an CRS (srid) is specified in the json object, it is returned. If no CRS is found in the current * parameter-map * null is returned. This is a simplified version from the actual GeoJSON specification, since the GeoJSON * specification * allows for relative links to either URLS or local files in which the crs should be defined. This implementation * only supports named crs's: namely: * <pre> * "crs": { * "type": "name", * "properties": { * "name": "...yourcrsname..." * } * } * </pre> * Besides the fact that only named crs is permitted for deserialization, the given name must either be of the * form: * <pre> * urn:ogc:def:EPSG:x.y:4326 * </pre> * (with x.y the version of the EPSG) or of the form: * <pre> * EPSG:4326 * </pre> * * @param to the transfer object * @return the CrsId of the crs system in the json if it is present. {@link CrsId#UNDEFINED} otherwise. * @throws IllegalArgumentException If a crs object is present, but deserialization is not possible */ protected CrsId getCrsId(GeoJsonTo to) throws IllegalArgumentException { if (to.getCrs() == null) { return CrsId.UNDEFINED; } else { if (to.getCrs().getType() == null || !"name".equals(to.getCrs().getType())) { throw new IllegalArgumentException("If the crs is specified the type must be specified. Currently, only named crses are supported."); } if (to.getCrs().getProperties() == null || to.getCrs().getProperties().getName() == null) { throw new IllegalArgumentException("A crs specification requires a properties value containing a name value."); } String sridString = to.getCrs().getProperties().getName(); if (sridString.startsWith("EPSG:")) { Integer srid = parseDefault(sridString.substring(5), null); if (srid == null) { throw new IllegalArgumentException("Unable to derive SRID from crs name"); } else { return new CrsId("EPSG", srid); } } else if (sridString.startsWith("urn:ogc:def:crs:EPSG:")) { String[] splits = sridString.split(":"); if (splits.length != 7) { throw new IllegalArgumentException("Unable to derive SRID from crs name"); } else { Integer srid = parseDefault(splits[6], null); if (srid == null) { throw new IllegalArgumentException("Unable to derive SRID from crs name"); } return new CrsId("EPSG", srid); } } else { throw new IllegalArgumentException("Unable to derive SRID from crs name"); } } } private CrsId getCrsId(GeoJsonTo input, CrsId defaultCrsId) { defaultCrsId = defaultCrsId == null ? getCrsId(input) : defaultCrsId; if (defaultCrsId == null) { throw new IllegalArgumentException("Input has not CRS and no default CRS provided."); } return defaultCrsId; } /** * Convenience method. Parses a string into a double. If it can not be converted to a double, the * defaultvalue is returned. Depending on your choice, you can allow null as output or assign it some value * and have very convenient syntax such as: double d = parseDefault(myString, 0.0); which is a lot shorter * than dealing with all kinds of numberformatexceptions. * * @param input The inputstring * @param defaultValue The value to assign in case of error * @return A double corresponding with the input, or defaultValue if no double can be extracted */ protected Integer parseDefault(String input, Integer defaultValue) { if (input == null) { return defaultValue; } Integer answer = defaultValue; try { answer = Integer.parseInt(input); } catch (NumberFormatException ignored) { } return answer; } /** * Checks whether the given to is a valid TO and throws an exception if not. * * @param to The TO to check * @throws IllegalArgumentException When the to is invalid. */ protected static void isValid(GeoJsonTo to) throws IllegalArgumentException { if (!to.isValid()) { throw new IllegalArgumentException("TO is not valid."); } } }