Skip to content

Commit 0ea0afe

Browse files
committed
add shape-type metadata to geo_shape's doc-value (#50104)
This commit serializes the ShapeType of the indexed geometry. The ShapeType can be useful for other future features. For one thing: #49887 depends on the ability to determine what the highest dimensional shape is for centroid calculations. GeometryCollection is reduced to the sub-shape of the highest dimension relates #37206.
1 parent df0447a commit 0ea0afe

File tree

8 files changed

+302
-6
lines changed

8 files changed

+302
-6
lines changed

server/src/main/java/org/elasticsearch/common/geo/CentroidCalculator.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,17 @@ public class CentroidCalculator {
4444
private double sumX;
4545
private double sumY;
4646
private int count;
47+
private DimensionalShapeType dimensionalShapeType;
4748

4849
public CentroidCalculator(Geometry geometry) {
4950
this.sumX = 0.0;
5051
this.compX = 0.0;
5152
this.sumY = 0.0;
5253
this.compY = 0.0;
5354
this.count = 0;
54-
geometry.visit(new CentroidCalculatorVisitor(this));
55+
CentroidCalculatorVisitor visitor = new CentroidCalculatorVisitor(this);
56+
geometry.visit(visitor);
57+
this.dimensionalShapeType = DimensionalShapeType.forGeometry(geometry);
5558
}
5659

5760
/**
@@ -87,6 +90,7 @@ public void addFrom(CentroidCalculator otherCalculator) {
8790
addCoordinate(otherCalculator.sumX, otherCalculator.sumY);
8891
// adjust count
8992
count += otherCalculator.count - 1;
93+
dimensionalShapeType = DimensionalShapeType.max(dimensionalShapeType, otherCalculator.dimensionalShapeType);
9094
}
9195

9296
/**
@@ -103,6 +107,10 @@ public double getY() {
103107
return sumY / count;
104108
}
105109

110+
public DimensionalShapeType getDimensionalShapeType() {
111+
return dimensionalShapeType;
112+
}
113+
106114
private static class CentroidCalculatorVisitor implements GeometryVisitor<Void, IllegalArgumentException> {
107115

108116
private final CentroidCalculator calculator;
@@ -127,7 +135,6 @@ public Void visit(GeometryCollection<?> collection) {
127135

128136
@Override
129137
public Void visit(Line line) {
130-
131138
for (int i = 0; i < line.length(); i++) {
132139
calculator.addCoordinate(line.getX(i), line.getY(i));
133140
}
@@ -174,6 +181,7 @@ public Void visit(Point point) {
174181

175182
@Override
176183
public Void visit(Polygon polygon) {
184+
// TODO: incorporate holes into centroid calculation
177185
return visit(polygon.getPolygon());
178186
}
179187

@@ -186,4 +194,5 @@ public Void visit(Rectangle rectangle) {
186194
return null;
187195
}
188196
}
197+
189198
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.common.geo;
20+
21+
import org.apache.lucene.store.ByteArrayDataInput;
22+
import org.apache.lucene.store.ByteBuffersDataOutput;
23+
import org.elasticsearch.geometry.Circle;
24+
import org.elasticsearch.geometry.Geometry;
25+
import org.elasticsearch.geometry.GeometryCollection;
26+
import org.elasticsearch.geometry.GeometryVisitor;
27+
import org.elasticsearch.geometry.Line;
28+
import org.elasticsearch.geometry.LinearRing;
29+
import org.elasticsearch.geometry.MultiLine;
30+
import org.elasticsearch.geometry.MultiPoint;
31+
import org.elasticsearch.geometry.MultiPolygon;
32+
import org.elasticsearch.geometry.Point;
33+
import org.elasticsearch.geometry.Polygon;
34+
import org.elasticsearch.geometry.Rectangle;
35+
import org.elasticsearch.geometry.ShapeType;
36+
37+
import java.util.Comparator;
38+
39+
/**
40+
* Like {@link ShapeType} but has specific
41+
* types for when the geometry is a {@link GeometryCollection} and
42+
* more information about what the highest-dimensional sub-shape
43+
* is.
44+
*/
45+
public enum DimensionalShapeType {
46+
POINT,
47+
MULTIPOINT,
48+
LINESTRING,
49+
MULTILINESTRING,
50+
POLYGON,
51+
MULTIPOLYGON,
52+
GEOMETRYCOLLECTION_POINTS, // highest-dimensional shapes are Points
53+
GEOMETRYCOLLECTION_LINES, // highest-dimensional shapes are Lines
54+
GEOMETRYCOLLECTION_POLYGONS; // highest-dimensional shapes are Polygons
55+
56+
private static DimensionalShapeType[] values = values();
57+
58+
private static Comparator<DimensionalShapeType> COMPARATOR = Comparator.comparingInt(DimensionalShapeType::centroidDimension);
59+
60+
public static DimensionalShapeType max(DimensionalShapeType s1, DimensionalShapeType s2) {
61+
if (s1 == null) {
62+
return s2;
63+
} else if (s2 == null) {
64+
return s1;
65+
}
66+
return COMPARATOR.compare(s1, s2) >= 0 ? s1 : s2;
67+
}
68+
69+
public void writeTo(ByteBuffersDataOutput out) {
70+
out.writeByte((byte) ordinal());
71+
}
72+
73+
public static DimensionalShapeType readFrom(ByteArrayDataInput in) {
74+
return values[Byte.toUnsignedInt(in.readByte())];
75+
}
76+
77+
public static DimensionalShapeType forGeometry(Geometry geometry) {
78+
return geometry.visit(new GeometryVisitor<>() {
79+
private DimensionalShapeType st = null;
80+
81+
@Override
82+
public DimensionalShapeType visit(Circle circle) {
83+
st = DimensionalShapeType.max(st, DimensionalShapeType.POLYGON);
84+
return st;
85+
}
86+
87+
@Override
88+
public DimensionalShapeType visit(Line line) {
89+
st = DimensionalShapeType.max(st, DimensionalShapeType.LINESTRING);
90+
return st;
91+
}
92+
93+
@Override
94+
public DimensionalShapeType visit(LinearRing ring) {
95+
throw new UnsupportedOperationException("should not visit LinearRing");
96+
}
97+
98+
@Override
99+
public DimensionalShapeType visit(MultiLine multiLine) {
100+
st = DimensionalShapeType.max(st, DimensionalShapeType.MULTILINESTRING);
101+
return st;
102+
}
103+
104+
@Override
105+
public DimensionalShapeType visit(MultiPoint multiPoint) {
106+
st = DimensionalShapeType.max(st, DimensionalShapeType.MULTIPOINT);
107+
return st;
108+
}
109+
110+
@Override
111+
public DimensionalShapeType visit(MultiPolygon multiPolygon) {
112+
st = DimensionalShapeType.max(st, DimensionalShapeType.MULTIPOLYGON);
113+
return st;
114+
}
115+
116+
@Override
117+
public DimensionalShapeType visit(Point point) {
118+
st = DimensionalShapeType.max(st, DimensionalShapeType.POINT);
119+
return st;
120+
}
121+
122+
@Override
123+
public DimensionalShapeType visit(Polygon polygon) {
124+
st = DimensionalShapeType.max(st, DimensionalShapeType.POLYGON);
125+
return st;
126+
}
127+
128+
@Override
129+
public DimensionalShapeType visit(Rectangle rectangle) {
130+
st = DimensionalShapeType.max(st, DimensionalShapeType.POLYGON);
131+
return st;
132+
}
133+
134+
@Override
135+
public DimensionalShapeType visit(GeometryCollection<?> collection) {
136+
for (Geometry shape : collection) {
137+
shape.visit(this);
138+
}
139+
int dimension = st.centroidDimension();
140+
if (dimension == 0) {
141+
return DimensionalShapeType.GEOMETRYCOLLECTION_POINTS;
142+
} else if (dimension == 1) {
143+
return DimensionalShapeType.GEOMETRYCOLLECTION_LINES;
144+
} else {
145+
return DimensionalShapeType.GEOMETRYCOLLECTION_POLYGONS;
146+
}
147+
}
148+
});
149+
}
150+
151+
/**
152+
* The integer representation of the dimension for the specific
153+
* dimensional shape type. This is to be used by the centroid
154+
* calculation to determine whether to add a sub-shape's centroid
155+
* to the overall shape calculation.
156+
*
157+
* @return 0 for points, 1 for lines, 2 for polygons
158+
*/
159+
private int centroidDimension() {
160+
switch (this) {
161+
case POINT:
162+
case MULTIPOINT:
163+
case GEOMETRYCOLLECTION_POINTS:
164+
return 0;
165+
case LINESTRING:
166+
case MULTILINESTRING:
167+
case GEOMETRYCOLLECTION_LINES:
168+
return 1;
169+
case POLYGON:
170+
case MULTIPOLYGON:
171+
case GEOMETRYCOLLECTION_POLYGONS:
172+
return 2;
173+
default:
174+
throw new IllegalStateException("dimension calculation of DimensionalShapeType [" + this + "] is not supported");
175+
}
176+
}
177+
}

server/src/main/java/org/elasticsearch/common/geo/TriangleTreeReader.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
* relations against the serialized triangle tree.
3434
*/
3535
public class TriangleTreeReader {
36+
private static final int CENTROID_HEADER_SIZE_IN_BYTES = 9;
3637

37-
private static final int extentOffset = 8;
3838
private final ByteArrayDataInput input;
3939
private final CoordinateEncoder coordinateEncoder;
4040
private final Rectangle2D rectangle2D;
@@ -59,7 +59,7 @@ public void reset(BytesRef bytesRef) throws IOException {
5959
public Extent getExtent() {
6060
if (treeOffset == 0) {
6161
// TODO: Compress serialization of extent
62-
input.setPosition(extentOffset);
62+
input.setPosition(CENTROID_HEADER_SIZE_IN_BYTES);
6363
int top = input.readInt();
6464
int bottom = Math.toIntExact(top - input.readVLong());
6565
int posRight = input.readInt();
@@ -90,6 +90,11 @@ public double getCentroidY() {
9090
return coordinateEncoder.decodeY(input.readInt());
9191
}
9292

93+
public DimensionalShapeType getDimensionalShapeType() {
94+
input.setPosition(8);
95+
return DimensionalShapeType.readFrom(input);
96+
}
97+
9398
/**
9499
* Compute the relation with the provided bounding box. If the result is CELL_INSIDE_QUERY
95100
* then the bounding box is within the shape.

server/src/main/java/org/elasticsearch/common/geo/TriangleTreeWriter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public TriangleTreeWriter(List<ShapeField.DecodedTriangle> triangles, Coordinate
4949
public void writeTo(ByteBuffersDataOutput out) throws IOException {
5050
out.writeInt(coordinateEncoder.encodeX(centroidCalculator.getX()));
5151
out.writeInt(coordinateEncoder.encodeY(centroidCalculator.getY()));
52-
// TODO: Compress serialization of extent
52+
centroidCalculator.getDimensionalShapeType().writeTo(out);
5353
out.writeInt(extent.top);
5454
out.writeVLong((long) extent.top - extent.bottom);
5555
out.writeInt(extent.posRight);

server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoValues.java

+12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.lucene.util.BytesRef;
2626
import org.elasticsearch.common.geo.CentroidCalculator;
2727
import org.elasticsearch.common.geo.CoordinateEncoder;
28+
import org.elasticsearch.common.geo.DimensionalShapeType;
2829
import org.elasticsearch.common.geo.Extent;
2930
import org.elasticsearch.common.geo.GeoPoint;
3031
import org.elasticsearch.common.geo.GeoRelation;
@@ -117,6 +118,11 @@ public GeoRelation relate(Rectangle rectangle) {
117118
return GeoRelation.QUERY_DISJOINT;
118119
}
119120

121+
@Override
122+
public DimensionalShapeType dimensionalShapeType() {
123+
return DimensionalShapeType.POINT;
124+
}
125+
120126
@Override
121127
public double lat() {
122128
return geoPoint.lat();
@@ -162,6 +168,11 @@ public GeoRelation relate(Rectangle rectangle) {
162168
return reader.relate(minX, minY, maxX, maxY);
163169
}
164170

171+
@Override
172+
public DimensionalShapeType dimensionalShapeType() {
173+
return reader.getDimensionalShapeType();
174+
}
175+
165176
@Override
166177
public double lat() {
167178
return reader.getCentroidY();
@@ -217,6 +228,7 @@ public interface GeoValue {
217228
double lon();
218229
BoundingBox boundingBox();
219230
GeoRelation relate(Rectangle rectangle);
231+
DimensionalShapeType dimensionalShapeType();
220232
}
221233

222234
public static class BoundingBox {

server/src/main/java/org/elasticsearch/index/mapper/AbstractGeometryFieldMapper.java

+1
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ public void parse(ParseContext context) throws IOException {
451451
System.arraycopy(bytesRef.bytes, bytesRef.offset, scratch, 0, 7 * Integer.BYTES);
452452
ShapeField.decodeTriangle(scratch, triangles[i] = new ShapeField.DecodedTriangle());
453453
}
454+
454455
geometryIndexer.indexDocValueField(context, triangles, new CentroidCalculator((Geometry) shape));
455456
}
456457
createFieldNamesField(context, fields);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.common.geo;
21+
22+
import org.apache.lucene.store.ByteArrayDataInput;
23+
import org.apache.lucene.store.ByteBuffersDataOutput;
24+
import org.elasticsearch.test.ESTestCase;
25+
26+
import static org.hamcrest.Matchers.equalTo;
27+
28+
public class DimensionalShapeTypeTests extends ESTestCase {
29+
30+
public void testValidOrdinals() {
31+
assertThat(DimensionalShapeType.values().length, equalTo(9));
32+
assertThat(DimensionalShapeType.POINT.ordinal(), equalTo(0));
33+
assertThat(DimensionalShapeType.MULTIPOINT.ordinal(), equalTo(1));
34+
assertThat(DimensionalShapeType.LINESTRING.ordinal(), equalTo(2));
35+
assertThat(DimensionalShapeType.MULTILINESTRING.ordinal(), equalTo(3));
36+
assertThat(DimensionalShapeType.POLYGON.ordinal(), equalTo(4));
37+
assertThat(DimensionalShapeType.MULTIPOLYGON.ordinal(), equalTo(5));
38+
assertThat(DimensionalShapeType.GEOMETRYCOLLECTION_POINTS.ordinal(), equalTo(6));
39+
assertThat(DimensionalShapeType.GEOMETRYCOLLECTION_LINES.ordinal(), equalTo(7));
40+
assertThat(DimensionalShapeType.GEOMETRYCOLLECTION_POLYGONS.ordinal(), equalTo(8));
41+
}
42+
43+
public void testSerialization() {
44+
for (DimensionalShapeType type : DimensionalShapeType.values()) {
45+
ByteBuffersDataOutput out = new ByteBuffersDataOutput();
46+
type.writeTo(out);
47+
ByteArrayDataInput input = new ByteArrayDataInput(out.toArrayCopy());
48+
assertThat(DimensionalShapeType.readFrom(input), equalTo(type));
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)