6
6
7
7
package org.elasticsearch.xpack.spatial.index.fielddata;
8
8
9
- import org.elasticsearch.common.geo.GeoUtils;
10
9
import org.elasticsearch.geometry.Circle;
11
10
import org.elasticsearch.geometry.Geometry;
12
11
import org.elasticsearch.geometry.GeometryCollection;
27
26
* as the centroid of a shape.
28
27
*/
29
28
public class CentroidCalculator {
30
- CompensatedSum compSumX;
31
- CompensatedSum compSumY;
32
- CompensatedSum compSumWeight;
33
- private CentroidCalculatorVisitor visitor;
34
- private DimensionalShapeType dimensionalShapeType;
35
-
36
- public CentroidCalculator(Geometry geometry) {
37
- this.compSumX = new CompensatedSum(0, 0);
38
- this.compSumY = new CompensatedSum(0, 0);
39
- this.compSumWeight = new CompensatedSum(0, 0);
40
- this.dimensionalShapeType = null;
41
- this.visitor = new CentroidCalculatorVisitor(this);
42
- geometry.visit(visitor);
43
- this.dimensionalShapeType = visitor.calculator.dimensionalShapeType;
44
- }
45
29
46
- /**
47
- * adds a single coordinate to the running sum and count of coordinates
48
- * for centroid calculation
49
- * @param x the x-coordinate of the point
50
- * @param y the y-coordinate of the point
51
- * @param weight the associated weight of the coordinate
52
- */
53
- private void addCoordinate(double x, double y, double weight, DimensionalShapeType dimensionalShapeType) {
54
- // x and y can be infinite due to really small areas and rounding problems
55
- if (Double.isFinite(x) && Double.isFinite(y)) {
56
- if (this.dimensionalShapeType == null || this.dimensionalShapeType == dimensionalShapeType) {
57
- compSumX.add(x * weight);
58
- compSumY.add(y * weight);
59
- compSumWeight.add(weight);
60
- this.dimensionalShapeType = dimensionalShapeType;
61
- } else if (dimensionalShapeType.compareTo(this.dimensionalShapeType) > 0) {
62
- // reset counters
63
- compSumX.reset(x * weight, 0);
64
- compSumY.reset(y * weight, 0);
65
- compSumWeight.reset(weight, 0);
66
- this.dimensionalShapeType = dimensionalShapeType;
67
- }
68
- }
30
+ private final CentroidCalculatorVisitor visitor;
31
+
32
+ public CentroidCalculator() {
33
+ this.visitor = new CentroidCalculatorVisitor();
69
34
}
70
35
71
36
/**
72
- * Adjusts the existing calculator to add the running sum and count
73
- * from another {@link CentroidCalculator}. This is used to keep
74
- * a running count of points from different sub-shapes of a single
75
- * geo-shape field
37
+ * Add a geometry to the calculator
76
38
*
77
- * @param otherCalculator the other centroid calculator to add from
39
+ * @param geometry the geometry to add
78
40
*/
79
- public void addFrom(CentroidCalculator otherCalculator) {
80
- int compared = dimensionalShapeType.compareTo(otherCalculator.dimensionalShapeType);
81
- if (compared < 0) {
82
- dimensionalShapeType = otherCalculator.dimensionalShapeType;
83
- this.compSumX = otherCalculator.compSumX;
84
- this.compSumY = otherCalculator.compSumY;
85
- this.compSumWeight = otherCalculator.compSumWeight;
86
-
87
- } else if (compared == 0) {
88
- this.compSumX.add(otherCalculator.compSumX.value());
89
- this.compSumY.add(otherCalculator.compSumY.value());
90
- this.compSumWeight.add(otherCalculator.compSumWeight.value());
91
- } // else (compared > 0) do not modify centroid calculation since otherCalculator is of lower dimension than this calculator
41
+ public void add(Geometry geometry) {
42
+ geometry.visit(visitor);
92
43
}
93
44
94
45
/**
95
46
* @return the x-coordinate centroid
96
47
*/
97
48
public double getX() {
98
- // normalization required due to floating point precision errors
99
- return GeoUtils.normalizeLon(compSumX.value() / compSumWeight.value());
49
+ return visitor.compSumX.value() / visitor.compSumWeight.value();
100
50
}
101
51
102
52
/**
103
53
* @return the y-coordinate centroid
104
54
*/
105
55
public double getY() {
106
- // normalization required due to floating point precision errors
107
- return GeoUtils.normalizeLat(compSumY.value() / compSumWeight.value());
56
+ return visitor.compSumY.value() / visitor.compSumWeight.value();
108
57
}
109
58
110
59
/**
111
60
* @return the sum of all the weighted coordinates summed in the calculator
112
61
*/
113
62
public double sumWeight() {
114
- return compSumWeight.value();
63
+ return visitor. compSumWeight.value();
115
64
}
116
65
117
66
/**
118
67
* @return the highest dimensional shape type summed in the calculator
119
68
*/
120
69
public DimensionalShapeType getDimensionalShapeType() {
121
- return dimensionalShapeType;
70
+ return visitor. dimensionalShapeType;
122
71
}
123
72
124
73
private static class CentroidCalculatorVisitor implements GeometryVisitor<Void, IllegalArgumentException> {
125
74
126
- private final CentroidCalculator calculator;
75
+ final CompensatedSum compSumX;
76
+ final CompensatedSum compSumY;
77
+ final CompensatedSum compSumWeight;
78
+ DimensionalShapeType dimensionalShapeType;
127
79
128
- private CentroidCalculatorVisitor(CentroidCalculator calculator) {
129
- this.calculator = calculator;
80
+ private CentroidCalculatorVisitor() {
81
+ this.compSumX = new CompensatedSum(0, 0);
82
+ this.compSumY = new CompensatedSum(0, 0);
83
+ this.compSumWeight = new CompensatedSum(0, 0);
84
+ this.dimensionalShapeType = DimensionalShapeType.POINT;
130
85
}
131
86
132
87
@Override
@@ -144,7 +99,7 @@ public Void visit(GeometryCollection<?> collection) {
144
99
145
100
@Override
146
101
public Void visit(Line line) {
147
- if (calculator. dimensionalShapeType != DimensionalShapeType.POLYGON) {
102
+ if (dimensionalShapeType != DimensionalShapeType.POLYGON) {
148
103
visitLine(line.length(), line::getX, line::getY);
149
104
}
150
105
return null;
@@ -158,7 +113,7 @@ public Void visit(LinearRing ring) {
158
113
159
114
@Override
160
115
public Void visit(MultiLine multiLine) {
161
- if (calculator.getDimensionalShapeType() != DimensionalShapeType.POLYGON) {
116
+ if (dimensionalShapeType != DimensionalShapeType.POLYGON) {
162
117
for (Line line : multiLine) {
163
118
visit(line);
164
119
}
@@ -168,7 +123,7 @@ public Void visit(MultiLine multiLine) {
168
123
169
124
@Override
170
125
public Void visit(MultiPoint multiPoint) {
171
- if (calculator.getDimensionalShapeType() == null || calculator.getDimensionalShapeType() == DimensionalShapeType.POINT) {
126
+ if (dimensionalShapeType == DimensionalShapeType.POINT) {
172
127
for (Point point : multiPoint) {
173
128
visit(point);
174
129
}
@@ -186,7 +141,7 @@ public Void visit(MultiPolygon multiPolygon) {
186
141
187
142
@Override
188
143
public Void visit(Point point) {
189
- if (calculator.getDimensionalShapeType() == null || calculator.getDimensionalShapeType() == DimensionalShapeType.POINT) {
144
+ if (dimensionalShapeType == DimensionalShapeType.POINT) {
190
145
visitPoint(point.getX(), point.getY());
191
146
}
192
147
return null;
@@ -211,11 +166,11 @@ public Void visit(Polygon polygon) {
211
166
sumWeight += w;
212
167
}
213
168
214
- if (sumWeight == 0 && calculator. dimensionalShapeType != DimensionalShapeType.POLYGON) {
169
+ if (sumWeight == 0 && dimensionalShapeType != DimensionalShapeType.POLYGON) {
215
170
visitLine(polygon.getPolygon().length(), polygon.getPolygon()::getX, polygon.getPolygon()::getY);
216
171
} else {
217
172
for (int i = 0; i < 1 + polygon.getNumberOfHoles(); i++) {
218
- calculator. addCoordinate(centroidX[i], centroidY[i], weight[i], DimensionalShapeType.POLYGON);
173
+ addCoordinate(centroidX[i], centroidY[i], weight[i], DimensionalShapeType.POLYGON);
219
174
}
220
175
}
221
176
@@ -230,7 +185,7 @@ public Void visit(Rectangle rectangle) {
230
185
if (rectWeight != 0) {
231
186
double sumX = rectangle.getMaxX() + rectangle.getMinX();
232
187
double sumY = rectangle.getMaxY() + rectangle.getMinY();
233
- calculator. addCoordinate(sumX / 2, sumY / 2, rectWeight, DimensionalShapeType.POLYGON);
188
+ addCoordinate(sumX / 2, sumY / 2, rectWeight, DimensionalShapeType.POLYGON);
234
189
} else {
235
190
// degenerated rectangle, transform to Line
236
191
Line line = new Line(new double[]{rectangle.getMinX(), rectangle.getMaxX()},
@@ -240,9 +195,8 @@ public Void visit(Rectangle rectangle) {
240
195
return null;
241
196
}
242
197
243
-
244
198
private void visitPoint(double x, double y) {
245
- calculator. addCoordinate(x, y, 1.0, DimensionalShapeType.POINT);
199
+ addCoordinate(x, y, 1.0, DimensionalShapeType.POINT);
246
200
}
247
201
248
202
private void visitLine(int length, CoordinateSupplier x, CoordinateSupplier y) {
@@ -258,7 +212,7 @@ private void visitLine(int length, CoordinateSupplier x, CoordinateSupplier y) {
258
212
// degenerated line, it can be considered a point
259
213
visitPoint(x.get(i), y.get(i));
260
214
} else {
261
- calculator. addCoordinate(xAvg, yAvg, weight, DimensionalShapeType.LINE);
215
+ addCoordinate(xAvg, yAvg, weight, DimensionalShapeType.LINE);
262
216
}
263
217
}
264
218
}
@@ -289,6 +243,31 @@ private void visitLinearRing(int length, CoordinateSupplier x, CoordinateSupplie
289
243
centroidY[idx] = sumY / (6 * totalRingArea);
290
244
weight[idx] = sign * Math.abs(totalRingArea);
291
245
}
246
+
247
+ /**
248
+ * adds a single coordinate to the running sum and count of coordinates
249
+ * for centroid calculation
250
+ * @param x the x-coordinate of the point
251
+ * @param y the y-coordinate of the point
252
+ * @param weight the associated weight of the coordinate
253
+ */
254
+ private void addCoordinate(double x, double y, double weight, DimensionalShapeType dimensionalShapeType) {
255
+ // x and y can be infinite due to really small areas and rounding problems
256
+ if (Double.isFinite(x) && Double.isFinite(y)) {
257
+ if (this.dimensionalShapeType == dimensionalShapeType) {
258
+ compSumX.add(x * weight);
259
+ compSumY.add(y * weight);
260
+ compSumWeight.add(weight);
261
+ this.dimensionalShapeType = dimensionalShapeType;
262
+ } else if (dimensionalShapeType.compareTo(this.dimensionalShapeType) > 0) {
263
+ // reset counters
264
+ compSumX.reset(x * weight, 0);
265
+ compSumY.reset(y * weight, 0);
266
+ compSumWeight.reset(weight, 0);
267
+ this.dimensionalShapeType = dimensionalShapeType;
268
+ }
269
+ }
270
+ }
292
271
}
293
272
294
273
@FunctionalInterface
0 commit comments