GeometryArea.java

/*
 * Copyright (c) 2020 Martin Davis
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jts.operation.overlayarea;

import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFilter;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;

/** 
 * Computes the area of a geometry using the {@link EdgeVector} summing
 * approach. 
 * This provides a validation of the correctness of the {@link OverlayArea} approach.
 * It is not intended to replace the standard polygon area computation.
 * 
 * @author Martin Davis
 *
 */
public class GeometryArea {
  
  public static double area(Geometry geom) {
    GeometryArea area = new GeometryArea(geom);
    return area.getArea();
  }
  
  private Geometry geom;

  public GeometryArea(Geometry geom) {
    this.geom = geom;
  }

  private double getArea() {   
    PolygonAreaFilter filter = new PolygonAreaFilter();
    geom.apply(filter);
    return filter.area;
  }
  
  private class PolygonAreaFilter implements GeometryFilter {
    double area = 0;
    @Override
    public void filter(Geometry geom) {
      if (geom instanceof Polygon) {
        area += areaPolygon((Polygon) geom);
      }
    }
  }
  
  private double areaPolygon(Polygon geom) {
    double area = areaRing(geom.getExteriorRing());
    for (int i = 0; i < geom.getNumInteriorRing(); i++) {
      LinearRing hole = geom.getInteriorRingN(i);
      area -= areaRing(hole);
    }
    return area;
  }
  
  public double areaRing(LinearRing ring) {
    // TODO: handle hole rings, multiPolygons
    CoordinateSequence seq = ring.getCoordinateSequence();
    boolean isCW = ! Orientation.isCCW(seq);
    
    // scan every segment
    double area = 0;
    for (int i = 1; i < seq.size(); i++) {
      int i0 = i - 1;
      int i1 = i;
      /**
       * Sum the partial areas for the two
       * opposing SegmentVectors representing the edge.
       * If the ring is oriented CW then the interior is to the right of the vector,
       * and the opposing vector is opposite.
       */
      area += EdgeVector.area2Term(
          seq.getX(i0), seq.getY(i0),
          seq.getX(i1), seq.getY(i1),
          isCW)
          + EdgeVector.area2Term(
              seq.getX(i1), seq.getY(i1),
              seq.getX(i0), seq.getY(i0),
              ! isCW);
    }
    return area / 2;
  }
}