CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-elasticsearch--elasticsearch-geo

Elasticsearch geometry library providing core geometric shapes and spatial utility classes for geometric computations and operations.

Pending
Overview
Eval results
Files

visitor-pattern.mddocs/

Visitor Pattern Processing

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.

Capabilities

GeometryVisitor Interface

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;
}

Geometry Visit Method

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);

Common Visitor Implementations

Area Calculation 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;
    }
}

Bounding Box Calculator

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())
        );
    }
}

Geometry Type Counter

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;
    }
}

Built-in Visitor Implementations

The library includes several built-in visitors for common operations:

SpatialEnvelopeVisitor

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);

Usage Examples

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);

Visitor Pattern Benefits

  1. Type Safety: Compile-time checking ensures all geometry types are handled
  2. Extensibility: New operations can be added without modifying geometry classes
  3. Performance: Avoids runtime type checking and casting
  4. Maintainability: Operations are centralized in visitor implementations
  5. Flexibility: Different visitors can return different result types

Install with Tessl CLI

npx tessl i tessl/maven-org-elasticsearch--elasticsearch-geo

docs

core-geometry.md

format-conversion.md

index.md

multi-geometry.md

simplification.md

spatial-utilities.md

validation.md

visitor-pattern.md

tile.json