org.locationtech.spatial4j.exception.InvalidShapeException Java Examples

The following examples show how to use org.locationtech.spatial4j.exception.InvalidShapeException. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: GeoJSONUtils.java    From crate with Apache License 2.0 6 votes vote down vote up
public static Map<String, Object> shape2Map(Shape shape) {
    if (shape instanceof ShapeCollection) {
        ShapeCollection<?> shapeCollection = (ShapeCollection<?>) shape;
        List<Map<String, Object>> geometries = new ArrayList<>(shapeCollection.size());
        for (Shape collShape : shapeCollection) {
            geometries.add(shape2Map(collShape));
        }
        return Map.of(
            TYPE_FIELD, GEOMETRY_COLLECTION,
            GEOMETRIES_FIELD, geometries
        );
    } else {
        try {
            return GEOJSON_CONVERTER.convert(JtsSpatialContext.GEO.getShapeFactory().getGeometryFrom(shape));
        } catch (InvalidShapeException e) {
            throw new IllegalArgumentException(
                String.format(Locale.ENGLISH, "Cannot convert shape %s to Map", shape), e);
        }
    }
}
 
Example #2
Source File: PolygonBuilder.java    From crate with Apache License 2.0 6 votes vote down vote up
/**
 * @return whether the points are clockwise (true) or anticlockwise (false)
 */
private static boolean getOrientation(Coordinate[] points, int offset, int length) {
    // calculate the direction of the points: find the southernmost point
    // and check its neighbors orientation.

    final int top = top(points, offset, length);
    final int prev = (top + length - 1) % length;
    final int next = (top + 1) % length;

    final int determinantSign = orient(
        points[offset + prev].x, points[offset + prev].y,
        points[offset + top].x, points[offset + top].y,
        points[offset + next].x, points[offset + next].y);

    if (determinantSign == 0) {
        // Points are collinear, but `top` is not in the middle if so, so the edges either side of `top` are intersecting.
        throw new InvalidShapeException("Cannot determine orientation: edges adjacent to ("
                + points[offset + top].x + "," + points[offset + top].y + ") coincide");
    }

    return determinantSign < 0;
}
 
Example #3
Source File: Geo3dTest.java    From lucene-solr with Apache License 2.0 6 votes vote down vote up
@Test
public void testPolygonWithCoplanarPoints() {
  Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
  SpatialContext ctx = factory.newSpatialContext();

  final String polygon = "POLYGON ((-180 90, -180 -90, 180 -90, 180 90,-180 -90))";
  expectThrows(InvalidShapeException.class, () -> ctx.getFormats().getWktReader().read(polygon));

  final String polygonWithHole = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 20 30, 20 30))";
  expectThrows(InvalidShapeException.class, () -> ctx.getFormats().getWktReader().read(polygonWithHole));

  final String geometryCollection = "GEOMETRYCOLLECTION(POINT(4 6), LINESTRING(4 6,7 10), POLYGON ((-180 90, -180 -90, 180 -90, 180 90,-180 -90)))";
  expectThrows(InvalidShapeException.class, () -> ctx.getFormats().getWktReader().read(geometryCollection));

  final String multiPolygon = "MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)), ((180 90, 90 90, 180 90)))";
  expectThrows(InvalidShapeException.class, () -> ctx.getFormats().getWktReader().read(multiPolygon));

}
 
Example #4
Source File: SpatialArgsParser.java    From lucene-solr with Apache License 2.0 5 votes vote down vote up
/**
 * Parses a string such as "Intersects(ENVELOPE(-10,-8,22,20)) distErrPct=0.025".
 *
 * @param v   The string to parse. Mandatory.
 * @param ctx The spatial context. Mandatory.
 * @return Not null.
 * @throws IllegalArgumentException if the parameters don't make sense or an add-on parameter is unknown
 * @throws ParseException If there is a problem parsing the string
 * @throws InvalidShapeException When the coordinates are invalid for the shape
 */
public SpatialArgs parse(String v, SpatialContext ctx) throws ParseException, InvalidShapeException {
  int idx = v.indexOf('(');
  int edx = v.lastIndexOf(')');

  if (idx < 0 || idx > edx) {
    throw new ParseException("missing parens: " + v, -1);
  }

  SpatialOperation op = SpatialOperation.get(v.substring(0, idx).trim());

  String body = v.substring(idx + 1, edx).trim();
  if (body.length() < 1) {
    throw new ParseException("missing body : " + v, idx + 1);
  }

  Shape shape = parseShape(body, ctx);
  SpatialArgs args = newSpatialArgs(op, shape);

  if (v.length() > (edx + 1)) {
    body = v.substring(edx + 1).trim();
    if (body.length() > 0) {
      Map<String, String> aa = parseMap(body);
      readNameValuePairs(args, aa);
      if (!aa.isEmpty()) {
        throw new IllegalArgumentException("unused parameters: " + aa);
      }
    }
  }
  args.validate();
  return args;
}
 
Example #5
Source File: WithinQuery.java    From crate with Apache License 2.0 5 votes vote down vote up
private Query getQuery(Function inner, LuceneQueryBuilder.Context context) {
    RefAndLiteral innerPair = RefAndLiteral.of(inner);
    if (innerPair == null) {
        return null;
    }
    if (innerPair.reference().valueType().equals(DataTypes.GEO_SHAPE)) {
        // we have within('POINT(0 0)', shape_column)
        return LuceneQueryBuilder.genericFunctionFilter(inner, context);
    }
    GeoPointFieldMapper.GeoPointFieldType geoPointFieldType = getGeoPointFieldType(
        innerPair.reference().column().fqn(),
        context.mapperService);

    Map<String, Object> geoJSON = DataTypes.GEO_SHAPE.value(innerPair.literal().value());
    Geometry geometry;
    Shape shape = GeoJSONUtils.map2Shape(geoJSON);
    if (shape instanceof ShapeCollection) {
        int i = 0;
        ShapeCollection<Shape> collection = (ShapeCollection) shape;
        org.locationtech.jts.geom.Polygon[] polygons = new org.locationtech.jts.geom.Polygon[collection.size()];
        for (Shape s : collection.getShapes()) {
            Geometry subGeometry = JtsSpatialContext.GEO.getShapeFactory().getGeometryFrom(s);
            if (subGeometry instanceof org.locationtech.jts.geom.Polygon) {
                polygons[i++] = (org.locationtech.jts.geom.Polygon) subGeometry;
            } else {
                throw new InvalidShapeException("Shape collection must contain only Polygon shapes.");
            }
        }
        GeometryFactory geometryFactory = JtsSpatialContext.GEO.getShapeFactory().getGeometryFactory();
        geometry = geometryFactory.createMultiPolygon(polygons);
    } else {
        geometry = JtsSpatialContext.GEO.getShapeFactory().getGeometryFrom(shape);
    }

    return getPolygonQuery(geometry, geoPointFieldType);
}
 
Example #6
Source File: GeoPointType.java    From crate with Apache License 2.0 5 votes vote down vote up
private static Point pointFromString(String value) {
    try {
        return (Point) WKT_READER.parse(value);
    } catch (ParseException | InvalidShapeException e) {
        throw new IllegalArgumentException(String.format(Locale.ENGLISH,
            "Cannot convert \"%s\" to geo_point. %s", value, e.getLocalizedMessage()), e);
    }
}
 
Example #7
Source File: PolygonBuilder.java    From crate with Apache License 2.0 5 votes vote down vote up
/**
 * Validates only 1 vertex is tangential (shared) between the interior and exterior of a polygon
 */
protected void validateHole(LineStringBuilder shell, LineStringBuilder hole) {
    HashSet<Coordinate> exterior = Sets.newHashSet(shell.coordinates);
    HashSet<Coordinate> interior = Sets.newHashSet(hole.coordinates);
    exterior.retainAll(interior);
    if (exterior.size() >= 2) {
        throw new InvalidShapeException("Invalid polygon, interior cannot share more than one point with the exterior");
    }
}
 
Example #8
Source File: ShapeBuilder.java    From crate with Apache License 2.0 5 votes vote down vote up
protected void setNext(Edge next) {
    // don't bother setting next if its null
    if (next != null) {
        // self-loop throws an invalid shape
        if (this.coordinate.equals(next.coordinate)) {
            throw new InvalidShapeException("Provided shape has duplicate consecutive coordinates at: " + this.coordinate);
        }
        this.next = next;
    }
}
 
Example #9
Source File: SpatialUtils.java    From lucene-solr with Apache License 2.0 5 votes vote down vote up
/**
 * Calls {@link #parseRectangle(String, org.locationtech.spatial4j.context.SpatialContext)} and wraps the exception with
 * {@link org.apache.solr.common.SolrException} with a helpful message.
 */
public static Rectangle parseRectangeSolrException(String externalVal, SpatialContext ctx) throws SolrException {
  try {
    return parseRectangle(externalVal, ctx);
  } catch (InvalidShapeException e) {
    String message = e.getMessage();
    if (!message.contains(externalVal))
      message = "Can't parse rectangle '" + externalVal + "' because: " + message;
    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, message, e);
  }
}
 
Example #10
Source File: SpatialUtils.java    From lucene-solr with Apache License 2.0 5 votes vote down vote up
/** Calls {@link #parsePoint(String, org.locationtech.spatial4j.context.SpatialContext)} and wraps
 * the exception with {@link org.apache.solr.common.SolrException} with a helpful message. */
public static Point parsePointSolrException(String externalVal, SpatialContext ctx) throws SolrException {
  try {
    return parsePoint(externalVal, ctx);
  } catch (InvalidShapeException e) {
    String message = e.getMessage();
    if (!message.contains(externalVal))
      message = "Can't parse point '" + externalVal + "' because: " + message;
    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, message, e);
  }
}
 
Example #11
Source File: Geo3dShapeFactory.java    From lucene-solr with Apache License 2.0 5 votes vote down vote up
@SuppressWarnings("unchecked")
@Override
public Shape build() {
  GeoPolygonFactory.PolygonDescription description = new GeoPolygonFactory.PolygonDescription(points, polyHoles);
  GeoPolygon polygon = GeoPolygonFactory.makeGeoPolygon(planetModel, description);
  if (polygon == null) {
    throw new InvalidShapeException("Invalid polygon, all points are coplanar");
  }
  return new Geo3dShape<>(polygon, context);
}
 
Example #12
Source File: Geo3dShapeFactory.java    From lucene-solr with Apache License 2.0 5 votes vote down vote up
@Override
public void verifyY(double y) {
  Rectangle bounds = this.context.getWorldBounds();
  if (y < bounds.getMinY() || y > bounds.getMaxY()) {
    throw new InvalidShapeException("Bad Y value " + y + " is not in boundary " + bounds);
  }
}
 
Example #13
Source File: Geo3dShapeFactory.java    From lucene-solr with Apache License 2.0 5 votes vote down vote up
@Override
public void verifyX(double x) {
  Rectangle bounds = this.context.getWorldBounds();
  if (x < bounds.getMinX() || x > bounds.getMaxX()) {
    throw new InvalidShapeException("Bad X value " + x + " is not in boundary " + bounds);
  }
}
 
Example #14
Source File: FunctionArguments.java    From rdf4j with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
/**
 * Get the geo shape
 *
 * @param func    function
 * @param v       value
 * @param context
 * @return shape
 * @throws ValueExprEvaluationException
 */
public static Shape getShape(Function func, Value v, SpatialContext context) throws ValueExprEvaluationException {
	Literal wktLiteral = getLiteral(func, v, GEO.WKT_LITERAL);
	try {
		ShapeReader reader = context.getFormats().getWktReader();
		return reader.read(wktLiteral.getLabel());
	} catch (IOException | InvalidShapeException | ParseException e) {
		throw new ValueExprEvaluationException("Invalid argument for " + func.getURI() + ": " + wktLiteral, e);
	}
}
 
Example #15
Source File: GeoExtensionProcessor.java    From elasticsearch-plugin-geoshape with MIT License 4 votes vote down vote up
@SuppressWarnings("unchecked")
@Override
public IngestDocument execute(IngestDocument ingestDocument) throws IOException, ParseException {
    List<String> geo_objects_list = getGeoShapeFieldsFromDoc(ingestDocument);
    for (String geoShapeField : geo_objects_list) {

        Object geoShapeObject = ingestDocument.getFieldValue(geoShapeField, Object.class);

        if (geoShapeObject == null) {
            continue;
        }

        ShapeBuilder<?,?, ?> shapeBuilder = getShapeBuilderFromObject(geoShapeObject);

        Shape shape = null;
        try {
            shape = shapeBuilder.buildS4J();
        }
        catch (InvalidShapeException ignored) {}

        if (shape == null && fixedField == null) {
            throw new IllegalArgumentException("unable to parse shape [" + shapeBuilder.toWKT() + "]");
        }

        Geometry geom = new WKTReader().read(shapeBuilder.toWKT());

        // fix shapes if needed
        if (shape == null && fixedField != null) {
            geom = GeoUtils.removeDuplicateCoordinates(geom);
        }

        ingestDocument.removeField(geoShapeField);

        if (keepShape) {
            ingestDocument.setFieldValue(geoShapeField + "." + shapeField, geoShapeObject);
        }

        if (fixedField != null) {
            ingestDocument.setFieldValue(geoShapeField + "." + fixedField, new WKTWriter().write(geom));
        }

        // compute and add extra geo sub-fields
        byte[] wkb = new WKBWriter().write(geom);  // elastic will auto-encode this as b64

        if (hashField != null) ingestDocument.setFieldValue(
                geoShapeField + ".hash", String.valueOf(GeoUtils.getHashFromWKB(new BytesRef(wkb))));
        if (wkbField != null) ingestDocument.setFieldValue(
                geoShapeField + "." + wkbField, wkb);
        if (typeField != null) ingestDocument.setFieldValue(
                geoShapeField + "." + typeField, geom.getGeometryType());
        if (areaField != null) ingestDocument.setFieldValue(
                geoShapeField + "." + areaField, geom.getArea());
        if (centroidField != null) ingestDocument.setFieldValue(
                geoShapeField + "." + centroidField, GeoUtils.getCentroidFromGeom(geom));
        if (bboxField != null) {
            Coordinate[] coords = geom.getEnvelope().getCoordinates();
            if (coords.length >= 4) ingestDocument.setFieldValue(
                    geoShapeField + "." + bboxField,
                    GeoUtils.getBboxFromCoords(coords));
        }
    }
    return ingestDocument;
}
 
Example #16
Source File: PolygonBuilder.java    From crate with Apache License 2.0 4 votes vote down vote up
/**
 * This method sets the component id of all edges in a ring to a given id and shifts the
 * coordinates of this component according to the dateline
 *
 * @param edge An arbitrary edge of the component
 * @param id id to apply to the component
 * @param edges a list of edges to which all edges of the component will be added (could be <code>null</code>)
 * @return number of edges that belong to this component
 */
private static int component(final Edge edge, final int id, final ArrayList<Edge> edges) {
    // find a coordinate that is not part of the dateline
    Edge any = edge;
    while (any.coordinate.x == +DATELINE || any.coordinate.x == -DATELINE) {
        if ((any = any.next) == edge) {
            break;
        }
    }

    double shiftOffset = any.coordinate.x > DATELINE ? DATELINE : (any.coordinate.x < -DATELINE ? -DATELINE : 0);
    if (debugEnabled()) {
        LOGGER.debug("shift: [{}]", shiftOffset);
    }

    // run along the border of the component, collect the
    // edges, shift them according to the dateline and
    // update the component id
    int length = 0, connectedComponents = 0;
    // if there are two connected components, splitIndex keeps track of where to split the edge array
    // start at 1 since the source coordinate is shared
    int splitIndex = 1;
    Edge current = edge;
    Edge prev = edge;
    // bookkeep the source and sink of each visited coordinate
    HashMap<Coordinate, Tuple<Edge, Edge>> visitedEdge = new HashMap<>();
    do {
        current.coordinate = shift(current.coordinate, shiftOffset);
        current.component = id;

        if (edges != null) {
            // found a closed loop - we have two connected components so we need to slice into two distinct components
            if (visitedEdge.containsKey(current.coordinate)) {
                if (connectedComponents > 0 && current.next != edge) {
                    throw new InvalidShapeException("Shape contains more than one shared point");
                }

                // a negative id flags the edge as visited for the edges(...) method.
                // since we're splitting connected components, we want the edges method to visit
                // the newly separated component
                final int visitID = -id;
                Edge firstAppearance = visitedEdge.get(current.coordinate).v2();
                // correct the graph pointers by correcting the 'next' pointer for both the
                // first appearance and this appearance of the edge
                Edge temp = firstAppearance.next;
                firstAppearance.next = current.next;
                current.next = temp;
                current.component = visitID;
                // backtrack until we get back to this coordinate, setting the visit id to
                // a non-visited value (anything positive)
                do {
                    prev.component = visitID;
                    prev = visitedEdge.get(prev.coordinate).v1();
                    ++splitIndex;
                } while (!current.coordinate.equals(prev.coordinate));
                ++connectedComponents;
            } else {
                visitedEdge.put(current.coordinate, new Tuple<Edge, Edge>(prev, current));
            }
            edges.add(current);
            prev = current;
        }
        length++;
    } while (connectedComponents == 0 && (current = current.next) != edge);

    return (splitIndex != 1) ? length - splitIndex : length;
}
 
Example #17
Source File: PolygonBuilder.java    From crate with Apache License 2.0 4 votes vote down vote up
private static void assign(Edge[] holes, Coordinate[][] points, int numHoles, Edge[] edges, List<List<Coordinate[]>> components) {
    // Assign Hole to related components
    // To find the new component the hole belongs to all intersections of the
    // polygon edges with a vertical line are calculated. This vertical line
    // is an arbitrary point of the hole. The polygon edge next to this point
    // is part of the polygon the hole belongs to.
    if (debugEnabled()) {
        LOGGER.debug("Holes: {}", Arrays.toString(holes));
    }
    for (int i = 0; i < numHoles; i++) {
        // To do the assignment we assume (and later, elsewhere, check) that each hole is within
        // a single component, and the components do not overlap. Based on this assumption, it's
        // enough to find a component that contains some vertex of the hole, and
        // holes[i].coordinate is such a vertex, so we use that one.

        // First, we sort all the edges according to their order of intersection with the line
        // of longitude through holes[i].coordinate, in order from south to north. Edges that do
        // not intersect this line are sorted to the end of the array and of no further interest
        // here.
        final Edge current = new Edge(holes[i].coordinate, holes[i].next);
        current.intersect = current.coordinate;
        final int intersections = intersections(current.coordinate.x, edges);

        if (intersections == 0) {
            // There were no edges that intersect the line of longitude through
            // holes[i].coordinate, so there's no way this hole is within the polygon.
            throw new InvalidShapeException("Invalid shape: Hole is not within polygon");
        }

        // Next we do a binary search to find the position of holes[i].coordinate in the array.
        // The binary search returns the index of an exact match, or (-insertionPoint - 1) if
        // the vertex lies between the intersections of edges[insertionPoint] and
        // edges[insertionPoint+1]. The latter case is vastly more common.

        final int pos;
        boolean sharedVertex = false;
        if (((pos = Arrays.binarySearch(edges, 0, intersections, current, INTERSECTION_ORDER)) >= 0)
            && !(sharedVertex = (edges[pos].intersect.compareTo(current.coordinate) == 0))) {
            // The binary search returned an exact match, but we checked again using compareTo()
            // and it didn't match after all.

            // TODO Can this actually happen? Needs a test to exercise it, or else needs to be removed.
            throw new InvalidShapeException("Invalid shape: Hole is not within polygon");
        }

        final int index;
        if (sharedVertex) {
            // holes[i].coordinate lies exactly on an edge.
            index = 0; // TODO Should this be pos instead of 0? This assigns exact matches to the southernmost component.
        } else if (pos == -1) {
            // holes[i].coordinate is strictly south of all intersections. Assign it to the
            // southernmost component, and allow later validation to spot that it is not
            // entirely within the chosen component.
            index = 0;
        } else {
            // holes[i].coordinate is strictly north of at least one intersection. Assign it to
            // the component immediately to its south.
            index = -(pos + 2);
        }

        final int component = -edges[index].component - numHoles - 1;

        if (debugEnabled()) {
            LOGGER.debug("\tposition ({}) of edge {}: {}", index, current, edges[index]);
            LOGGER.debug("\tComponent: {}", component);
            LOGGER.debug("\tHole intersections ({}): {}", current.coordinate.x, Arrays.toString(edges));
        }

        components.get(component).add(points[i]);
    }
}