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

com.facebook.presto.geospatial.GeometryUtils Maven / Gradle / Ivy

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.facebook.presto.geospatial;

import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.GeometryCursor;
import com.esri.core.geometry.GeometryEngine;
import com.esri.core.geometry.MultiVertexGeometry;
import com.esri.core.geometry.Operator;
import com.esri.core.geometry.Point;
import com.esri.core.geometry.Polygon;
import com.esri.core.geometry.ogc.OGCConcreteGeometryCollection;
import com.esri.core.geometry.ogc.OGCGeometry;
import com.esri.core.geometry.ogc.OGCGeometryCollection;
import com.esri.core.geometry.ogc.OGCPoint;
import com.esri.core.geometry.ogc.OGCPolygon;
import com.facebook.presto.spi.PrestoException;
import com.google.common.collect.ImmutableList;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateSequenceFactory;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.locationtech.jts.io.geojson.GeoJsonReader;
import org.locationtech.jts.io.geojson.GeoJsonWriter;
import org.locationtech.jts.operation.IsSimpleOp;
import org.locationtech.jts.operation.valid.IsValidOp;
import org.locationtech.jts.operation.valid.TopologyValidationError;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;

import static com.facebook.presto.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;

public final class GeometryUtils
{
    private static final CoordinateSequenceFactory COORDINATE_SEQUENCE_FACTORY = new PackedCoordinateSequenceFactory();
    private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(COORDINATE_SEQUENCE_FACTORY);
    private static final Set ATOMIC_GEOMETRY_TYPES = new HashSet<>();
    static {
        ATOMIC_GEOMETRY_TYPES.add("LineString");
        ATOMIC_GEOMETRY_TYPES.add("Polygon");
        ATOMIC_GEOMETRY_TYPES.add("Point");
    }

    private GeometryUtils() {}

    /**
     * Copy of com.esri.core.geometry.Interop.translateFromAVNaN
     * 

* deserializeEnvelope needs to recognize custom NAN values generated by * ESRI's serialization of empty geometries. */ private static double translateFromAVNaN(double n) { return n < -1.0E38D ? (0.0D / 0.0) : n; } public static boolean isEsriNaN(double d) { return Double.isNaN(d) || Double.isNaN(translateFromAVNaN(d)); } public static int getPointCount(OGCGeometry ogcGeometry) { GeometryCursor cursor = ogcGeometry.getEsriGeometryCursor(); int points = 0; while (true) { com.esri.core.geometry.Geometry geometry = cursor.next(); if (geometry == null) { return points; } if (geometry.isEmpty()) { continue; } if (geometry instanceof Point) { points++; } else { points += ((MultiVertexGeometry) geometry).getPointCount(); } } } public static Envelope getEnvelope(OGCGeometry ogcGeometry) { GeometryCursor cursor = ogcGeometry.getEsriGeometryCursor(); Envelope overallEnvelope = new Envelope(); while (true) { Geometry geometry = cursor.next(); if (geometry == null) { return overallEnvelope; } Envelope envelope = new Envelope(); geometry.queryEnvelope(envelope); overallEnvelope.merge(envelope); } } /** * Get the bounding box for an OGCGeometry. *

* If the geometry is empty, return a Rectangle with NaN coordinates. * * @param ogcGeometry * @return Rectangle bounding box */ public static Rectangle getExtent(OGCGeometry ogcGeometry) { return getExtent(ogcGeometry, 0.0); } /** * Get the bounding box for an OGCGeometry, inflated by radius. *

* If the geometry is empty, return a Rectangle with NaN coordinates. * * @param ogcGeometry * @return Rectangle bounding box */ public static Rectangle getExtent(OGCGeometry ogcGeometry, double radius) { com.esri.core.geometry.Envelope envelope = getEnvelope(ogcGeometry); return new Rectangle( envelope.getXMin() - radius, envelope.getYMin() - radius, envelope.getXMax() + radius, envelope.getYMax() + radius); } public static org.locationtech.jts.geom.Envelope getJtsEnvelope(OGCGeometry ogcGeometry, double radius) { Envelope esriEnvelope = getEnvelope(ogcGeometry); if (esriEnvelope.isEmpty()) { return new org.locationtech.jts.geom.Envelope(); } return new org.locationtech.jts.geom.Envelope( esriEnvelope.getXMin() - radius, esriEnvelope.getXMax() + radius, esriEnvelope.getYMin() - radius, esriEnvelope.getYMax() + radius); } public static org.locationtech.jts.geom.Envelope getJtsEnvelope(OGCGeometry ogcGeometry) { return getJtsEnvelope(ogcGeometry, 0.0); } public static boolean disjoint(Envelope envelope, OGCGeometry ogcGeometry) { GeometryCursor cursor = ogcGeometry.getEsriGeometryCursor(); while (true) { Geometry geometry = cursor.next(); if (geometry == null) { return true; } if (!GeometryEngine.disjoint(geometry, envelope, null)) { return false; } } } public static boolean contains(OGCGeometry ogcGeometry, Envelope envelope) { GeometryCursor cursor = ogcGeometry.getEsriGeometryCursor(); while (true) { Geometry geometry = cursor.next(); if (geometry == null) { return false; } if (GeometryEngine.contains(geometry, envelope, null)) { return true; } } } public static boolean isPointOrRectangle(OGCGeometry ogcGeometry, Envelope envelope) { if (ogcGeometry instanceof OGCPoint) { return true; } if (!(ogcGeometry instanceof OGCPolygon)) { return false; } Polygon polygon = (Polygon) ogcGeometry.getEsriGeometry(); if (polygon.getPathCount() > 1) { return false; } if (polygon.getPointCount() != 4) { return false; } Set corners = new HashSet<>(); corners.add(new Point(envelope.getXMin(), envelope.getYMin())); corners.add(new Point(envelope.getXMin(), envelope.getYMax())); corners.add(new Point(envelope.getXMax(), envelope.getYMin())); corners.add(new Point(envelope.getXMax(), envelope.getYMax())); for (int i = 0; i < 4; i++) { Point point = polygon.getPoint(i); if (!corners.contains(point)) { return false; } } return true; } public static org.locationtech.jts.geom.Geometry jtsGeometryFromJson(String json) { try { return new GeoJsonReader().read(json); } catch (ParseException | IllegalArgumentException e) { throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Invalid GeoJSON: " + e.getMessage(), e); } } public static Optional jsonFromJtsGeometry(org.locationtech.jts.geom.Geometry geometry) { if (ATOMIC_GEOMETRY_TYPES.contains(geometry.getGeometryType()) && geometry.isEmpty()) { return Optional.empty(); } else { return Optional.of(new GeoJsonWriter().write(geometry)); } } public static org.locationtech.jts.geom.Geometry jtsGeometryFromWkt(String wkt) { try { return new WKTReader(GEOMETRY_FACTORY).read(wkt); } catch (ParseException | IllegalArgumentException e) { throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Invalid WKT: " + e.getMessage(), e); } } public static String wktFromJtsGeometry(org.locationtech.jts.geom.Geometry geometry) { return new WKTWriter().write(geometry); } public static org.locationtech.jts.geom.Point createJtsEmptyPoint() { return GEOMETRY_FACTORY.createPoint(); } public static org.locationtech.jts.geom.Point createJtsPoint(Coordinate coordinate) { return GEOMETRY_FACTORY.createPoint(coordinate); } public static org.locationtech.jts.geom.Point createJtsPoint(double x, double y) { return createJtsPoint(new Coordinate(x, y)); } public static org.locationtech.jts.geom.MultiPoint createJtsMultiPoint(CoordinateSequence coordinates) { return GEOMETRY_FACTORY.createMultiPoint(coordinates); } public static org.locationtech.jts.geom.Geometry createJtsEmptyLineString() { return GEOMETRY_FACTORY.createLineString(); } public static org.locationtech.jts.geom.Geometry createJtsLineString(CoordinateSequence coordinates) { return GEOMETRY_FACTORY.createLineString(coordinates); } public static org.locationtech.jts.geom.Geometry createJtsEmptyPolygon() { return GEOMETRY_FACTORY.createPolygon(); } public static Optional getGeometryInvalidReason(org.locationtech.jts.geom.Geometry geometry) { IsValidOp validOp = new IsValidOp(geometry); IsSimpleOp simpleOp = new IsSimpleOp(geometry); try { TopologyValidationError err = validOp.getValidationError(); if (err != null) { return Optional.of(err.getMessage()); } } catch (UnsupportedOperationException e) { // This is thrown if the type of geometry is unsupported by JTS. // It should not happen in practice. throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Geometry type not valid", e); } if (!simpleOp.isSimple()) { String errorDescription; String geometryType = geometry.getGeometryType(); switch (GeometryType.getForJtsGeometryType(geometryType)) { case POINT: errorDescription = "Invalid point"; break; case MULTI_POINT: errorDescription = "Repeated point"; break; case LINE_STRING: case MULTI_LINE_STRING: errorDescription = "Self-intersection at or near"; break; case POLYGON: case MULTI_POLYGON: case GEOMETRY_COLLECTION: // In OGC (which JTS follows): Polygons, MultiPolygons, Geometry Collections are simple. // This shouldn't happen, but in case it does, return a reasonable generic message. errorDescription = "Topology exception at or near"; break; default: throw new PrestoException(INVALID_FUNCTION_ARGUMENT, format("Unknown geometry type: %s", geometryType)); } org.locationtech.jts.geom.Coordinate nonSimpleLocation = simpleOp.getNonSimpleLocation(); return Optional.of(format("[%s] %s: (%s %s)", geometryType, errorDescription, nonSimpleLocation.getX(), nonSimpleLocation.getY())); } return Optional.empty(); } /** * Recursively flatten GeometryCollection in geometry. * * If `geometry` is null, return an empty iterable. * If `geometry` is not a GeometryCollection, yield a single geometry. * (For this, MultiX is not considered a GeometryCollection.) * Otherwise, iterate through all children of the GeometryCollection, * recursively flattening contained GeometryCollections. */ public static Iterable flattenCollection(OGCGeometry geometry) { if (geometry == null) { return ImmutableList.of(); } if (!(geometry instanceof OGCConcreteGeometryCollection)) { return ImmutableList.of(geometry); } if (((OGCConcreteGeometryCollection) geometry).numGeometries() == 0) { return ImmutableList.of(); } return () -> new GeometryCollectionIterator(geometry); } public static void accelerateGeometry(OGCGeometry ogcGeometry, Operator relateOperator) { accelerateGeometry(ogcGeometry, relateOperator, Geometry.GeometryAccelerationDegree.enumMild); } public static void accelerateGeometry( OGCGeometry ogcGeometry, Operator relateOperator, Geometry.GeometryAccelerationDegree accelerationDegree) { // Recurse into GeometryCollections GeometryCursor cursor = ogcGeometry.getEsriGeometryCursor(); while (true) { Geometry esriGeometry = cursor.next(); if (esriGeometry == null) { break; } relateOperator.accelerateGeometry(esriGeometry, null, accelerationDegree); } } private static class GeometryCollectionIterator implements Iterator { private final Deque geometriesDeque = new ArrayDeque<>(); GeometryCollectionIterator(OGCGeometry geometries) { geometriesDeque.push(requireNonNull(geometries, "geometries is null")); } @Override public boolean hasNext() { if (geometriesDeque.isEmpty()) { return false; } while (geometriesDeque.peek() instanceof OGCConcreteGeometryCollection) { OGCGeometryCollection collection = (OGCGeometryCollection) geometriesDeque.pop(); for (int i = 0; i < collection.numGeometries(); i++) { geometriesDeque.push(collection.geometryN(i)); } } return !geometriesDeque.isEmpty(); } @Override public OGCGeometry next() { if (!hasNext()) { throw new NoSuchElementException("Geometries have been consumed"); } return geometriesDeque.pop(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy