or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-geometry.mdformat-conversion.mdindex.mdmulti-geometry.mdsimplification.mdspatial-utilities.mdvalidation.mdvisitor-pattern.md

visitor-pattern.mddocs/

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