Rectangle.java

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

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineSegment;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;

class Rectangle {
  
  /**
   * Creates a rectangular {@link Polygon} from a base segment
   * defining the position and orientation of one side of the rectangle, and 
   * three points defining the locations of the line segments 
   * forming the opposite, left and right sides of the rectangle.
   * The base segment and side points must be presented so that the 
   * rectangle has CW orientation.
   * <p>
   * The rectangle corners are computed as intersections of
   * lines, which generally cannot produce exact values.
   * If a rectangle corner is determined to coincide with a side point
   * the side point value is used to avoid numerical inaccuracy.
   * <p>
   * The first side of the constructed rectangle contains the base segment.
   * 
   * @param baseRightPt the right point of the base segment
   * @param baseLeftPt the left point of the base segment
   * @param oppositePt the point defining the opposite side
   * @param leftSidePt the point defining the left side
   * @param rightSidePt the point defining the right side
   * @param factory the geometry factory to use
   * @return the rectangular polygon
   */
  public static Polygon createFromSidePts(Coordinate baseRightPt, Coordinate baseLeftPt, 
      Coordinate oppositePt, 
      Coordinate leftSidePt, Coordinate rightSidePt, 
      GeometryFactory factory)
  {
    //-- deltas for the base segment provide slope
    double dx = baseLeftPt.x - baseRightPt.x;
    double dy = baseLeftPt.y - baseRightPt.y;
    // Assert: dx and dy are not both zero
    
    double baseC = computeLineEquationC(dx, dy, baseRightPt);
    double oppC = computeLineEquationC(dx, dy, oppositePt);
    double leftC = computeLineEquationC(-dy, dx, leftSidePt);
    double rightC = computeLineEquationC(-dy, dx, rightSidePt);
    
    //-- compute lines along edges of rectangle
    LineSegment baseLine = createLineForStandardEquation(-dy, dx, baseC);
    LineSegment oppLine = createLineForStandardEquation(-dy, dx, oppC);
    LineSegment leftLine = createLineForStandardEquation(-dx, -dy, leftC);
    LineSegment rightLine = createLineForStandardEquation(-dx, -dy, rightC);
    
    /**
     * Corners of rectangle are the intersections of the 
     * base and opposite, and left and right lines.
     * The rectangle is constructed with CW orientation.
     * The first side of the constructed rectangle contains the base segment.
     * 
     * If a corner coincides with a input point
     * the exact value is used to avoid numerical inaccuracy.
     */
    Coordinate p0 = rightSidePt.equals2D(baseRightPt) ? baseRightPt.copy() 
        : baseLine.lineIntersection(rightLine);
    Coordinate p1 = leftSidePt.equals2D(baseLeftPt) ? baseLeftPt.copy() 
        : baseLine.lineIntersection(leftLine);
    Coordinate p2 = leftSidePt.equals2D(oppositePt) ? oppositePt.copy() 
        : oppLine.lineIntersection(leftLine);
    Coordinate p3 = rightSidePt.equals2D(oppositePt) ? oppositePt.copy() 
        : oppLine.lineIntersection(rightLine);
    
    LinearRing shell = factory.createLinearRing(
        new Coordinate[] { p0, p1, p2, p3, p0.copy() });
    return factory.createPolygon(shell);
  }

  /**
   * Computes the constant C in the standard line equation Ax + By = C
   * from A and B and a point on the line.
   * 
   * @param a the X coefficient
   * @param b the Y coefficient
   * @param p a point on the line
   * @return the constant C
   */
  private static double computeLineEquationC(double a, double b, Coordinate p)
  {
    return a * p.y - b * p.x;
  }
  
  private static LineSegment createLineForStandardEquation(double a, double b, double c)
  {
    Coordinate p0;
    Coordinate p1;
    /*
    * Line equation is ax + by = c
    * Slope m = -a/b.
    * Y-intercept = c/b
    * X-intercept = c/a
    * 
    * If slope is low, use constant X values; if high use Y values.
    * This handles lines that are vertical (b = 0, m = Inf ) 
    * and horizontal (a = 0, m = 0).
    */
    if (Math.abs(b) > Math.abs(a)) {
      //-- abs(m) < 1
      p0 = new Coordinate(0.0, c/b);
      p1 = new Coordinate(1.0, c/b - a/b);
    }
    else {
      //-- abs(m) >= 1
      p0 = new Coordinate(c/a, 0.0);
      p1 = new Coordinate(c/a - b/a, 1.0);
    }
    return new LineSegment(p0, p1);
  }
}