0
# Visitor Pattern Processing
1
2
The Elasticsearch Geo library implements the visitor pattern to enable type-safe processing of different geometry types without requiring instanceof checks or type casting. This pattern allows for extensible operations on geometric data while maintaining compile-time type safety.
3
4
## Capabilities
5
6
### GeometryVisitor Interface
7
The core interface that defines visit methods for all supported geometry types.
8
9
```java { .api }
10
/**
11
* Generic visitor interface for processing geometry objects
12
* @param <T> return type of visit operations
13
* @param <E> exception type that visit methods may throw
14
*/
15
public interface GeometryVisitor<T, E extends Exception> {
16
17
/**
18
* Visits a Circle geometry
19
* @param circle the circle to process
20
* @return processing result
21
* @throws E if processing fails
22
*/
23
T visit(Circle circle) throws E;
24
25
/**
26
* Visits a GeometryCollection
27
* @param collection the geometry collection to process
28
* @return processing result
29
* @throws E if processing fails
30
*/
31
T visit(GeometryCollection<?> collection) throws E;
32
33
/**
34
* Visits a Line geometry
35
* @param line the line to process
36
* @return processing result
37
* @throws E if processing fails
38
*/
39
T visit(Line line) throws E;
40
41
/**
42
* Visits a LinearRing geometry
43
* @param ring the linear ring to process
44
* @return processing result
45
* @throws E if processing fails
46
*/
47
T visit(LinearRing ring) throws E;
48
49
/**
50
* Visits a MultiLine geometry
51
* @param multiLine the multi-line to process
52
* @return processing result
53
* @throws E if processing fails
54
*/
55
T visit(MultiLine multiLine) throws E;
56
57
/**
58
* Visits a MultiPoint geometry
59
* @param multiPoint the multi-point to process
60
* @return processing result
61
* @throws E if processing fails
62
*/
63
T visit(MultiPoint multiPoint) throws E;
64
65
/**
66
* Visits a MultiPolygon geometry
67
* @param multiPolygon the multi-polygon to process
68
* @return processing result
69
* @throws E if processing fails
70
*/
71
T visit(MultiPolygon multiPolygon) throws E;
72
73
/**
74
* Visits a Point geometry
75
* @param point the point to process
76
* @return processing result
77
* @throws E if processing fails
78
*/
79
T visit(Point point) throws E;
80
81
/**
82
* Visits a Polygon geometry
83
* @param polygon the polygon to process
84
* @return processing result
85
* @throws E if processing fails
86
*/
87
T visit(Polygon polygon) throws E;
88
89
/**
90
* Visits a Rectangle geometry
91
* @param rectangle the rectangle to process
92
* @return processing result
93
* @throws E if processing fails
94
*/
95
T visit(Rectangle rectangle) throws E;
96
}
97
```
98
99
### Geometry Visit Method
100
All geometry objects implement the visit method to accept visitors.
101
102
```java { .api }
103
/**
104
* Accepts a visitor for type-safe processing
105
* @param visitor the geometry visitor to accept
106
* @return result from visitor processing
107
* @throws E exception type from visitor
108
*/
109
<T, E extends Exception> T visit(GeometryVisitor<T, E> visitor) throws E
110
111
// Usage pattern:
112
Geometry geometry = // ... get geometry
113
SomeVisitor visitor = new SomeVisitor();
114
SomeResult result = geometry.visit(visitor);
115
```
116
117
## Common Visitor Implementations
118
119
### Area Calculation Visitor
120
Calculate area/extent of different geometry types.
121
122
```java { .api }
123
public class AreaCalculator implements GeometryVisitor<Double, RuntimeException> {
124
125
@Override
126
public Double visit(Circle circle) {
127
// Calculate circle area: π * r²
128
double radiusMeters = circle.getRadiusMeters();
129
return Math.PI * radiusMeters * radiusMeters;
130
}
131
132
@Override
133
public Double visit(Rectangle rectangle) {
134
// Calculate rectangle area using coordinate differences
135
double width = Math.abs(rectangle.getMaxX() - rectangle.getMinX());
136
double height = Math.abs(rectangle.getMaxY() - rectangle.getMinY());
137
return width * height;
138
}
139
140
@Override
141
public Double visit(Point point) {
142
// Points have zero area
143
return 0.0;
144
}
145
146
@Override
147
public Double visit(Line line) {
148
// Lines have zero area
149
return 0.0;
150
}
151
152
@Override
153
public Double visit(LinearRing ring) {
154
// Rings have zero area (they are boundaries)
155
return 0.0;
156
}
157
158
@Override
159
public Double visit(Polygon polygon) {
160
// Simplified polygon area calculation
161
// In practice, would use proper geometric algorithm
162
LinearRing outerRing = polygon.getPolygon();
163
double area = calculateRingArea(outerRing);
164
165
// Subtract holes
166
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
167
LinearRing hole = polygon.getHole(i);
168
area -= calculateRingArea(hole);
169
}
170
return Math.abs(area);
171
}
172
173
@Override
174
public Double visit(MultiPoint multiPoint) {
175
// Multi-points have zero area
176
return 0.0;
177
}
178
179
@Override
180
public Double visit(MultiLine multiLine) {
181
// Multi-lines have zero area
182
return 0.0;
183
}
184
185
@Override
186
public Double visit(MultiPolygon multiPolygon) {
187
// Sum areas of all polygons
188
double totalArea = 0.0;
189
for (int i = 0; i < multiPolygon.size(); i++) {
190
totalArea += visit(multiPolygon.get(i));
191
}
192
return totalArea;
193
}
194
195
@Override
196
public Double visit(GeometryCollection<?> collection) {
197
// Sum areas of all geometries in collection
198
double totalArea = 0.0;
199
for (int i = 0; i < collection.size(); i++) {
200
totalArea += collection.get(i).visit(this);
201
}
202
return totalArea;
203
}
204
205
private double calculateRingArea(LinearRing ring) {
206
// Simplified area calculation using shoelace formula
207
double area = 0.0;
208
int n = ring.length();
209
for (int i = 0; i < n - 1; i++) {
210
area += (ring.getX(i) * ring.getY(i + 1)) - (ring.getX(i + 1) * ring.getY(i));
211
}
212
return Math.abs(area) / 2.0;
213
}
214
}
215
```
216
217
### Bounding Box Calculator
218
Calculate the minimum bounding rectangle for any geometry.
219
220
```java { .api }
221
public class BoundingBoxCalculator implements GeometryVisitor<Rectangle, RuntimeException> {
222
223
@Override
224
public Rectangle visit(Point point) {
225
if (point.isEmpty()) {
226
return Rectangle.EMPTY;
227
}
228
// Point bounding box is the point itself
229
return new Rectangle(point.getX(), point.getX(), point.getY(), point.getY());
230
}
231
232
@Override
233
public Rectangle visit(Circle circle) {
234
if (circle.isEmpty()) {
235
return Rectangle.EMPTY;
236
}
237
// Convert radius from meters to approximate degrees (simplified)
238
double radiusDegrees = circle.getRadiusMeters() / 111320.0; // rough conversion
239
return new Rectangle(
240
circle.getX() - radiusDegrees,
241
circle.getX() + radiusDegrees,
242
circle.getY() + radiusDegrees,
243
circle.getY() - radiusDegrees
244
);
245
}
246
247
@Override
248
public Rectangle visit(Rectangle rectangle) {
249
// Rectangle is already a bounding box
250
return rectangle;
251
}
252
253
@Override
254
public Rectangle visit(Line line) {
255
if (line.isEmpty()) {
256
return Rectangle.EMPTY;
257
}
258
return calculateBoundsFromCoordinates(line);
259
}
260
261
@Override
262
public Rectangle visit(LinearRing ring) {
263
if (ring.isEmpty()) {
264
return Rectangle.EMPTY;
265
}
266
return calculateBoundsFromCoordinates(ring);
267
}
268
269
@Override
270
public Rectangle visit(Polygon polygon) {
271
if (polygon.isEmpty()) {
272
return Rectangle.EMPTY;
273
}
274
// Use outer ring for bounds (holes don't extend bounds)
275
return visit(polygon.getPolygon());
276
}
277
278
@Override
279
public Rectangle visit(MultiPoint multiPoint) {
280
if (multiPoint.isEmpty()) {
281
return Rectangle.EMPTY;
282
}
283
return calculateCollectionBounds(multiPoint);
284
}
285
286
@Override
287
public Rectangle visit(MultiLine multiLine) {
288
if (multiLine.isEmpty()) {
289
return Rectangle.EMPTY;
290
}
291
return calculateCollectionBounds(multiLine);
292
}
293
294
@Override
295
public Rectangle visit(MultiPolygon multiPolygon) {
296
if (multiPolygon.isEmpty()) {
297
return Rectangle.EMPTY;
298
}
299
return calculateCollectionBounds(multiPolygon);
300
}
301
302
@Override
303
public Rectangle visit(GeometryCollection<?> collection) {
304
if (collection.isEmpty()) {
305
return Rectangle.EMPTY;
306
}
307
return calculateCollectionBounds(collection);
308
}
309
310
private Rectangle calculateBoundsFromCoordinates(Line line) {
311
double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE;
312
double minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE;
313
314
for (int i = 0; i < line.length(); i++) {
315
double x = line.getX(i);
316
double y = line.getY(i);
317
minX = Math.min(minX, x);
318
maxX = Math.max(maxX, x);
319
minY = Math.min(minY, y);
320
maxY = Math.max(maxY, y);
321
}
322
323
return new Rectangle(minX, maxX, maxY, minY);
324
}
325
326
private Rectangle calculateCollectionBounds(GeometryCollection<?> collection) {
327
Rectangle bounds = null;
328
for (int i = 0; i < collection.size(); i++) {
329
Rectangle itemBounds = collection.get(i).visit(this);
330
if (bounds == null) {
331
bounds = itemBounds;
332
} else {
333
bounds = mergeBounds(bounds, itemBounds);
334
}
335
}
336
return bounds != null ? bounds : Rectangle.EMPTY;
337
}
338
339
private Rectangle mergeBounds(Rectangle r1, Rectangle r2) {
340
if (r1.isEmpty()) return r2;
341
if (r2.isEmpty()) return r1;
342
343
return new Rectangle(
344
Math.min(r1.getMinX(), r2.getMinX()),
345
Math.max(r1.getMaxX(), r2.getMaxX()),
346
Math.max(r1.getMaxY(), r2.getMaxY()),
347
Math.min(r1.getMinY(), r2.getMinY())
348
);
349
}
350
}
351
```
352
353
### Geometry Type Counter
354
Count occurrences of different geometry types in collections.
355
356
```java { .api }
357
public class GeometryTypeCounter implements GeometryVisitor<Map<ShapeType, Integer>, RuntimeException> {
358
359
@Override
360
public Map<ShapeType, Integer> visit(Point point) {
361
Map<ShapeType, Integer> counts = new HashMap<>();
362
counts.put(ShapeType.POINT, 1);
363
return counts;
364
}
365
366
@Override
367
public Map<ShapeType, Integer> visit(Circle circle) {
368
Map<ShapeType, Integer> counts = new HashMap<>();
369
counts.put(ShapeType.CIRCLE, 1);
370
return counts;
371
}
372
373
@Override
374
public Map<ShapeType, Integer> visit(Rectangle rectangle) {
375
Map<ShapeType, Integer> counts = new HashMap<>();
376
counts.put(ShapeType.ENVELOPE, 1);
377
return counts;
378
}
379
380
@Override
381
public Map<ShapeType, Integer> visit(Line line) {
382
Map<ShapeType, Integer> counts = new HashMap<>();
383
counts.put(ShapeType.LINESTRING, 1);
384
return counts;
385
}
386
387
@Override
388
public Map<ShapeType, Integer> visit(LinearRing ring) {
389
Map<ShapeType, Integer> counts = new HashMap<>();
390
counts.put(ShapeType.LINEARRING, 1);
391
return counts;
392
}
393
394
@Override
395
public Map<ShapeType, Integer> visit(Polygon polygon) {
396
Map<ShapeType, Integer> counts = new HashMap<>();
397
counts.put(ShapeType.POLYGON, 1);
398
return counts;
399
}
400
401
@Override
402
public Map<ShapeType, Integer> visit(MultiPoint multiPoint) {
403
Map<ShapeType, Integer> counts = new HashMap<>();
404
counts.put(ShapeType.MULTIPOINT, 1);
405
counts.put(ShapeType.POINT, multiPoint.size());
406
return counts;
407
}
408
409
@Override
410
public Map<ShapeType, Integer> visit(MultiLine multiLine) {
411
Map<ShapeType, Integer> counts = new HashMap<>();
412
counts.put(ShapeType.MULTILINESTRING, 1);
413
counts.put(ShapeType.LINESTRING, multiLine.size());
414
return counts;
415
}
416
417
@Override
418
public Map<ShapeType, Integer> visit(MultiPolygon multiPolygon) {
419
Map<ShapeType, Integer> counts = new HashMap<>();
420
counts.put(ShapeType.MULTIPOLYGON, 1);
421
counts.put(ShapeType.POLYGON, multiPolygon.size());
422
return counts;
423
}
424
425
@Override
426
public Map<ShapeType, Integer> visit(GeometryCollection<?> collection) {
427
Map<ShapeType, Integer> totalCounts = new HashMap<>();
428
totalCounts.put(ShapeType.GEOMETRYCOLLECTION, 1);
429
430
for (int i = 0; i < collection.size(); i++) {
431
Map<ShapeType, Integer> itemCounts = collection.get(i).visit(this);
432
for (Map.Entry<ShapeType, Integer> entry : itemCounts.entrySet()) {
433
totalCounts.merge(entry.getKey(), entry.getValue(), Integer::sum);
434
}
435
}
436
return totalCounts;
437
}
438
}
439
```
440
441
## Built-in Visitor Implementations
442
443
The library includes several built-in visitors for common operations:
444
445
### SpatialEnvelopeVisitor
446
Calculates spatial envelopes (bounding boxes) for geometries.
447
448
```java { .api }
449
// Built-in visitor for envelope calculation
450
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
451
452
SpatialEnvelopeVisitor envelopeVisitor = new SpatialEnvelopeVisitor();
453
Rectangle envelope = geometry.visit(envelopeVisitor);
454
```
455
456
## Usage Examples
457
458
```java
459
import org.elasticsearch.geometry.*;
460
import java.util.Map;
461
462
// Create various geometries
463
Point point = new Point(-73.935242, 40.730610);
464
Circle circle = new Circle(-73.945242, 40.740610, 1000.0);
465
List<Geometry> geometries = Arrays.asList(point, circle);
466
GeometryCollection<Geometry> collection = new GeometryCollection<>(geometries);
467
468
// Calculate areas using visitor
469
AreaCalculator areaCalculator = new AreaCalculator();
470
Double pointArea = point.visit(areaCalculator); // 0.0
471
Double circleArea = circle.visit(areaCalculator); // 3141592.653589793
472
Double totalArea = collection.visit(areaCalculator); // sum of all areas
473
474
// Calculate bounding boxes
475
BoundingBoxCalculator boundsCalculator = new BoundingBoxCalculator();
476
Rectangle pointBounds = point.visit(boundsCalculator);
477
Rectangle circleBounds = circle.visit(boundsCalculator);
478
Rectangle collectionBounds = collection.visit(boundsCalculator);
479
480
// Count geometry types
481
GeometryTypeCounter typeCounter = new GeometryTypeCounter();
482
Map<ShapeType, Integer> counts = collection.visit(typeCounter);
483
// Result: {GEOMETRYCOLLECTION=1, POINT=1, CIRCLE=1}
484
485
// Use built-in envelope visitor
486
SpatialEnvelopeVisitor envelopeVisitor = new SpatialEnvelopeVisitor();
487
Rectangle envelope = collection.visit(envelopeVisitor);
488
```
489
490
## Visitor Pattern Benefits
491
492
1. **Type Safety**: Compile-time checking ensures all geometry types are handled
493
2. **Extensibility**: New operations can be added without modifying geometry classes
494
3. **Performance**: Avoids runtime type checking and casting
495
4. **Maintainability**: Operations are centralized in visitor implementations
496
5. **Flexibility**: Different visitors can return different result types