Elasticsearch geometry library providing core geometric shapes and spatial utility classes for geometric computations and operations.
—
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.
The core interface that defines visit methods for all supported geometry types.
/**
* Generic visitor interface for processing geometry objects
* @param <T> return type of visit operations
* @param <E> exception type that visit methods may throw
*/
public interface GeometryVisitor<T, E extends Exception> {
/**
* Visits a Circle geometry
* @param circle the circle to process
* @return processing result
* @throws E if processing fails
*/
T visit(Circle circle) throws E;
/**
* Visits a GeometryCollection
* @param collection the geometry collection to process
* @return processing result
* @throws E if processing fails
*/
T visit(GeometryCollection<?> collection) throws E;
/**
* Visits a Line geometry
* @param line the line to process
* @return processing result
* @throws E if processing fails
*/
T visit(Line line) throws E;
/**
* Visits a LinearRing geometry
* @param ring the linear ring to process
* @return processing result
* @throws E if processing fails
*/
T visit(LinearRing ring) throws E;
/**
* Visits a MultiLine geometry
* @param multiLine the multi-line to process
* @return processing result
* @throws E if processing fails
*/
T visit(MultiLine multiLine) throws E;
/**
* Visits a MultiPoint geometry
* @param multiPoint the multi-point to process
* @return processing result
* @throws E if processing fails
*/
T visit(MultiPoint multiPoint) throws E;
/**
* Visits a MultiPolygon geometry
* @param multiPolygon the multi-polygon to process
* @return processing result
* @throws E if processing fails
*/
T visit(MultiPolygon multiPolygon) throws E;
/**
* Visits a Point geometry
* @param point the point to process
* @return processing result
* @throws E if processing fails
*/
T visit(Point point) throws E;
/**
* Visits a Polygon geometry
* @param polygon the polygon to process
* @return processing result
* @throws E if processing fails
*/
T visit(Polygon polygon) throws E;
/**
* Visits a Rectangle geometry
* @param rectangle the rectangle to process
* @return processing result
* @throws E if processing fails
*/
T visit(Rectangle rectangle) throws E;
}All geometry objects implement the visit method to accept visitors.
/**
* Accepts a visitor for type-safe processing
* @param visitor the geometry visitor to accept
* @return result from visitor processing
* @throws E exception type from visitor
*/
<T, E extends Exception> T visit(GeometryVisitor<T, E> visitor) throws E
// Usage pattern:
Geometry geometry = // ... get geometry
SomeVisitor visitor = new SomeVisitor();
SomeResult result = geometry.visit(visitor);Calculate area/extent of different geometry types.
public class AreaCalculator implements GeometryVisitor<Double, RuntimeException> {
@Override
public Double visit(Circle circle) {
// Calculate circle area: π * r²
double radiusMeters = circle.getRadiusMeters();
return Math.PI * radiusMeters * radiusMeters;
}
@Override
public Double visit(Rectangle rectangle) {
// Calculate rectangle area using coordinate differences
double width = Math.abs(rectangle.getMaxX() - rectangle.getMinX());
double height = Math.abs(rectangle.getMaxY() - rectangle.getMinY());
return width * height;
}
@Override
public Double visit(Point point) {
// Points have zero area
return 0.0;
}
@Override
public Double visit(Line line) {
// Lines have zero area
return 0.0;
}
@Override
public Double visit(LinearRing ring) {
// Rings have zero area (they are boundaries)
return 0.0;
}
@Override
public Double visit(Polygon polygon) {
// Simplified polygon area calculation
// In practice, would use proper geometric algorithm
LinearRing outerRing = polygon.getPolygon();
double area = calculateRingArea(outerRing);
// Subtract holes
for (int i = 0; i < polygon.getNumberOfHoles(); i++) {
LinearRing hole = polygon.getHole(i);
area -= calculateRingArea(hole);
}
return Math.abs(area);
}
@Override
public Double visit(MultiPoint multiPoint) {
// Multi-points have zero area
return 0.0;
}
@Override
public Double visit(MultiLine multiLine) {
// Multi-lines have zero area
return 0.0;
}
@Override
public Double visit(MultiPolygon multiPolygon) {
// Sum areas of all polygons
double totalArea = 0.0;
for (int i = 0; i < multiPolygon.size(); i++) {
totalArea += visit(multiPolygon.get(i));
}
return totalArea;
}
@Override
public Double visit(GeometryCollection<?> collection) {
// Sum areas of all geometries in collection
double totalArea = 0.0;
for (int i = 0; i < collection.size(); i++) {
totalArea += collection.get(i).visit(this);
}
return totalArea;
}
private double calculateRingArea(LinearRing ring) {
// Simplified area calculation using shoelace formula
double area = 0.0;
int n = ring.length();
for (int i = 0; i < n - 1; i++) {
area += (ring.getX(i) * ring.getY(i + 1)) - (ring.getX(i + 1) * ring.getY(i));
}
return Math.abs(area) / 2.0;
}
}Calculate the minimum bounding rectangle for any geometry.
public class BoundingBoxCalculator implements GeometryVisitor<Rectangle, RuntimeException> {
@Override
public Rectangle visit(Point point) {
if (point.isEmpty()) {
return Rectangle.EMPTY;
}
// Point bounding box is the point itself
return new Rectangle(point.getX(), point.getX(), point.getY(), point.getY());
}
@Override
public Rectangle visit(Circle circle) {
if (circle.isEmpty()) {
return Rectangle.EMPTY;
}
// Convert radius from meters to approximate degrees (simplified)
double radiusDegrees = circle.getRadiusMeters() / 111320.0; // rough conversion
return new Rectangle(
circle.getX() - radiusDegrees,
circle.getX() + radiusDegrees,
circle.getY() + radiusDegrees,
circle.getY() - radiusDegrees
);
}
@Override
public Rectangle visit(Rectangle rectangle) {
// Rectangle is already a bounding box
return rectangle;
}
@Override
public Rectangle visit(Line line) {
if (line.isEmpty()) {
return Rectangle.EMPTY;
}
return calculateBoundsFromCoordinates(line);
}
@Override
public Rectangle visit(LinearRing ring) {
if (ring.isEmpty()) {
return Rectangle.EMPTY;
}
return calculateBoundsFromCoordinates(ring);
}
@Override
public Rectangle visit(Polygon polygon) {
if (polygon.isEmpty()) {
return Rectangle.EMPTY;
}
// Use outer ring for bounds (holes don't extend bounds)
return visit(polygon.getPolygon());
}
@Override
public Rectangle visit(MultiPoint multiPoint) {
if (multiPoint.isEmpty()) {
return Rectangle.EMPTY;
}
return calculateCollectionBounds(multiPoint);
}
@Override
public Rectangle visit(MultiLine multiLine) {
if (multiLine.isEmpty()) {
return Rectangle.EMPTY;
}
return calculateCollectionBounds(multiLine);
}
@Override
public Rectangle visit(MultiPolygon multiPolygon) {
if (multiPolygon.isEmpty()) {
return Rectangle.EMPTY;
}
return calculateCollectionBounds(multiPolygon);
}
@Override
public Rectangle visit(GeometryCollection<?> collection) {
if (collection.isEmpty()) {
return Rectangle.EMPTY;
}
return calculateCollectionBounds(collection);
}
private Rectangle calculateBoundsFromCoordinates(Line line) {
double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE;
double minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE;
for (int i = 0; i < line.length(); i++) {
double x = line.getX(i);
double y = line.getY(i);
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
}
return new Rectangle(minX, maxX, maxY, minY);
}
private Rectangle calculateCollectionBounds(GeometryCollection<?> collection) {
Rectangle bounds = null;
for (int i = 0; i < collection.size(); i++) {
Rectangle itemBounds = collection.get(i).visit(this);
if (bounds == null) {
bounds = itemBounds;
} else {
bounds = mergeBounds(bounds, itemBounds);
}
}
return bounds != null ? bounds : Rectangle.EMPTY;
}
private Rectangle mergeBounds(Rectangle r1, Rectangle r2) {
if (r1.isEmpty()) return r2;
if (r2.isEmpty()) return r1;
return new Rectangle(
Math.min(r1.getMinX(), r2.getMinX()),
Math.max(r1.getMaxX(), r2.getMaxX()),
Math.max(r1.getMaxY(), r2.getMaxY()),
Math.min(r1.getMinY(), r2.getMinY())
);
}
}Count occurrences of different geometry types in collections.
public class GeometryTypeCounter implements GeometryVisitor<Map<ShapeType, Integer>, RuntimeException> {
@Override
public Map<ShapeType, Integer> visit(Point point) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.POINT, 1);
return counts;
}
@Override
public Map<ShapeType, Integer> visit(Circle circle) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.CIRCLE, 1);
return counts;
}
@Override
public Map<ShapeType, Integer> visit(Rectangle rectangle) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.ENVELOPE, 1);
return counts;
}
@Override
public Map<ShapeType, Integer> visit(Line line) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.LINESTRING, 1);
return counts;
}
@Override
public Map<ShapeType, Integer> visit(LinearRing ring) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.LINEARRING, 1);
return counts;
}
@Override
public Map<ShapeType, Integer> visit(Polygon polygon) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.POLYGON, 1);
return counts;
}
@Override
public Map<ShapeType, Integer> visit(MultiPoint multiPoint) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.MULTIPOINT, 1);
counts.put(ShapeType.POINT, multiPoint.size());
return counts;
}
@Override
public Map<ShapeType, Integer> visit(MultiLine multiLine) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.MULTILINESTRING, 1);
counts.put(ShapeType.LINESTRING, multiLine.size());
return counts;
}
@Override
public Map<ShapeType, Integer> visit(MultiPolygon multiPolygon) {
Map<ShapeType, Integer> counts = new HashMap<>();
counts.put(ShapeType.MULTIPOLYGON, 1);
counts.put(ShapeType.POLYGON, multiPolygon.size());
return counts;
}
@Override
public Map<ShapeType, Integer> visit(GeometryCollection<?> collection) {
Map<ShapeType, Integer> totalCounts = new HashMap<>();
totalCounts.put(ShapeType.GEOMETRYCOLLECTION, 1);
for (int i = 0; i < collection.size(); i++) {
Map<ShapeType, Integer> itemCounts = collection.get(i).visit(this);
for (Map.Entry<ShapeType, Integer> entry : itemCounts.entrySet()) {
totalCounts.merge(entry.getKey(), entry.getValue(), Integer::sum);
}
}
return totalCounts;
}
}The library includes several built-in visitors for common operations:
Calculates spatial envelopes (bounding boxes) for geometries.
// Built-in visitor for envelope calculation
import org.elasticsearch.geometry.utils.SpatialEnvelopeVisitor;
SpatialEnvelopeVisitor envelopeVisitor = new SpatialEnvelopeVisitor();
Rectangle envelope = geometry.visit(envelopeVisitor);import org.elasticsearch.geometry.*;
import java.util.Map;
// Create various geometries
Point point = new Point(-73.935242, 40.730610);
Circle circle = new Circle(-73.945242, 40.740610, 1000.0);
List<Geometry> geometries = Arrays.asList(point, circle);
GeometryCollection<Geometry> collection = new GeometryCollection<>(geometries);
// Calculate areas using visitor
AreaCalculator areaCalculator = new AreaCalculator();
Double pointArea = point.visit(areaCalculator); // 0.0
Double circleArea = circle.visit(areaCalculator); // 3141592.653589793
Double totalArea = collection.visit(areaCalculator); // sum of all areas
// Calculate bounding boxes
BoundingBoxCalculator boundsCalculator = new BoundingBoxCalculator();
Rectangle pointBounds = point.visit(boundsCalculator);
Rectangle circleBounds = circle.visit(boundsCalculator);
Rectangle collectionBounds = collection.visit(boundsCalculator);
// Count geometry types
GeometryTypeCounter typeCounter = new GeometryTypeCounter();
Map<ShapeType, Integer> counts = collection.visit(typeCounter);
// Result: {GEOMETRYCOLLECTION=1, POINT=1, CIRCLE=1}
// Use built-in envelope visitor
SpatialEnvelopeVisitor envelopeVisitor = new SpatialEnvelopeVisitor();
Rectangle envelope = collection.visit(envelopeVisitor);Install with Tessl CLI
npx tessl i tessl/maven-org-elasticsearch--elasticsearch-geo