38
38
* as the centroid of a shape.
39
39
*/
40
40
public class CentroidCalculator {
41
-
42
41
private double compX ;
43
42
private double compY ;
44
43
private double sumX ;
45
44
private double sumY ;
46
- private int count ;
45
+ private double sumWeight ;
47
46
private DimensionalShapeType dimensionalShapeType ;
48
47
49
48
public CentroidCalculator (Geometry geometry ) {
50
49
this .sumX = 0.0 ;
51
50
this .compX = 0.0 ;
52
51
this .sumY = 0.0 ;
53
52
this .compY = 0.0 ;
54
- this .count = 0 ;
53
+ this .sumWeight = 0. 0 ;
55
54
CentroidCalculatorVisitor visitor = new CentroidCalculatorVisitor (this );
56
55
geometry .visit (visitor );
57
56
this .dimensionalShapeType = DimensionalShapeType .forGeometry (geometry );
@@ -60,22 +59,22 @@ public CentroidCalculator(Geometry geometry) {
60
59
/**
61
60
* adds a single coordinate to the running sum and count of coordinates
62
61
* for centroid calculation
63
- *
64
- * @param x the x-coordinate of the point
62
+ * @param x the x-coordinate of the point
65
63
* @param y the y-coordinate of the point
64
+ * @param weight the associated weight of the coordinate
66
65
*/
67
- private void addCoordinate (double x , double y ) {
68
- double correctedX = x - compX ;
66
+ private void addCoordinate (double x , double y , double weight ) {
67
+ double correctedX = weight * x - compX ;
69
68
double newSumX = sumX + correctedX ;
70
69
compX = (newSumX - sumX ) - correctedX ;
71
70
sumX = newSumX ;
72
71
73
- double correctedY = y - compY ;
72
+ double correctedY = weight * y - compY ;
74
73
double newSumY = sumY + correctedY ;
75
74
compY = (newSumY - sumY ) - correctedY ;
76
75
sumY = newSumY ;
77
76
78
- count += 1 ;
77
+ sumWeight += weight ;
79
78
}
80
79
81
80
/**
@@ -87,26 +86,45 @@ private void addCoordinate(double x, double y) {
87
86
* @param otherCalculator the other centroid calculator to add from
88
87
*/
89
88
public void addFrom (CentroidCalculator otherCalculator ) {
90
- addCoordinate (otherCalculator .sumX , otherCalculator .sumY );
91
- // adjust count
92
- count += otherCalculator .count - 1 ;
93
- dimensionalShapeType = DimensionalShapeType .max (dimensionalShapeType , otherCalculator .dimensionalShapeType );
89
+ int compared = DimensionalShapeType .COMPARATOR .compare (dimensionalShapeType , otherCalculator .dimensionalShapeType );
90
+ if (compared < 0 ) {
91
+ sumWeight = otherCalculator .sumWeight ;
92
+ dimensionalShapeType = otherCalculator .dimensionalShapeType ;
93
+ sumX = otherCalculator .sumX ;
94
+ sumY = otherCalculator .sumY ;
95
+ compX = otherCalculator .compX ;
96
+ compY = otherCalculator .compY ;
97
+ } else if (compared == 0 ) {
98
+ addCoordinate (otherCalculator .sumX , otherCalculator .sumY , otherCalculator .sumWeight );
99
+ } // else (compared > 0) do not modify centroid calculation since otherCalculator is of lower dimension than this calculator
94
100
}
95
101
96
102
/**
97
103
* @return the x-coordinate centroid
98
104
*/
99
105
public double getX () {
100
- return sumX / count ;
106
+ // normalization required due to floating point precision errors
107
+ return GeoUtils .normalizeLon (sumX / sumWeight );
101
108
}
102
109
103
110
/**
104
111
* @return the y-coordinate centroid
105
112
*/
106
113
public double getY () {
107
- return sumY / count ;
114
+ // normalization required due to floating point precision errors
115
+ return GeoUtils .normalizeLat (sumY / sumWeight );
116
+ }
117
+
118
+ /**
119
+ * @return the sum of all the weighted coordinates summed in the calculator
120
+ */
121
+ public double sumWeight () {
122
+ return sumWeight ;
108
123
}
109
124
125
+ /**
126
+ * @return the highest dimensional shape type summed in the calculator
127
+ */
110
128
public DimensionalShapeType getDimensionalShapeType () {
111
129
return dimensionalShapeType ;
112
130
}
@@ -121,8 +139,7 @@ private CentroidCalculatorVisitor(CentroidCalculator calculator) {
121
139
122
140
@ Override
123
141
public Void visit (Circle circle ) {
124
- calculator .addCoordinate (circle .getX (), circle .getY ());
125
- return null ;
142
+ throw new IllegalArgumentException ("invalid shape type found [Circle] while calculating centroid" );
126
143
}
127
144
128
145
@ Override
@@ -135,17 +152,47 @@ public Void visit(GeometryCollection<?> collection) {
135
152
136
153
@ Override
137
154
public Void visit (Line line ) {
138
- for (int i = 0 ; i < line .length (); i ++) {
139
- calculator .addCoordinate (line .getX (i ), line .getY (i ));
155
+ // a line's centroid is calculated by summing the center of each
156
+ // line segment weighted by the line segment's length in degrees
157
+ for (int i = 0 ; i < line .length () - 1 ; i ++) {
158
+ double diffX = line .getX (i ) - line .getX (i + 1 );
159
+ double diffY = line .getY (i ) - line .getY (i + 1 );
160
+ double x = (line .getX (i ) + line .getX (i + 1 )) / 2 ;
161
+ double y = (line .getY (i ) + line .getY (i + 1 )) / 2 ;
162
+ calculator .addCoordinate (x , y , Math .sqrt (diffX * diffX + diffY * diffY ));
140
163
}
141
164
return null ;
142
165
}
143
-
144
166
@ Override
145
167
public Void visit (LinearRing ring ) {
168
+ throw new IllegalArgumentException ("invalid shape type found [LinearRing] while calculating centroid" );
169
+ }
170
+
171
+ private Void visit (LinearRing ring , boolean isHole ) {
172
+ // implementation of calculation defined in
173
+ // https://www.seas.upenn.edu/~sys502/extra_materials/Polygon%20Area%20and%20Centroid.pdf
174
+ //
175
+ // centroid of a ring is a weighted coordinate based on the ring's area.
176
+ // the sign of the area is positive for the outer-shell of a polygon and negative for the holes
177
+
178
+ int sign = isHole ? -1 : 1 ;
179
+ double totalRingArea = 0.0 ;
146
180
for (int i = 0 ; i < ring .length () - 1 ; i ++) {
147
- calculator . addCoordinate ( ring .getX (i ), ring .getY (i ));
181
+ totalRingArea += ( ring . getX ( i ) * ring .getY ( i + 1 )) - ( ring . getX (i + 1 ) * ring .getY (i ));
148
182
}
183
+ totalRingArea = totalRingArea / 2 ;
184
+
185
+ double sumX = 0.0 ;
186
+ double sumY = 0.0 ;
187
+ for (int i = 0 ; i < ring .length () - 1 ; i ++) {
188
+ double twiceArea = (ring .getX (i ) * ring .getY (i + 1 )) - (ring .getX (i + 1 ) * ring .getY (i ));
189
+ sumX += twiceArea * (ring .getX (i ) + ring .getX (i + 1 ));
190
+ sumY += twiceArea * (ring .getY (i ) + ring .getY (i + 1 ));
191
+ }
192
+ double cX = sumX / (6 * totalRingArea );
193
+ double cY = sumY / (6 * totalRingArea );
194
+ calculator .addCoordinate (cX , cY , sign * Math .abs (totalRingArea ));
195
+
149
196
return null ;
150
197
}
151
198
@@ -175,22 +222,26 @@ public Void visit(MultiPolygon multiPolygon) {
175
222
176
223
@ Override
177
224
public Void visit (Point point ) {
178
- calculator .addCoordinate (point .getX (), point .getY ());
225
+ calculator .addCoordinate (point .getX (), point .getY (), 1.0 );
179
226
return null ;
180
227
}
181
228
182
229
@ Override
183
230
public Void visit (Polygon polygon ) {
184
- // TODO: incorporate holes into centroid calculation
185
- return visit (polygon .getPolygon ());
231
+ visit (polygon .getPolygon (), false );
232
+ for (int i = 0 ; i < polygon .getNumberOfHoles (); i ++) {
233
+ visit (polygon .getHole (i ), true );
234
+ }
235
+ return null ;
186
236
}
187
237
188
238
@ Override
189
239
public Void visit (Rectangle rectangle ) {
190
- calculator .addCoordinate (rectangle .getMinX (), rectangle .getMinY ());
191
- calculator .addCoordinate (rectangle .getMinX (), rectangle .getMaxY ());
192
- calculator .addCoordinate (rectangle .getMaxX (), rectangle .getMinY ());
193
- calculator .addCoordinate (rectangle .getMaxX (), rectangle .getMaxY ());
240
+ double sumX = rectangle .getMaxX () + rectangle .getMinX ();
241
+ double sumY = rectangle .getMaxY () + rectangle .getMinY ();
242
+ double diffX = rectangle .getMaxX () - rectangle .getMinX ();
243
+ double diffY = rectangle .getMaxY () - rectangle .getMinY ();
244
+ calculator .addCoordinate (sumX / 2 , sumY / 2 , Math .abs (diffX * diffY ));
194
245
return null ;
195
246
}
196
247
}
0 commit comments