MetricFunctions.java

/*
 * Copyright (c) 2025 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.jtstest.function;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateSequenceFilter;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jtstest.geomfunction.Metadata;

public class MetricFunctions {
  
  /**
   * Returns a line graph of segment lengths.
   * Graph is scaled to maximum segment length.
   * 
   * @param geom geometry to sample
   * @param numSamples number of points in line graph
   * @return line graph of segment lengths
   */
  public static Geometry segmentLengths(final Geometry geom, 
      @Metadata(title="# of samples")
      int numSamples) {
    
    if (numSamples < 1)
      numSamples = 1;
    
    List<Double> segLen = new ArrayList<Double>();
    CoordinateSequenceFilter segLenFilter = new CoordinateSequenceFilter() {

      @Override
      public void filter(CoordinateSequence seq, int i) {
        if (i == 0) {
          segLen.add(0.0);
          return;
        }
        Coordinate p0 = seq.getCoordinate(i);
        Coordinate p1 = seq.getCoordinate(i-1);
        double len = p0.distance(p1);
        segLen.add(len);
      }

      @Override
      public boolean isDone() {
        return false;
      }

      @Override
      public boolean isGeometryChanged() {
        return false;
      }
      
    };
    geom.apply(segLenFilter);
    Collections.sort(segLen);
    
    double maxLen = segLen.get(segLen.size() - 1);
    Coordinate[] pts = new Coordinate[numSamples + 1];
    int breakSize = segLen.size() / numSamples + 1;
    double dx = maxLen / numSamples;
    for (int i = 0; i < numSamples + 1; i++) {
      
      double x = (i >= numSamples) ? maxLen : i * dx;
      
      int sampleIndex = i * breakSize; 
      if (sampleIndex >= segLen.size())
        sampleIndex = segLen.size() - 1;
      double y = segLen.get(sampleIndex);
      pts[i] = new Coordinate(x, y);
    }
    
    return geom.getFactory().createLineString(pts);
  }
  
  public static double compactness(Geometry poly) {
    double perimeter = poly.getLength();
    double area = poly.getArea();
    if (perimeter <= 0) return 0;
    return Math.abs(area) * Math.PI * 4 / (perimeter * perimeter);
  }
}