NodeSection.java

/*
 * Copyright (c) 2024 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.relateng;

import java.util.Comparator;

import org.locationtech.jts.algorithm.PolygonNodeTopology;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Dimension;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTWriter;

/**
 * Represents a computed node along with the incident edges on either side of
 * it (if they exist).
 * This captures the information about a node in a geometry component
 * required to determine the component's contribution to the node topology.
 * A node in an area geometry always has edges on both sides of the node.
 * A node in a linear geometry may have one or other incident edge missing, if
 * the node occurs at an endpoint of the line.
 * The edges of an area node are assumed to be provided 
 * with CW-shell orientation (as per JTS norm).
 * This must be enforced by the caller.
 * 
 * @author Martin Davis
 *
 */
class NodeSection implements Comparable<NodeSection>
{
  /**
   * Compares sections by the angle the entering edge makes with the positive X axis.
   */
  public static class EdgeAngleComparator implements Comparator<NodeSection> {

    @Override
    public int compare(NodeSection ns1, NodeSection ns2) {
      return PolygonNodeTopology.compareAngle(ns1.nodePt, ns1.getVertex(0), ns2.getVertex(0));
    }
  }

  public static boolean isAreaArea(NodeSection a, NodeSection b) {
    return a.dimension() == Dimension.A && b.dimension() == Dimension.A;
  }
  
  private boolean isA;
  private int dim;
  private int id;
  private int ringId;
  private boolean isNodeAtVertex;
  private Coordinate nodePt;
  private Coordinate v0;
  private Coordinate v1;
  private Geometry poly;

  public NodeSection(boolean isA, 
      int dimension, int id, int ringId,  
      Geometry poly, boolean isNodeAtVertex, Coordinate v0, Coordinate nodePt, Coordinate v1) {
    this.isA = isA;
    this.dim = dimension;
    this.id = id;
    this.ringId = ringId;
    this.poly = poly;
    this.isNodeAtVertex = isNodeAtVertex;
    this.nodePt = nodePt;
    this.v0 = v0;
    this.v1 = v1;
  }
  
  public Coordinate getVertex(int i) {
    return i == 0 ? v0 : v1;
  }

  public Coordinate nodePt() {
    return nodePt;
  }

  public int dimension() {
    return dim;
  }

  public int id() {
    return id;
  }

  public int ringId() {
    return ringId;
  }
  
  /**
   * Gets the polygon this section is part of.
   * Will be null if section is not on a polygon boundary.
   * 
   * @return the associated polygon, or null
   */
  public Geometry getPolygonal() {
    return poly;
  }
  
  public boolean isShell() {
    return ringId == 0;
  }
  
  public boolean isArea() {
    return dim == Dimension.A;
  }

  public boolean isA() {
     return isA;
  }

  public boolean isSameGeometry(NodeSection ns) {
    return isA() == ns.isA();
  }
  
  public boolean isSamePolygon(NodeSection ns) {
    return isA() == ns.isA() && id() == ns.id();
  }
  
  public boolean isNodeAtVertex() {
    return isNodeAtVertex;
  }

  public boolean isProper() {
    return ! isNodeAtVertex;
  }
  
  public static boolean isProper(NodeSection a, NodeSection b) {
    return a.isProper() && b.isProper();
  }
  
  public String toString() {
    String geomName = RelateGeometry.name(isA);
    String atVertexInd = isNodeAtVertex ? "-V-" : "---";
    String polyId = id >= 0 ? "[" + id + ":" + ringId + "]" : "";
    return String.format("%s%d%s: %s %s %s", 
        geomName, dim, polyId, edgeRep(v0, nodePt), atVertexInd, edgeRep(nodePt, v1));
  }

  private String edgeRep(Coordinate p0, Coordinate p1) {
    if (p0 == null || p1 == null)
      return "null";
    return WKTWriter.toLineString(p0, p1);
  }

  /**
   * Compare node sections by parent geometry, dimension, element id and ring id,
   * and edge vertices.
   * Sections are assumed to be at the same node point.
   */
  @Override
  public int compareTo(NodeSection o) {
    // Assert: nodePt.equals2D(o.nodePt())
    
    // sort A before B
    if (isA != o.isA) {
      if (isA) return -1;
      return 1;
    }
    //-- sort on dimensions
    int compDim = Integer.compare(dim,  o.dim);
    if (compDim != 0) return compDim;

    //-- sort on id and ring id
    int compId = Integer.compare(id, o.id);
    if (compId != 0) return compId;
    
    int compRingId = Integer.compare(ringId, o.ringId);
    if (compRingId != 0) return compRingId;
  
    //-- sort on edge coordinates
    int compV0 = compareWithNull(v0, o.v0);
    if (compV0 != 0) return compV0;
    
    return compareWithNull(v1, o.v1);
  }
  
  private static int compareWithNull(Coordinate v0, Coordinate v1) {
    if (v0 == null) {
      if (v1 == null) 
        return 0;
      //-- null is lower than non-null
      return -1;
    }
    // v0 is non-null
    if (v1 == null) 
      return 1;
    return v0.compareTo(v1);
  }


  
}