Geo: deprecate ShapeBuilder in QueryBuilders (#44715)

Removes unnecessary now timeline decompositions from shape builders
and deprecates ShapeBuilders in QueryBuilder in favor of libs/geo
shapes.

Relates to #40908
This commit is contained in:
Igor Motov 2019-07-24 14:07:10 -04:00
parent b3d8b39f29
commit f9943a3e53
13 changed files with 655 additions and 184 deletions

View File

@ -382,17 +382,17 @@ public final class GeoJson {
return geometry.visit(new GeometryVisitor<String, RuntimeException>() { return geometry.visit(new GeometryVisitor<String, RuntimeException>() {
@Override @Override
public String visit(Circle circle) { public String visit(Circle circle) {
return "Circle"; return "circle";
} }
@Override @Override
public String visit(GeometryCollection<?> collection) { public String visit(GeometryCollection<?> collection) {
return "GeometryCollection"; return "geometrycollection";
} }
@Override @Override
public String visit(Line line) { public String visit(Line line) {
return "LineString"; return "linestring";
} }
@Override @Override
@ -402,32 +402,32 @@ public final class GeoJson {
@Override @Override
public String visit(MultiLine multiLine) { public String visit(MultiLine multiLine) {
return "MultiLineString"; return "multilinestring";
} }
@Override @Override
public String visit(MultiPoint multiPoint) { public String visit(MultiPoint multiPoint) {
return "MultiPoint"; return "multipoint";
} }
@Override @Override
public String visit(MultiPolygon multiPolygon) { public String visit(MultiPolygon multiPolygon) {
return "MultiPolygon"; return "multipolygon";
} }
@Override @Override
public String visit(Point point) { public String visit(Point point) {
return "Point"; return "point";
} }
@Override @Override
public String visit(Polygon polygon) { public String visit(Polygon polygon) {
return "Polygon"; return "polygon";
} }
@Override @Override
public String visit(Rectangle rectangle) { public String visit(Rectangle rectangle) {
return "Envelope"; return "envelope";
} }
}); });
} }

View File

@ -0,0 +1,307 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.common.geo;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.geo.geometry.Circle;
import org.elasticsearch.geo.geometry.Geometry;
import org.elasticsearch.geo.geometry.GeometryCollection;
import org.elasticsearch.geo.geometry.GeometryVisitor;
import org.elasticsearch.geo.geometry.Line;
import org.elasticsearch.geo.geometry.LinearRing;
import org.elasticsearch.geo.geometry.MultiLine;
import org.elasticsearch.geo.geometry.MultiPoint;
import org.elasticsearch.geo.geometry.MultiPolygon;
import org.elasticsearch.geo.geometry.Point;
import org.elasticsearch.geo.geometry.Polygon;
import org.elasticsearch.geo.geometry.Rectangle;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Utility class for binary serializtion/deserialization of libs/geo classes
*/
public final class GeometryIO {
public static void writeGeometry(StreamOutput out, Geometry geometry) throws IOException {
out.writeString(GeoJson.getGeoJsonName(geometry).toLowerCase(Locale.ROOT));
geometry.visit(new GeometryVisitor<Void, IOException>() {
@Override
public Void visit(Circle circle) throws IOException {
throw new UnsupportedOperationException("circle is not supported");
}
@Override
public Void visit(GeometryCollection<?> collection) throws IOException {
out.writeVInt(collection.size());
for (Geometry shape : collection) {
writeGeometry(out, shape);
}
return null;
}
@Override
public Void visit(Line line) throws IOException {
writeCoordinates(line);
return null;
}
@Override
public Void visit(LinearRing ring) {
throw new UnsupportedOperationException("linear ring is not supported");
}
@Override
public Void visit(MultiLine multiLine) throws IOException {
out.writeVInt(multiLine.size());
for (Line line : multiLine) {
visit(line);
}
return null;
}
@Override
public Void visit(MultiPoint multiPoint) throws IOException {
out.writeVInt(multiPoint.size());
for (int i = 0; i < multiPoint.size(); i++) {
Point point = multiPoint.get(i);
writeCoordinate(point.getLat(), point.getLon(), point.getAlt());
}
return null;
}
@Override
public Void visit(MultiPolygon multiPolygon) throws IOException {
out.writeBoolean(true); // Orientation for BWC with ShapeBuilder
out.writeVInt(multiPolygon.size());
for (int i = 0; i < multiPolygon.size(); i++) {
visit(multiPolygon.get(i));
}
return null;
}
@Override
public Void visit(Point point) throws IOException {
out.writeVInt(1); // Number of points For BWC with Shape Builder
writeCoordinate(point.getLat(), point.getLon(), point.getAlt());
return null;
}
@Override
public Void visit(Polygon polygon) throws IOException {
writeCoordinates(polygon.getPolygon());
out.writeBoolean(true); // Orientation for BWC with ShapeBuilder
out.writeVInt(polygon.getNumberOfHoles());
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
writeCoordinates(polygon.getHole(i));
}
return null;
}
@Override
public Void visit(Rectangle rectangle) throws IOException {
writeCoordinate(rectangle.getMaxLat(), rectangle.getMinLon(), rectangle.getMinAlt()); // top left
writeCoordinate(rectangle.getMinLat(), rectangle.getMaxLon(), rectangle.getMaxAlt()); // bottom right
return null;
}
private void writeCoordinate(double lat, double lon, double alt) throws IOException {
out.writeDouble(lon);
out.writeDouble(lat);
out.writeOptionalDouble(Double.isNaN(alt) ? null : alt);
}
private void writeCoordinates(Line line) throws IOException {
out.writeVInt(line.length());
for (int i = 0; i < line.length(); i++) {
writeCoordinate(line.getLat(i), line.getLon(i), line.getAlt(i));
}
}
});
}
public static Geometry readGeometry(StreamInput in) throws IOException {
String type = in.readString();
switch (type) {
case "geometrycollection":
return readGeometryCollection(in);
case "polygon":
return readPolygon(in);
case "point":
return readPoint(in);
case "linestring":
return readLine(in);
case "multilinestring":
return readMultiLine(in);
case "multipoint":
return readMultiPoint(in);
case "multipolygon":
return readMultiPolygon(in);
case "envelope":
return readRectangle(in);
default:
throw new UnsupportedOperationException("unsupported shape type " + type);
}
}
private static GeometryCollection<Geometry> readGeometryCollection(StreamInput in) throws IOException {
int size = in.readVInt();
List<Geometry> shapes = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
shapes.add(readGeometry(in));
}
return new GeometryCollection<>(shapes);
}
private static Polygon readPolygon(StreamInput in) throws IOException {
double[][] shellComponents = readLineComponents(in);
boolean orientation = in.readBoolean();
LinearRing shell = buildLinearRing(shellComponents, orientation);
int numberOfHoles = in.readVInt();
if (numberOfHoles > 0) {
List<LinearRing> holes = new ArrayList<>(numberOfHoles);
for (int i = 0; i < numberOfHoles; i++) {
holes.add(buildLinearRing(readLineComponents(in), orientation));
}
return new Polygon(shell, holes);
} else {
return new Polygon(shell);
}
}
private static double[][] readLineComponents(StreamInput in) throws IOException {
int len = in.readVInt();
double[] lat = new double[len];
double[] lon = new double[len];
double[] alt = new double[len];
for (int i = 0; i < len; i++) {
lon[i] = in.readDouble();
lat[i] = in.readDouble();
alt[i] = readAlt(in);
}
if (Double.isNaN(alt[0])) {
return new double[][]{lat, lon};
} else {
return new double[][]{lat, lon, alt};
}
}
private static void reverse(double[][] arr) {
for (double[] carr : arr) {
int len = carr.length;
for (int j = 0; j < len / 2; j++) {
double temp = carr[j];
carr[j] = carr[len - j - 1];
carr[len - j - 1] = temp;
}
}
}
private static LinearRing buildLinearRing(double[][] arr, boolean orientation) {
if (orientation == false) {
reverse(arr);
}
if (arr.length == 3) {
return new LinearRing(arr[0], arr[1], arr[2]);
} else {
return new LinearRing(arr[0], arr[1]);
}
}
private static Point readPoint(StreamInput in) throws IOException {
int size = in.readVInt(); // For BWC with Shape Builder
if (size != 1) {
throw new IOException("Unexpected point count " + size);
}
double lon = in.readDouble();
double lat = in.readDouble();
double alt = readAlt(in);
return new Point(lat, lon, alt);
}
private static Line readLine(StreamInput in) throws IOException {
double[][] coords = readLineComponents(in);
if (coords.length == 3) {
return new Line(coords[0], coords[1], coords[2]);
} else {
return new Line(coords[0], coords[1]);
}
}
private static MultiLine readMultiLine(StreamInput in) throws IOException {
int size = in.readVInt();
List<Line> lines = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
lines.add(readLine(in));
}
return new MultiLine(lines);
}
private static MultiPoint readMultiPoint(StreamInput in) throws IOException {
int size = in.readVInt();
List<Point> points = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
double lon = in.readDouble();
double lat = in.readDouble();
double alt = readAlt(in);
points.add(new Point(lat, lon, alt));
}
return new MultiPoint(points);
}
private static MultiPolygon readMultiPolygon(StreamInput in) throws IOException {
in.readBoolean(); // orientation for BWC
int size = in.readVInt();
List<Polygon> polygons = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
polygons.add(readPolygon(in));
}
return new MultiPolygon(polygons);
}
private static Rectangle readRectangle(StreamInput in) throws IOException {
// top left
double minLon = in.readDouble();
double maxLat = in.readDouble();
double minAlt = readAlt(in);
// bottom right
double maxLon = in.readDouble();
double minLat = in.readDouble();
double maxAlt = readAlt(in);
return new Rectangle(minLat, maxLat, minLon, maxLon, minAlt, maxAlt);
}
private static double readAlt(StreamInput in) throws IOException {
Double alt = in.readOptionalDouble();
if (alt == null) {
return Double.NaN;
} else {
return alt;
}
}
}

View File

@ -245,6 +245,7 @@ public final class GeometryIndexer {
for (int i = 1; i < lons.length; i++) { for (int i = 1; i < lons.length; i++) {
double t = intersection(lastLon, lons[i], dateline); double t = intersection(lastLon, lons[i], dateline);
lastLon = lons[i];
if (Double.isNaN(t) == false) { if (Double.isNaN(t) == false) {
double[] partLons = Arrays.copyOfRange(lons, offset, i + 1); double[] partLons = Arrays.copyOfRange(lons, offset, i + 1);
double[] partLats = Arrays.copyOfRange(lats, offset, i + 1); double[] partLats = Arrays.copyOfRange(lats, offset, i + 1);
@ -330,7 +331,7 @@ public final class GeometryIndexer {
exterior.add(new Point(shell.getLat(i), shell.getLon(i))); exterior.add(new Point(shell.getLat(i), shell.getLon(i)));
} }
for (int i = 0; i < hole.length(); i++) { for (int i = 0; i < hole.length(); i++) {
interior.remove(new Point(hole.getLat(i), hole.getLon(i))); interior.add(new Point(hole.getLat(i), hole.getLon(i)));
} }
exterior.retainAll(interior); exterior.retainAll(interior);
if (exterior.size() >= 2) { if (exterior.size() >= 2) {
@ -645,7 +646,7 @@ public final class GeometryIndexer {
edges[edgeOffset + i - 1].next = edges[edgeOffset + i] = new Edge(nextPoint, null); edges[edgeOffset + i - 1].next = edges[edgeOffset + i] = new Edge(nextPoint, null);
edges[edgeOffset + i - 1].component = component; edges[edgeOffset + i - 1].component = component;
} else { } else {
throw new InvalidShapeException("Provided shape has duplicate consecutive coordinates at: " + nextPoint); throw new InvalidShapeException("Provided shape has duplicate consecutive coordinates at: (" + nextPoint + ")");
} }
} }

View File

@ -24,7 +24,6 @@ import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.geo.geometry.Line; import org.elasticsearch.geo.geometry.Line;
import org.elasticsearch.geo.geometry.MultiLine;
import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.GeometryFactory;
@ -36,9 +35,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.elasticsearch.common.geo.GeoUtils.normalizeLat;
import static org.elasticsearch.common.geo.GeoUtils.normalizeLon;
public class LineStringBuilder extends ShapeBuilder<JtsGeometry, org.elasticsearch.geo.geometry.Geometry, LineStringBuilder> { public class LineStringBuilder extends ShapeBuilder<JtsGeometry, org.elasticsearch.geo.geometry.Geometry, LineStringBuilder> {
public static final GeoShapeType TYPE = GeoShapeType.LINESTRING; public static final GeoShapeType TYPE = GeoShapeType.LINESTRING;
@ -126,18 +122,8 @@ public class LineStringBuilder extends ShapeBuilder<JtsGeometry, org.elasticsear
@Override @Override
public org.elasticsearch.geo.geometry.Geometry buildGeometry() { public org.elasticsearch.geo.geometry.Geometry buildGeometry() {
// decompose linestrings crossing dateline into array of Lines return new Line(coordinates.stream().mapToDouble(i->i.y).toArray(),
Coordinate[] coordinates = this.coordinates.toArray(new Coordinate[this.coordinates.size()]); coordinates.stream().mapToDouble(i->i.x).toArray());
if (wrapdateline) {
List<Line> linestrings = decomposeGeometry(coordinates, new ArrayList<>());
if (linestrings.size() == 1) {
return linestrings.get(0);
} else {
return new MultiLine(linestrings);
}
}
return new Line(Arrays.stream(coordinates).mapToDouble(i->normalizeLat(i.y)).toArray(),
Arrays.stream(coordinates).mapToDouble(i->normalizeLon(i.x)).toArray());
} }
static ArrayList<LineString> decomposeS4J(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) { static ArrayList<LineString> decomposeS4J(GeometryFactory factory, Coordinate[] coordinates, ArrayList<LineString> strings) {
@ -149,16 +135,6 @@ public class LineStringBuilder extends ShapeBuilder<JtsGeometry, org.elasticsear
return strings; return strings;
} }
static List<Line> decomposeGeometry(Coordinate[] coordinates, List<Line> lines) {
for (Coordinate[] part : decompose(+DATELINE, coordinates)) {
for (Coordinate[] line : decompose(-DATELINE, part)) {
lines.add(new Line(Arrays.stream(line).mapToDouble(i->normalizeLat(i.y)).toArray(),
Arrays.stream(line).mapToDouble(i->normalizeLon(i.x)).toArray()));
}
}
return lines;
}
/** /**
* Decompose a linestring given as array of coordinates at a vertical line. * Decompose a linestring given as array of coordinates at a vertical line.
* *

View File

@ -154,16 +154,6 @@ public class MultiLineStringBuilder extends ShapeBuilder<JtsGeometry, org.elasti
if (lines.isEmpty()) { if (lines.isEmpty()) {
return MultiLine.EMPTY; return MultiLine.EMPTY;
} }
if (wrapdateline) {
List<org.elasticsearch.geo.geometry.Line> parts = new ArrayList<>();
for (LineStringBuilder line : lines) {
LineStringBuilder.decomposeGeometry(line.coordinates(false), parts);
}
if (parts.size() == 1) {
return parts.get(0);
}
return new MultiLine(parts);
}
List<org.elasticsearch.geo.geometry.Line> linestrings = new ArrayList<>(lines.size()); List<org.elasticsearch.geo.geometry.Line> linestrings = new ArrayList<>(lines.size());
for (int i = 0; i < lines.size(); ++i) { for (int i = 0; i < lines.size(); ++i) {
LineStringBuilder lsb = lines.get(i); LineStringBuilder lsb = lines.get(i);

View File

@ -19,12 +19,6 @@
package org.elasticsearch.common.geo.builders; package org.elasticsearch.common.geo.builders;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.geo.parsers.ShapeParser;
@ -32,13 +26,18 @@ import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.jts.JtsGeometry; import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -47,8 +46,6 @@ import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static org.elasticsearch.common.geo.GeoUtils.normalizeLat;
import static org.elasticsearch.common.geo.GeoUtils.normalizeLon;
import static org.apache.lucene.geo.GeoUtils.orient; import static org.apache.lucene.geo.GeoUtils.orient;
/** /**
@ -235,12 +232,6 @@ public class PolygonBuilder extends ShapeBuilder<JtsGeometry, org.elasticsearch.
@Override @Override
public org.elasticsearch.geo.geometry.Geometry buildGeometry() { public org.elasticsearch.geo.geometry.Geometry buildGeometry() {
if (wrapdateline) {
Coordinate[][][] polygons = coordinates();
return polygons.length == 1
? polygonGeometry(polygons[0])
: multipolygon(polygons);
}
return toPolygonGeometry(); return toPolygonGeometry();
} }
@ -294,15 +285,12 @@ public class PolygonBuilder extends ShapeBuilder<JtsGeometry, org.elasticsearch.
for (int i = 0; i < this.holes.size(); ++i) { for (int i = 0; i < this.holes.size(); ++i) {
holes.add(linearRing(this.holes.get(i).coordinates)); holes.add(linearRing(this.holes.get(i).coordinates));
} }
return new org.elasticsearch.geo.geometry.Polygon( return new org.elasticsearch.geo.geometry.Polygon(linearRing(this.shell.coordinates), holes);
new org.elasticsearch.geo.geometry.LinearRing(
this.shell.coordinates.stream().mapToDouble(i -> normalizeLat(i.y)).toArray(),
this.shell.coordinates.stream().mapToDouble(i -> normalizeLon(i.x)).toArray()), holes);
} }
protected static org.elasticsearch.geo.geometry.LinearRing linearRing(List<Coordinate> coordinates) { protected static org.elasticsearch.geo.geometry.LinearRing linearRing(List<Coordinate> coordinates) {
return new org.elasticsearch.geo.geometry.LinearRing(coordinates.stream().mapToDouble(i -> normalizeLat(i.y)).toArray(), return new org.elasticsearch.geo.geometry.LinearRing(coordinates.stream().mapToDouble(i -> i.y).toArray(),
coordinates.stream().mapToDouble(i -> normalizeLon(i.x)).toArray()); coordinates.stream().mapToDouble(i -> i.x).toArray());
} }
protected static LinearRing linearRingS4J(GeometryFactory factory, List<Coordinate> coordinates) { protected static LinearRing linearRingS4J(GeometryFactory factory, List<Coordinate> coordinates) {
@ -338,39 +326,6 @@ public class PolygonBuilder extends ShapeBuilder<JtsGeometry, org.elasticsearch.
return factory.createPolygon(shell, holes); return factory.createPolygon(shell, holes);
} }
protected static org.elasticsearch.geo.geometry.Polygon polygonGeometry(Coordinate[][] polygon) {
List<org.elasticsearch.geo.geometry.LinearRing> holes;
Coordinate[] shell = polygon[0];
if (polygon.length > 1) {
holes = new ArrayList<>(polygon.length - 1);
for (int i = 1; i < polygon.length; ++i) {
Coordinate[] coords = polygon[i];
//We do not have holes on the dateline as they get eliminated
//when breaking the polygon around it.
double[] x = new double[coords.length];
double[] y = new double[coords.length];
for (int c = 0; c < coords.length; ++c) {
x[c] = normalizeLon(coords[c].x);
y[c] = normalizeLat(coords[c].y);
}
holes.add(new org.elasticsearch.geo.geometry.LinearRing(y, x));
}
} else {
holes = Collections.emptyList();
}
double[] x = new double[shell.length];
double[] y = new double[shell.length];
for (int i = 0; i < shell.length; ++i) {
//Lucene Tessellator treats different +180 and -180 and we should keep the sign.
//normalizeLon method excludes -180.
x[i] = Math.abs(shell[i].x) > 180 ? normalizeLon(shell[i].x) : shell[i].x;
y[i] = normalizeLat(shell[i].y);
}
return new org.elasticsearch.geo.geometry.Polygon(new org.elasticsearch.geo.geometry.LinearRing(y, x), holes);
}
/** /**
* Create a Multipolygon from a set of coordinates. Each primary array contains a polygon which * Create a Multipolygon from a set of coordinates. Each primary array contains a polygon which
* in turn contains an array of linestrings. These line Strings are represented as an array of * in turn contains an array of linestrings. These line Strings are represented as an array of
@ -389,14 +344,6 @@ public class PolygonBuilder extends ShapeBuilder<JtsGeometry, org.elasticsearch.
return factory.createMultiPolygon(polygonSet); return factory.createMultiPolygon(polygonSet);
} }
protected static org.elasticsearch.geo.geometry.MultiPolygon multipolygon(Coordinate[][][] polygons) {
List<org.elasticsearch.geo.geometry.Polygon> polygonSet = new ArrayList<>(polygons.length);
for (int i = 0; i < polygons.length; ++i) {
polygonSet.add(polygonGeometry(polygons[i]));
}
return new org.elasticsearch.geo.geometry.MultiPolygon(polygonSet);
}
/** /**
* This method sets the component id of all edges in a ring to a given id and shifts the * 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 * coordinates of this component according to the dateline

View File

@ -41,9 +41,21 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.geo.GeoJson;
import org.elasticsearch.common.geo.GeoShapeType; import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.GeometryIO;
import org.elasticsearch.common.geo.GeometryIndexer;
import org.elasticsearch.common.geo.GeometryParser;
import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.geo.SpatialStrategy; import org.elasticsearch.common.geo.SpatialStrategy;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder;
import org.elasticsearch.common.geo.builders.MultiLineStringBuilder;
import org.elasticsearch.common.geo.builders.MultiPointBuilder;
import org.elasticsearch.common.geo.builders.MultiPolygonBuilder;
import org.elasticsearch.common.geo.builders.PointBuilder;
import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.geo.parsers.ShapeParser; import org.elasticsearch.common.geo.parsers.ShapeParser;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -63,11 +75,16 @@ import org.elasticsearch.geo.geometry.MultiLine;
import org.elasticsearch.geo.geometry.MultiPoint; import org.elasticsearch.geo.geometry.MultiPoint;
import org.elasticsearch.geo.geometry.MultiPolygon; import org.elasticsearch.geo.geometry.MultiPolygon;
import org.elasticsearch.geo.geometry.Point; import org.elasticsearch.geo.geometry.Point;
import org.elasticsearch.geo.geometry.Rectangle;
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper; import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper; import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MappedFieldType;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.spatial4j.shape.Shape;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -105,8 +122,8 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
private final String fieldName; private final String fieldName;
private final ShapeBuilder shape; private final Geometry shape;
private final Supplier<ShapeBuilder> supplier; private final Supplier<Geometry> supplier;
private SpatialStrategy strategy; private SpatialStrategy strategy;
@ -130,11 +147,28 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
* Name of the field that will be queried * Name of the field that will be queried
* @param shape * @param shape
* Shape used in the Query * Shape used in the Query
*
* @deprecated use {@link #GeoShapeQueryBuilder(String, Geometry)} instead
*/ */
@Deprecated
public GeoShapeQueryBuilder(String fieldName, ShapeBuilder shape) { public GeoShapeQueryBuilder(String fieldName, ShapeBuilder shape) {
this(fieldName, shape == null ? null : shape.buildGeometry(), null, null);
}
/**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given
* field name using the given Shape
*
* @param fieldName
* Name of the field that will be queried
* @param shape
* Shape used in the Query
*/
public GeoShapeQueryBuilder(String fieldName, Geometry shape) {
this(fieldName, shape, null, null); this(fieldName, shape, null, null);
} }
/** /**
* Creates a new GeoShapeQueryBuilder whose Query will be against the given * Creates a new GeoShapeQueryBuilder whose Query will be against the given
* field name and will use the Shape found with the given ID * field name and will use the Shape found with the given ID
@ -145,7 +179,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
* ID of the indexed Shape that will be used in the Query * ID of the indexed Shape that will be used in the Query
*/ */
public GeoShapeQueryBuilder(String fieldName, String indexedShapeId) { public GeoShapeQueryBuilder(String fieldName, String indexedShapeId) {
this(fieldName, (ShapeBuilder) null, indexedShapeId, null); this(fieldName, (Geometry) null, indexedShapeId, null);
} }
/** /**
@ -163,10 +197,10 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
*/ */
@Deprecated @Deprecated
public GeoShapeQueryBuilder(String fieldName, String indexedShapeId, String indexedShapeType) { public GeoShapeQueryBuilder(String fieldName, String indexedShapeId, String indexedShapeType) {
this(fieldName, (ShapeBuilder) null, indexedShapeId, indexedShapeType); this(fieldName, (Geometry) null, indexedShapeId, indexedShapeType);
} }
private GeoShapeQueryBuilder(String fieldName, ShapeBuilder shape, String indexedShapeId, @Nullable String indexedShapeType) { private GeoShapeQueryBuilder(String fieldName, Geometry shape, String indexedShapeId, @Nullable String indexedShapeType) {
if (fieldName == null) { if (fieldName == null) {
throw new IllegalArgumentException("fieldName is required"); throw new IllegalArgumentException("fieldName is required");
} }
@ -180,7 +214,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
this.supplier = null; this.supplier = null;
} }
private GeoShapeQueryBuilder(String fieldName, Supplier<ShapeBuilder> supplier, String indexedShapeId, private GeoShapeQueryBuilder(String fieldName, Supplier<Geometry> supplier, String indexedShapeId,
@Nullable String indexedShapeType) { @Nullable String indexedShapeType) {
this.fieldName = fieldName; this.fieldName = fieldName;
this.shape = null; this.shape = null;
@ -196,7 +230,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
super(in); super(in);
fieldName = in.readString(); fieldName = in.readString();
if (in.readBoolean()) { if (in.readBoolean()) {
shape = in.readNamedWriteable(ShapeBuilder.class); shape = GeometryIO.readGeometry(in);
indexedShapeId = null; indexedShapeId = null;
indexedShapeType = null; indexedShapeType = null;
} else { } else {
@ -226,7 +260,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
boolean hasShape = shape != null; boolean hasShape = shape != null;
out.writeBoolean(hasShape); out.writeBoolean(hasShape);
if (hasShape) { if (hasShape) {
out.writeNamedWriteable(shape); GeometryIO.writeGeometry(out, shape);;
} else { } else {
out.writeOptionalString(indexedShapeId); out.writeOptionalString(indexedShapeId);
out.writeOptionalString(indexedShapeType); out.writeOptionalString(indexedShapeType);
@ -253,7 +287,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
/** /**
* @return the shape used in the Query * @return the shape used in the Query
*/ */
public ShapeBuilder shape() { public Geometry shape() {
return shape; return shape;
} }
@ -406,7 +440,6 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
if (shape == null || supplier != null) { if (shape == null || supplier != null) {
throw new UnsupportedOperationException("query must be rewritten first"); throw new UnsupportedOperationException("query must be rewritten first");
} }
final ShapeBuilder shapeToQuery = shape;
final MappedFieldType fieldType = context.fieldMapper(fieldName); final MappedFieldType fieldType = context.fieldMapper(fieldName);
if (fieldType == null) { if (fieldType == null) {
if (ignoreUnmapped) { if (ignoreUnmapped) {
@ -434,32 +467,36 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
// in this case, execute disjoint as exists && !intersects // in this case, execute disjoint as exists && !intersects
BooleanQuery.Builder bool = new BooleanQuery.Builder(); BooleanQuery.Builder bool = new BooleanQuery.Builder();
Query exists = ExistsQueryBuilder.newFilter(context, fieldName); Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
Query intersects = prefixTreeStrategy.makeQuery(getArgs(shapeToQuery, ShapeRelation.INTERSECTS)); Query intersects = prefixTreeStrategy.makeQuery(getArgs(shape, ShapeRelation.INTERSECTS));
bool.add(exists, BooleanClause.Occur.MUST); bool.add(exists, BooleanClause.Occur.MUST);
bool.add(intersects, BooleanClause.Occur.MUST_NOT); bool.add(intersects, BooleanClause.Occur.MUST_NOT);
query = new ConstantScoreQuery(bool.build()); query = new ConstantScoreQuery(bool.build());
} else { } else {
query = new ConstantScoreQuery(prefixTreeStrategy.makeQuery(getArgs(shapeToQuery, relation))); query = new ConstantScoreQuery(prefixTreeStrategy.makeQuery(getArgs(shape, relation)));
} }
} else { } else {
query = new ConstantScoreQuery(getVectorQuery(context, shapeToQuery)); query = new ConstantScoreQuery(getVectorQuery(context, shape));
} }
return query; return query;
} }
private Query getVectorQuery(QueryShardContext context, ShapeBuilder queryShapeBuilder) { private Query getVectorQuery(QueryShardContext context, Geometry queryShape) {
// CONTAINS queries are not yet supported by VECTOR strategy // CONTAINS queries are not yet supported by VECTOR strategy
if (relation == ShapeRelation.CONTAINS) { if (relation == ShapeRelation.CONTAINS) {
throw new QueryShardException(context, throw new QueryShardException(context,
ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]"); ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]");
} }
// wrap geoQuery as a ConstantScoreQuery // TODO: Move this to QueryShardContext
return getVectorQueryFromShape(context, queryShapeBuilder.buildGeometry()); GeometryIndexer geometryIndexer = new GeometryIndexer(true);
}
private Query getVectorQueryFromShape(QueryShardContext context, Geometry queryShape) { Geometry processedShape = geometryIndexer.prepareForIndexing(queryShape);
return queryShape.visit(new GeometryVisitor<Query, RuntimeException>() {
if (processedShape == null) {
return new MatchNoDocsQuery();
}
return processedShape.visit(new GeometryVisitor<Query, RuntimeException>() {
@Override @Override
public Query visit(Circle circle) { public Query visit(Circle circle) {
throw new QueryShardException(context, "Field [" + fieldName + "] found and unknown shape Circle"); throw new QueryShardException(context, "Field [" + fieldName + "] found and unknown shape Circle");
@ -545,7 +582,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
* Name or path of the field in the Shape Document where the * Name or path of the field in the Shape Document where the
* Shape itself is located * Shape itself is located
*/ */
private void fetch(Client client, GetRequest getRequest, String path, ActionListener<ShapeBuilder> listener) { private void fetch(Client client, GetRequest getRequest, String path, ActionListener<Geometry> listener) {
getRequest.preference("_local"); getRequest.preference("_local");
client.get(getRequest, new ActionListener<GetResponse>(){ client.get(getRequest, new ActionListener<GetResponse>(){
@ -574,7 +611,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
if (pathElements[currentPathSlot].equals(parser.currentName())) { if (pathElements[currentPathSlot].equals(parser.currentName())) {
parser.nextToken(); parser.nextToken();
if (++currentPathSlot == pathElements.length) { if (++currentPathSlot == pathElements.length) {
listener.onResponse(ShapeParser.parse(parser)); listener.onResponse(new GeometryParser(true, true, true).parse(parser));
return; return;
} }
} else { } else {
@ -598,16 +635,16 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
} }
public static SpatialArgs getArgs(ShapeBuilder shape, ShapeRelation relation) { public static SpatialArgs getArgs(Geometry shape, ShapeRelation relation) {
switch (relation) { switch (relation) {
case DISJOINT: case DISJOINT:
return new SpatialArgs(SpatialOperation.IsDisjointTo, shape.buildS4J()); return new SpatialArgs(SpatialOperation.IsDisjointTo, buildS4J(shape));
case INTERSECTS: case INTERSECTS:
return new SpatialArgs(SpatialOperation.Intersects, shape.buildS4J()); return new SpatialArgs(SpatialOperation.Intersects, buildS4J(shape));
case WITHIN: case WITHIN:
return new SpatialArgs(SpatialOperation.IsWithin, shape.buildS4J()); return new SpatialArgs(SpatialOperation.IsWithin, buildS4J(shape));
case CONTAINS: case CONTAINS:
return new SpatialArgs(SpatialOperation.Contains, shape.buildS4J()); return new SpatialArgs(SpatialOperation.Contains, buildS4J(shape));
default: default:
throw new IllegalArgumentException("invalid relation [" + relation + "]"); throw new IllegalArgumentException("invalid relation [" + relation + "]");
} }
@ -625,7 +662,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
if (shape != null) { if (shape != null) {
builder.field(SHAPE_FIELD.getPreferredName()); builder.field(SHAPE_FIELD.getPreferredName());
shape.toXContent(builder, params); GeoJson.toXContent(shape, builder,params);
} else { } else {
builder.startObject(INDEXED_SHAPE_FIELD.getPreferredName()) builder.startObject(INDEXED_SHAPE_FIELD.getPreferredName())
.field(SHAPE_ID_FIELD.getPreferredName(), indexedShapeId); .field(SHAPE_ID_FIELD.getPreferredName(), indexedShapeId);
@ -806,7 +843,7 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
return supplier.get() == null ? this : new GeoShapeQueryBuilder(this.fieldName, supplier.get()).relation(relation).strategy return supplier.get() == null ? this : new GeoShapeQueryBuilder(this.fieldName, supplier.get()).relation(relation).strategy
(strategy); (strategy);
} else if (this.shape == null) { } else if (this.shape == null) {
SetOnce<ShapeBuilder> supplier = new SetOnce<>(); SetOnce<Geometry> supplier = new SetOnce<>();
queryRewriteContext.registerAsyncAction((client, listener) -> { queryRewriteContext.registerAsyncAction((client, listener) -> {
GetRequest getRequest; GetRequest getRequest;
if (indexedShapeType == null) { if (indexedShapeType == null) {
@ -825,4 +862,96 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
} }
return this; return this;
} }
/**
* Builds JTS shape from a geometry
*
* This method is needed to handle legacy indices and will be removed when we no longer need to build JTS shapes
*/
private static Shape buildS4J(Geometry geometry) {
return geometryToShapeBuilder(geometry).buildS4J();
}
public static ShapeBuilder<?, ?, ?> geometryToShapeBuilder(Geometry geometry) {
ShapeBuilder<?, ?, ?> shapeBuilder = geometry.visit(new GeometryVisitor<ShapeBuilder<?, ?, ?>, RuntimeException>() {
@Override
public ShapeBuilder<?, ?, ?> visit(Circle circle) {
throw new UnsupportedOperationException("circle is not supported");
}
@Override
public ShapeBuilder<?, ?, ?> visit(GeometryCollection<?> collection) {
GeometryCollectionBuilder shapes = new GeometryCollectionBuilder();
for (Geometry geometry : collection) {
shapes.shape(geometry.visit(this));
}
return shapes;
}
@Override
public ShapeBuilder<?, ?, ?> visit(org.elasticsearch.geo.geometry.Line line) {
List<Coordinate> coordinates = new ArrayList<>();
for (int i = 0; i < line.length(); i++) {
coordinates.add(new Coordinate(line.getLon(i), line.getLat(i), line.getAlt(i)));
}
return new LineStringBuilder(coordinates);
}
@Override
public ShapeBuilder<?, ?, ?> visit(LinearRing ring) {
throw new UnsupportedOperationException("circle is not supported");
}
@Override
public ShapeBuilder<?, ?, ?> visit(MultiLine multiLine) {
MultiLineStringBuilder lines = new MultiLineStringBuilder();
for (int i = 0; i < multiLine.size(); i++) {
lines.linestring((LineStringBuilder) visit(multiLine.get(i)));
}
return lines;
}
@Override
public ShapeBuilder<?, ?, ?> visit(MultiPoint multiPoint) {
List<Coordinate> coordinates = new ArrayList<>();
for (int i = 0; i < multiPoint.size(); i++) {
Point p = multiPoint.get(i);
coordinates.add(new Coordinate(p.getLon(), p.getLat(), p.getAlt()));
}
return new MultiPointBuilder(coordinates);
}
@Override
public ShapeBuilder<?, ?, ?> visit(MultiPolygon multiPolygon) {
MultiPolygonBuilder polygons = new MultiPolygonBuilder();
for (int i = 0; i < multiPolygon.size(); i++) {
polygons.polygon((PolygonBuilder) visit(multiPolygon.get(i)));
}
return polygons;
}
@Override
public ShapeBuilder<?, ?, ?> visit(Point point) {
return new PointBuilder(point.getLon(), point.getLat());
}
@Override
public ShapeBuilder<?, ?, ?> visit(org.elasticsearch.geo.geometry.Polygon polygon) {
PolygonBuilder polygonBuilder =
new PolygonBuilder((LineStringBuilder) visit((org.elasticsearch.geo.geometry.Line) polygon.getPolygon()),
ShapeBuilder.Orientation.RIGHT, false);
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
polygonBuilder.hole((LineStringBuilder) visit((org.elasticsearch.geo.geometry.Line) polygon.getHole(i)));
}
return polygonBuilder;
}
@Override
public ShapeBuilder<?, ?, ?> visit(Rectangle rectangle) {
return new EnvelopeBuilder(new Coordinate(rectangle.getMinLon(), rectangle.getMaxLat()),
new Coordinate(rectangle.getMaxLon(), rectangle.getMinLat()));
}
});
return shapeBuilder;
}
} }

View File

@ -470,7 +470,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
} else { } else {
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random()); GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
assertExpected(gcb.buildS4J(), gcb, true); assertExpected(gcb.buildS4J(), gcb, true);
assertExpected(gcb.buildGeometry(), gcb, false); assertExpected(new GeometryIndexer(true).prepareForIndexing(gcb.buildGeometry()), gcb, false);
} }
} }

View File

@ -0,0 +1,110 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.common.geo;
import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.geo.geometry.Geometry;
import org.elasticsearch.geo.geometry.GeometryCollection;
import org.elasticsearch.geo.geometry.ShapeType;
import org.elasticsearch.test.ESTestCase;
import static org.elasticsearch.geo.GeometryTestUtils.randomGeometry;
import static org.elasticsearch.index.query.GeoShapeQueryBuilder.geometryToShapeBuilder;
public class GeometryIOTests extends ESTestCase {
public void testRandomSerialization() throws Exception {
for (int i = 0; i < randomIntBetween(1, 20); i++) {
boolean hasAlt = randomBoolean();
Geometry geometry = randomGeometry(hasAlt);
if (shapeSupported(geometry) && randomBoolean()) {
// Shape builder conversion doesn't support altitude
ShapeBuilder<?, ?, ?> shapeBuilder = geometryToShapeBuilder(geometry);
if (randomBoolean()) {
Geometry actual = shapeBuilder.buildGeometry();
assertEquals(geometry, actual);
}
if (randomBoolean()) {
// Test ShapeBuilder -> Geometry Serialization
try (BytesStreamOutput out = new BytesStreamOutput()) {
out.writeNamedWriteable(shapeBuilder);
try (StreamInput in = out.bytes().streamInput()) {
Geometry actual = GeometryIO.readGeometry(in);
assertEquals(geometry, actual);
assertEquals(0, in.available());
}
}
} else {
// Test Geometry -> ShapeBuilder Serialization
try (BytesStreamOutput out = new BytesStreamOutput()) {
GeometryIO.writeGeometry(out, geometry);
try (StreamInput in = out.bytes().streamInput()) {
try (StreamInput nin = new NamedWriteableAwareStreamInput(in, this.writableRegistry())) {
ShapeBuilder<?, ?, ?> actual = nin.readNamedWriteable(ShapeBuilder.class);
assertEquals(shapeBuilder, actual);
assertEquals(0, in.available());
}
}
}
}
// Test Geometry -> Geometry
try (BytesStreamOutput out = new BytesStreamOutput()) {
GeometryIO.writeGeometry(out, geometry);
;
try (StreamInput in = out.bytes().streamInput()) {
Geometry actual = GeometryIO.readGeometry(in);
assertEquals(geometry, actual);
assertEquals(0, in.available());
}
}
}
}
}
private boolean shapeSupported(Geometry geometry) {
if (geometry.hasAlt()) {
return false;
}
if (geometry.type() == ShapeType.CIRCLE) {
return false;
}
if (geometry.type() == ShapeType.GEOMETRYCOLLECTION) {
GeometryCollection<?> collection = (GeometryCollection<?>) geometry;
for (Geometry g : collection) {
if (shapeSupported(g) == false) {
return false;
}
}
}
return true;
}
@Override
protected NamedWriteableRegistry writableRegistry() {
return new NamedWriteableRegistry(GeoShapeType.getShapeWriteables());
}
}

View File

@ -51,7 +51,7 @@ public class GeometryParserTests extends ESTestCase {
assertEquals(new Point(0, 100), format.fromXContent(parser)); assertEquals(new Point(0, 100), format.fromXContent(parser));
XContentBuilder newGeoJson = XContentFactory.jsonBuilder(); XContentBuilder newGeoJson = XContentFactory.jsonBuilder();
format.toXContent(new Point(10, 100), newGeoJson, ToXContent.EMPTY_PARAMS); format.toXContent(new Point(10, 100), newGeoJson, ToXContent.EMPTY_PARAMS);
assertEquals("{\"type\":\"Point\",\"coordinates\":[100.0,10.0]}", Strings.toString(newGeoJson)); assertEquals("{\"type\":\"point\",\"coordinates\":[100.0,10.0]}", Strings.toString(newGeoJson));
} }
XContentBuilder pointGeoJsonWithZ = XContentFactory.jsonBuilder() XContentBuilder pointGeoJsonWithZ = XContentFactory.jsonBuilder()
@ -148,7 +148,7 @@ public class GeometryParserTests extends ESTestCase {
// if we serialize non-null value - it should be serialized as geojson // if we serialize non-null value - it should be serialized as geojson
format.toXContent(new Point(10, 100), newGeoJson, ToXContent.EMPTY_PARAMS); format.toXContent(new Point(10, 100), newGeoJson, ToXContent.EMPTY_PARAMS);
newGeoJson.endObject(); newGeoJson.endObject();
assertEquals("{\"val\":{\"type\":\"Point\",\"coordinates\":[100.0,10.0]}}", Strings.toString(newGeoJson)); assertEquals("{\"val\":{\"type\":\"point\",\"coordinates\":[100.0,10.0]}}", Strings.toString(newGeoJson));
newGeoJson = XContentFactory.jsonBuilder().startObject().field("val"); newGeoJson = XContentFactory.jsonBuilder().startObject().field("val");
format.toXContent(null, newGeoJson, ToXContent.EMPTY_PARAMS); format.toXContent(null, newGeoJson, ToXContent.EMPTY_PARAMS);

View File

@ -19,12 +19,8 @@
package org.elasticsearch.common.geo; package org.elasticsearch.common.geo;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Polygon;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.CircleBuilder; import org.elasticsearch.common.geo.builders.CircleBuilder;
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
import org.elasticsearch.common.geo.builders.EnvelopeBuilder; import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
import org.elasticsearch.common.geo.builders.LineStringBuilder; import org.elasticsearch.common.geo.builders.LineStringBuilder;
import org.elasticsearch.common.geo.builders.MultiLineStringBuilder; import org.elasticsearch.common.geo.builders.MultiLineStringBuilder;
@ -32,6 +28,9 @@ import org.elasticsearch.common.geo.builders.PointBuilder;
import org.elasticsearch.common.geo.builders.PolygonBuilder; import org.elasticsearch.common.geo.builders.PolygonBuilder;
import org.elasticsearch.common.geo.builders.ShapeBuilder; import org.elasticsearch.common.geo.builders.ShapeBuilder;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.spatial4j.exception.InvalidShapeException; import org.locationtech.spatial4j.exception.InvalidShapeException;
import org.locationtech.spatial4j.shape.Circle; import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Point;
@ -161,7 +160,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(-110.0, 55.0)); .coordinate(-110.0, 55.0));
lsb.buildS4J(); lsb.buildS4J();
lsb.buildGeometry(); buildGeometry(lsb);
// Building a linestring that needs to be wrapped // Building a linestring that needs to be wrapped
lsb = new LineStringBuilder(new CoordinatesBuilder() lsb = new LineStringBuilder(new CoordinatesBuilder()
@ -175,7 +174,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(130.0, 60.0)); .coordinate(130.0, 60.0));
lsb.buildS4J(); lsb.buildS4J();
lsb.buildGeometry(); buildGeometry(lsb);
// Building a lineString on the dateline // Building a lineString on the dateline
lsb = new LineStringBuilder(new CoordinatesBuilder() lsb = new LineStringBuilder(new CoordinatesBuilder()
@ -185,7 +184,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(-180.0, -80.0)); .coordinate(-180.0, -80.0));
lsb.buildS4J(); lsb.buildS4J();
lsb.buildGeometry(); buildGeometry(lsb);
// Building a lineString on the dateline // Building a lineString on the dateline
lsb = new LineStringBuilder(new CoordinatesBuilder() lsb = new LineStringBuilder(new CoordinatesBuilder()
@ -195,7 +194,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(180.0, -80.0)); .coordinate(180.0, -80.0));
lsb.buildS4J(); lsb.buildS4J();
lsb.buildGeometry(); buildGeometry(lsb);
} }
public void testMultiLineString() { public void testMultiLineString() {
@ -215,7 +214,7 @@ public class ShapeBuilderTests extends ESTestCase {
) )
); );
mlsb.buildS4J(); mlsb.buildS4J();
mlsb.buildGeometry(); buildGeometry(mlsb);
// LineString that needs to be wrapped // LineString that needs to be wrapped
new MultiLineStringBuilder() new MultiLineStringBuilder()
@ -235,7 +234,7 @@ public class ShapeBuilderTests extends ESTestCase {
); );
mlsb.buildS4J(); mlsb.buildS4J();
mlsb.buildGeometry(); buildGeometry(mlsb);
} }
public void testPolygonSelfIntersection() { public void testPolygonSelfIntersection() {
@ -283,7 +282,7 @@ public class ShapeBuilderTests extends ESTestCase {
.close()); .close());
assertMultiPolygon(pb.buildS4J(), true); assertMultiPolygon(pb.buildS4J(), true);
assertMultiPolygon(pb.buildGeometry(), false); assertMultiPolygon(buildGeometry(pb), false);
} }
public void testLineStringWrapping() { public void testLineStringWrapping() {
@ -295,7 +294,7 @@ public class ShapeBuilderTests extends ESTestCase {
.close()); .close());
assertMultiLineString(lsb.buildS4J(), true); assertMultiLineString(lsb.buildS4J(), true);
assertMultiLineString(lsb.buildGeometry(), false); assertMultiLineString(buildGeometry(lsb), false);
} }
public void testDatelineOGC() { public void testDatelineOGC() {
@ -339,7 +338,7 @@ public class ShapeBuilderTests extends ESTestCase {
)); ));
assertMultiPolygon(builder.close().buildS4J(), true); assertMultiPolygon(builder.close().buildS4J(), true);
assertMultiPolygon(builder.close().buildGeometry(), false); assertMultiPolygon(buildGeometry(builder.close()), false);
} }
public void testDateline() { public void testDateline() {
@ -383,7 +382,7 @@ public class ShapeBuilderTests extends ESTestCase {
)); ));
assertMultiPolygon(builder.close().buildS4J(), true); assertMultiPolygon(builder.close().buildS4J(), true);
assertMultiPolygon(builder.close().buildGeometry(), false); assertMultiPolygon(buildGeometry(builder.close()), false);
} }
public void testComplexShapeWithHole() { public void testComplexShapeWithHole() {
@ -458,7 +457,7 @@ public class ShapeBuilderTests extends ESTestCase {
) )
); );
assertPolygon(builder.close().buildS4J(), true); assertPolygon(builder.close().buildS4J(), true);
assertPolygon(builder.close().buildGeometry(), false); assertPolygon(buildGeometry(builder.close()), false);
} }
public void testShapeWithHoleAtEdgeEndPoints() { public void testShapeWithHoleAtEdgeEndPoints() {
@ -480,7 +479,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(4, 1) .coordinate(4, 1)
)); ));
assertPolygon(builder.close().buildS4J(), true); assertPolygon(builder.close().buildS4J(), true);
assertPolygon(builder.close().buildGeometry(), false); assertPolygon(buildGeometry(builder.close()), false);
} }
public void testShapeWithPointOnDateline() { public void testShapeWithPointOnDateline() {
@ -491,7 +490,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(180, 0) .coordinate(180, 0)
); );
assertPolygon(builder.close().buildS4J(), true); assertPolygon(builder.close().buildS4J(), true);
assertPolygon(builder.close().buildGeometry(), false); assertPolygon(buildGeometry(builder.close()), false);
} }
public void testShapeWithEdgeAlongDateline() { public void testShapeWithEdgeAlongDateline() {
@ -504,7 +503,7 @@ public class ShapeBuilderTests extends ESTestCase {
); );
assertPolygon(builder.close().buildS4J(), true); assertPolygon(builder.close().buildS4J(), true);
assertPolygon(builder.close().buildGeometry(), false); assertPolygon(buildGeometry(builder.close()), false);
// test case 2: test the negative side of the dateline // test case 2: test the negative side of the dateline
builder = new PolygonBuilder(new CoordinatesBuilder() builder = new PolygonBuilder(new CoordinatesBuilder()
@ -515,7 +514,7 @@ public class ShapeBuilderTests extends ESTestCase {
); );
assertPolygon(builder.close().buildS4J(), true); assertPolygon(builder.close().buildS4J(), true);
assertPolygon(builder.close().buildGeometry(), false); assertPolygon(buildGeometry(builder.close()), false);
} }
public void testShapeWithBoundaryHoles() { public void testShapeWithBoundaryHoles() {
@ -537,7 +536,7 @@ public class ShapeBuilderTests extends ESTestCase {
)); ));
assertMultiPolygon(builder.close().buildS4J(), true); assertMultiPolygon(builder.close().buildS4J(), true);
assertMultiPolygon(builder.close().buildGeometry(), false); assertMultiPolygon(buildGeometry(builder.close()), false);
// test case 2: test the negative side of the dateline // test case 2: test the negative side of the dateline
builder = new PolygonBuilder( builder = new PolygonBuilder(
@ -560,7 +559,7 @@ public class ShapeBuilderTests extends ESTestCase {
)); ));
assertMultiPolygon(builder.close().buildS4J(), true); assertMultiPolygon(builder.close().buildS4J(), true);
assertMultiPolygon(builder.close().buildGeometry(), false); assertMultiPolygon(buildGeometry(builder.close()), false);
} }
public void testShapeWithTangentialHole() { public void testShapeWithTangentialHole() {
@ -582,7 +581,7 @@ public class ShapeBuilderTests extends ESTestCase {
)); ));
assertMultiPolygon(builder.close().buildS4J(), true); assertMultiPolygon(builder.close().buildS4J(), true);
assertMultiPolygon(builder.close().buildGeometry(), false); assertMultiPolygon(buildGeometry(builder.close()), false);
} }
public void testShapeWithInvalidTangentialHole() { public void testShapeWithInvalidTangentialHole() {
@ -606,7 +605,7 @@ public class ShapeBuilderTests extends ESTestCase {
e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J());
assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior"));
e = expectThrows(InvalidShapeException.class, () -> builder.close().buildGeometry()); e = expectThrows(IllegalArgumentException.class, () -> buildGeometry(builder.close()));
assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior"));
} }
@ -634,7 +633,7 @@ public class ShapeBuilderTests extends ESTestCase {
.coordinate(172, 0) .coordinate(172, 0)
)); ));
assertMultiPolygon(builder.close().buildS4J(), true); assertMultiPolygon(builder.close().buildS4J(), true);
assertMultiPolygon(builder.close().buildGeometry(), false); assertMultiPolygon(buildGeometry(builder.close()), false);
} }
public void testBoundaryShapeWithInvalidTangentialHole() { public void testBoundaryShapeWithInvalidTangentialHole() {
@ -657,7 +656,7 @@ public class ShapeBuilderTests extends ESTestCase {
Exception e; Exception e;
e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J());
assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior"));
e = expectThrows(InvalidShapeException.class, () -> builder.close().buildGeometry()); e = expectThrows(IllegalArgumentException.class, () -> buildGeometry(builder.close()));
assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior")); assertThat(e.getMessage(), containsString("interior cannot share more than one point with the exterior"));
} }
@ -673,7 +672,7 @@ public class ShapeBuilderTests extends ESTestCase {
); );
assertPolygon(builder.close().buildS4J(), true); assertPolygon(builder.close().buildS4J(), true);
assertPolygon(builder.close().buildGeometry(), false); assertPolygon(buildGeometry(builder.close()), false);
} }
public void testShapeWithAlternateOrientation() { public void testShapeWithAlternateOrientation() {
@ -686,7 +685,7 @@ public class ShapeBuilderTests extends ESTestCase {
); );
assertPolygon(builder.close().buildS4J(), true); assertPolygon(builder.close().buildS4J(), true);
assertPolygon(builder.close().buildGeometry(), false); assertPolygon(buildGeometry(builder.close()), false);
// cw: geo core will convert to ccw across the dateline // cw: geo core will convert to ccw across the dateline
builder = new PolygonBuilder(new CoordinatesBuilder() builder = new PolygonBuilder(new CoordinatesBuilder()
@ -697,7 +696,7 @@ public class ShapeBuilderTests extends ESTestCase {
); );
assertMultiPolygon(builder.close().buildS4J(), true); assertMultiPolygon(builder.close().buildS4J(), true);
assertMultiPolygon(builder.close().buildGeometry(), false); assertMultiPolygon(buildGeometry(builder.close()), false);
} }
public void testInvalidShapeWithConsecutiveDuplicatePoints() { public void testInvalidShapeWithConsecutiveDuplicatePoints() {
@ -711,7 +710,7 @@ public class ShapeBuilderTests extends ESTestCase {
Exception e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); Exception e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J());
assertThat(e.getMessage(), containsString("duplicate consecutive coordinates at: (")); assertThat(e.getMessage(), containsString("duplicate consecutive coordinates at: ("));
e = expectThrows(InvalidShapeException.class, () -> builder.close().buildGeometry()); e = expectThrows(InvalidShapeException.class, () -> buildGeometry(builder.close()));
assertThat(e.getMessage(), containsString("duplicate consecutive coordinates at: (")); assertThat(e.getMessage(), containsString("duplicate consecutive coordinates at: ("));
} }
@ -774,7 +773,11 @@ public class ShapeBuilderTests extends ESTestCase {
); );
Exception e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J()); Exception e = expectThrows(InvalidShapeException.class, () -> builder.close().buildS4J());
assertThat(e.getMessage(), containsString("Self-intersection at or near point [")); assertThat(e.getMessage(), containsString("Self-intersection at or near point ["));
e = expectThrows(InvalidShapeException.class, () -> builder.close().buildGeometry()); e = expectThrows(InvalidShapeException.class, () -> buildGeometry(builder.close()));
assertThat(e.getMessage(), containsString("Self-intersection at or near point [")); assertThat(e.getMessage(), containsString("Self-intersection at or near point ["));
} }
public Object buildGeometry(ShapeBuilder<?, ?, ?> builder) {
return new GeometryIndexer(true).prepareForIndexing(builder.buildGeometry());
}
} }

View File

@ -198,7 +198,7 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
// see #3878 // see #3878
public void testThatXContentSerializationInsideOfArrayWorks() throws Exception { public void testThatXContentSerializationInsideOfArrayWorks() throws Exception {
EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(0, 0), new Coordinate(10, 10)); EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(0, 10), new Coordinate(10, 0));
GeoShapeQueryBuilder geoQuery = QueryBuilders.geoShapeQuery("searchGeometry", envelopeBuilder); GeoShapeQueryBuilder geoQuery = QueryBuilders.geoShapeQuery("searchGeometry", envelopeBuilder);
JsonXContent.contentBuilder().startArray().value(geoQuery).endArray(); JsonXContent.contentBuilder().startArray().value(geoQuery).endArray();
} }

View File

@ -161,19 +161,27 @@ public class GeometryTestUtils {
int size = ESTestCase.randomIntBetween(1, 10); int size = ESTestCase.randomIntBetween(1, 10);
List<Geometry> shapes = new ArrayList<>(); List<Geometry> shapes = new ArrayList<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
@SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = ESTestCase.randomFrom( shapes.add(randomGeometry(level, hasAlt));
GeometryTestUtils::randomCircle,
GeometryTestUtils::randomLine,
GeometryTestUtils::randomPoint,
GeometryTestUtils::randomPolygon,
GeometryTestUtils::randomMultiLine,
GeometryTestUtils::randomMultiPoint,
GeometryTestUtils::randomMultiPolygon,
hasAlt ? GeometryTestUtils::randomPoint : (b) -> randomRectangle(),
level < 3 ? (b) -> randomGeometryCollection(level + 1, b) : GeometryTestUtils::randomPoint // don't build too deep
);
shapes.add(geometry.apply(hasAlt));
} }
return new GeometryCollection<>(shapes); return new GeometryCollection<>(shapes);
} }
public static Geometry randomGeometry(boolean hasAlt) {
return randomGeometry(0, hasAlt);
}
private static Geometry randomGeometry(int level, boolean hasAlt) {
@SuppressWarnings("unchecked") Function<Boolean, Geometry> geometry = ESTestCase.randomFrom(
GeometryTestUtils::randomCircle,
GeometryTestUtils::randomLine,
GeometryTestUtils::randomPoint,
GeometryTestUtils::randomPolygon,
GeometryTestUtils::randomMultiLine,
GeometryTestUtils::randomMultiPoint,
GeometryTestUtils::randomMultiPolygon,
hasAlt ? GeometryTestUtils::randomPoint : (b) -> randomRectangle(),
level < 3 ? (b) -> randomGeometryCollection(level + 1, b) : GeometryTestUtils::randomPoint // don't build too deep
);
return geometry.apply(hasAlt);
}
} }