package org.geotoolkit.data.geojson.utils; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonLocation; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.storage.DataStoreException; import org.apache.sis.io.wkt.Convention; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.util.ArgumentChecks; import org.geotoolkit.data.geojson.binding.GeoJSONObject; import org.geotoolkit.io.wkt.WKTFormat; import org.geotoolkit.lang.Static; import org.geotoolkit.nio.IOUtilities; import org.geotoolkit.referencing.IdentifiedObjects; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.FactoryException; import java.io.*; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.text.ParseException; import java.util.Collection; import java.util.logging.Level; import org.apache.sis.util.Utilities; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; import static org.geotoolkit.data.geojson.utils.GeoJSONMembres.*; import static org.geotoolkit.data.geojson.utils.GeoJSONTypes.*; /** * @author Quentin Boileau (Geomatys) */ public final class GeoJSONUtils extends Static { /** * Fallback CRS */ private static final CoordinateReferenceSystem DEFAULT_CRS = CommonCRS.WGS84.normalizedGeographic(); /** * Parse LinkedCRS (href + type). * @param href * @param crsType * @return CoordinateReferenceSystem or null. */ public static CoordinateReferenceSystem parseCRS(String href, String crsType) { String wkt = null; try(InputStream stream = new URL(href).openStream()) { wkt = IOUtilities.toString(stream); } catch (IOException e) { GeoJSONParser.LOGGER.log(Level.WARNING, "Can't access to linked CRS "+href, e); } if (wkt != null) { WKTFormat format = new WKTFormat(); if (crsType.equals(CRS_TYPE_OGCWKT)) { format.setConvention(Convention.WKT1); } else if (crsType.equals(CRS_TYPE_ESRIWKT)) { format.setConvention(Convention.WKT1_COMMON_UNITS); } try { Object obj = format.parseObject(wkt); if (obj instanceof CoordinateReferenceSystem) { return (CoordinateReferenceSystem) obj; } else { GeoJSONParser.LOGGER.log(Level.WARNING, "Parsed WKT is not a CRS "+wkt); } } catch (ParseException e) { GeoJSONParser.LOGGER.log(Level.WARNING, "Can't parse CRS WKT " + crsType+ " : "+wkt, e); } } return null; } /** * Convert a CoordinateReferenceSystem to a identifier string like * urn:ogc:def:crs:EPSG::4326 * @param crs * @return */ public static String toURN(CoordinateReferenceSystem crs) { ArgumentChecks.ensureNonNull("crs", crs); try { if (Utilities.equalsIgnoreMetadata(crs, CommonCRS.WGS84.normalizedGeographic())) { return "urn:ogc:def:crs:OGC:1.3:CRS84"; } int code = IdentifiedObjects.lookupEpsgCode(crs, true); return "urn:ogc:def:crs:EPSG::"+code; } catch (FactoryException e) { GeoJSONParser.LOGGER.log(Level.WARNING, "Unable to extract epsg code from given CRS "+crs, e); } return null; } /** * Try to extract/parse the CoordinateReferenceSystem from a GeoJSONObject. * Use WGS_84 as fallback CRS. * @param obj GeoJSONObject * @return GeoJSONObject CoordinateReferenceSystem or fallback CRS (WGS84). * @throws MalformedURLException * @throws DataStoreException */ public static CoordinateReferenceSystem getCRS(GeoJSONObject obj) throws MalformedURLException, DataStoreException { CoordinateReferenceSystem crs = null; try { if (obj.getCrs() != null) { crs = obj.getCrs().getCRS(); } } catch (FactoryException e) { throw new DataStoreException(e.getMessage(), e); } if (crs == null) { crs = DEFAULT_CRS; } return crs; } /** * Utility method Create geotk Envelope if bbox array is filled. * @return Envelope or null. */ public static Envelope getEnvelope(GeoJSONObject obj, CoordinateReferenceSystem crs) { double[] bbox = obj.getBbox(); if (bbox != null) { GeneralEnvelope env = new GeneralEnvelope(crs); int dim = bbox.length/2; if (dim == 2) { env.setRange(0, bbox[0], bbox[2]); env.setRange(1, bbox[1], bbox[3]); } else if (dim == 3) { env.setRange(0, bbox[0], bbox[3]); env.setRange(1, bbox[1], bbox[4]); } return env; } return null; } /** * Return file name without extension * @param file candidate * @return String */ public static String getNameWithoutExt(Path file) { return IOUtilities.filenameWithoutExtension(file); } /** * Returns the filename extension from a {@link String}, {@link File}, {@link URL} or * {@link URI}. If no extension is found, returns an empty string. * * @param path The path as a {@link String}, {@link File}, {@link URL} or {@link URI}. * @return The filename extension in the given path, or an empty string if none. */ public static String extension(final Object path) { return IOUtilities.extension(path); } /** * Write an empty FeatureCollection in a file * @param f output file * @throws IOException */ @Deprecated public static void writeEmptyFeatureCollection(File f) throws IOException { writeEmptyFeatureCollection(f.toPath()); } /** * Write an empty FeatureCollection in a file * @param f output file * @throws IOException */ public static void writeEmptyFeatureCollection(Path f) throws IOException { try (OutputStream outStream = Files.newOutputStream(f, CREATE, WRITE, TRUNCATE_EXISTING); JsonGenerator writer = GeoJSONParser.FACTORY.createGenerator(outStream, JsonEncoding.UTF8)) { //start write feature collection. writer.writeStartObject(); writer.writeStringField(TYPE, FEATURE_COLLECTION); writer.writeArrayFieldStart(FEATURES); writer.writeEndArray(); writer.writeEndObject(); writer.flush(); } } /** * Useful method to help write an object into a JsonGenerator. * This method can handle : * <ul> * <li>Arrays</li> * <li>Collection</li> * <li>Numbers (Double, Float, Short, BigInteger, BigDecimal, integer, Long, Byte)</li> * <li>Boolean</li> * <li>String</li> * </ul> * @param value * @param writer * @throws IOException * @throws IllegalArgumentException */ public static void writeValue(Object value, JsonGenerator writer) throws IOException, IllegalArgumentException { if (value == null) { writer.writeNull(); return; } Class binding = value.getClass(); if (binding.isArray()) { if (byte.class.isAssignableFrom(binding.getComponentType())) { writer.writeBinary((byte[])value); } else { writer.writeStartArray(); final int size = Array.getLength(value); for (int i = 0; i < size; i++) { writeValue(Array.get(value, i), writer); } writer.writeEndArray(); } } else if (Collection.class.isAssignableFrom(binding)) { writer.writeStartArray(); Collection coll = (Collection) value; for (Object obj : coll) { writeValue(obj, writer); } writer.writeEndArray(); } else if (Double.class.isAssignableFrom(binding)) { writer.writeNumber((Double) value); } else if (Float.class.isAssignableFrom(binding)) { writer.writeNumber((Float) value); } else if (Short.class.isAssignableFrom(binding)) { writer.writeNumber((Short) value); } else if (Byte.class.isAssignableFrom(binding)) { writer.writeNumber((Byte) value); } else if (BigInteger.class.isAssignableFrom(binding)) { writer.writeNumber((BigInteger) value); } else if (BigDecimal.class.isAssignableFrom(binding)) { writer.writeNumber((BigDecimal) value); } else if (Integer.class.isAssignableFrom(binding)) { writer.writeNumber((Integer) value); } else if (Long.class.isAssignableFrom(binding)) { writer.writeNumber((Long) value); } else if (Boolean.class.isAssignableFrom(binding)) { writer.writeBoolean((Boolean) value); } else if (String.class.isAssignableFrom(binding)) { writer.writeString(String.valueOf(value)); } else { //fallback writer.writeString(String.valueOf(value)); } } /** * Compare {@link JsonLocation} equality without sourceRef test. * @param loc1 * @param loc2 * @return */ public static boolean equals(JsonLocation loc1, JsonLocation loc2) { if (loc1 == null) { return (loc2 == null); } return loc2 != null && (loc1.getLineNr() == loc2.getLineNr() && loc1.getColumnNr() == loc2.getColumnNr() && loc1.getByteOffset() == loc2.getByteOffset() && loc1.getCharOffset() == loc2.getCharOffset()); } }