code: /*******************************************************************************
   * Copyright (c) 2015 MITRE
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Apache License, Version 2.0 which
   * accompanies this distribution and is available at
   *    http://www.apache.org/licenses/LICENSE-2.0.txt
   ******************************************************************************/
  
  package org.locationtech.spatial4j.shape.impl;
  
  import org.locationtech.spatial4j.context.SpatialContext;
  import org.locationtech.spatial4j.distance.DistanceUtils;
  import org.locationtech.spatial4j.shape.BaseShape;
  import org.locationtech.spatial4j.shape.Point;
  import org.locationtech.spatial4j.shape.Rectangle;
  import org.locationtech.spatial4j.shape.Shape;
  import org.locationtech.spatial4j.shape.SpatialRelation;
  
  import static org.locationtech.spatial4j.shape.SpatialRelation.CONTAINS;
  import static org.locationtech.spatial4j.shape.SpatialRelation.DISJOINT;
  import static org.locationtech.spatial4j.shape.SpatialRelation.INTERSECTS;
  import static org.locationtech.spatial4j.shape.SpatialRelation.WITHIN;
  
  /**
   * INTERNAL: A line between two points with a buffer distance extending in every direction. By
   * contrast, an un-buffered line covers no area and as such is extremely unlikely to intersect with
   * a point. BufferedLine isn't yet aware of geodesics (e.g. the anti-meridian); it operates in Euclidean
   * space.
   */
  public class BufferedLine extends BaseShape<SpatialContext> {
  
    private final Point pA, pB;
    private final double buf;
    private final Rectangle bbox;
    /**
     * the primary line; passes through pA & pB
     */
    private final InfBufLine linePrimary;
    /**
     * perpendicular to the primary line, centered between pA & pB
     */
    private final InfBufLine linePerp;
  
    /**
     * Creates a buffered line from pA to pB. The buffer extends on both sides of
     * the line, making the width 2x the buffer. The buffer extends out from
     * pA &amp; pB, making the line in effect 2x the buffer longer than pA to pB.
     *
     * @param pA  start point
     * @param pB  end point
     * @param buf the buffer distance in degrees
     */
    public BufferedLine(Point pA, Point pB, double buf, SpatialContext ctx) {
      super(ctx);
      assert buf >= 0;//TODO support buf=0 via another class ?
  
      /**
       * If true, buf should bump-out from the pA & pB, in effect
       *                  extending the line a little.
       */
      final boolean bufExtend = true;//TODO support false and make this a
      // parameter
  
      this.pA = pA;
      this.pB = pB;
      this.buf = buf;
  
      double deltaY = pB.getY() - pA.getY();
      double deltaX = pB.getX() - pA.getX();
  
      PointImpl center = new PointImpl(pA.getX() + deltaX / 2,
          pA.getY() + deltaY / 2, null);
  
      double perpExtent = bufExtend ? buf : 0;
  
      if (deltaX == 0 && deltaY == 0) {
        linePrimary = new InfBufLine(0, center, buf);
        linePerp = new InfBufLine(Double.POSITIVE_INFINITY, center, buf);
      } else {
        linePrimary = new InfBufLine(deltaY / deltaX, center, buf);
        double length = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
        linePerp = new InfBufLine(-deltaX / deltaY, center,
            length / 2 + perpExtent);
      }
  
      double minY, maxY;
      double minX, maxX;
      if (deltaX == 0) { // vertical
        if (pA.getY() <= pB.getY()) {
          minY = pA.getY();
          maxY = pB.getY();
        } else {
          minY = pB.getY();
          maxY = pA.getY();
        }
        minX = pA.getX() - buf;
        maxX = pA.getX() + buf;
        minY = minY - perpExtent;
        maxY = maxY + perpExtent;
  
      } else {
        if (!bufExtend) {
          throw new UnsupportedOperationException("TODO");
          //solve for B & A (C=buf), one is buf-x, other is buf-y.
        }
  
        //Given a right triangle of A, B, C sides, C (hypotenuse) ==
        // buf, and A + B == the bounding box offset from pA & pB in x & y.
        double bboxBuf = buf * (1 + Math.abs(linePrimary.getSlope()))
            * linePrimary.getDistDenomInv();
        assert bboxBuf >= buf && bboxBuf <= buf * 1.5;
  
        if (pA.getX() <= pB.getX()) {
          minX = pA.getX() - bboxBuf;
          maxX = pB.getX() + bboxBuf;
        } else {
          minX = pB.getX() - bboxBuf;
          maxX = pA.getX() + bboxBuf;
        }
        if (pA.getY() <= pB.getY()) {
          minY = pA.getY() - bboxBuf;
          maxY = pB.getY() + bboxBuf;
        } else {
          minY = pB.getY() - bboxBuf;
          maxY = pA.getY() + bboxBuf;
        }
  
      }
      Rectangle bounds = ctx.getWorldBounds();
  
      bbox = ctx.makeRectangle(
          Math.max(bounds.getMinX(), minX),
          Math.min(bounds.getMaxX(), maxX),
          Math.max(bounds.getMinY(), minY),
          Math.min(bounds.getMaxY(), maxY));
    }
  
    @Override
    public boolean isEmpty() {
      return pA.isEmpty();
    }
  
    @Override
    public Shape getBuffered(double distance, SpatialContext ctx) {
      return new BufferedLine(pA, pB, buf + distance, ctx);
    }
  
    /**
     * Calls {@link DistanceUtils#calcLonDegreesAtLat(double, double)} given pA or pB's latitude;
     * whichever is farthest. It's useful to expand a buffer of a line segment when used in
     * a geospatial context to cover the desired area.
     */
    public static double expandBufForLongitudeSkew(Point pA, Point pB,
                                                   double buf) {
      double absA = Math.abs(pA.getY());
      double absB = Math.abs(pB.getY());
      double maxLat = Math.max(absA, absB);
      double newBuf = DistanceUtils.calcLonDegreesAtLat(maxLat, buf);
  //    if (newBuf + maxLat >= 90) {
  //      //TODO substitute spherical cap ?
  //    }
      assert newBuf >= buf;
      return newBuf;
    }
  
    @Override
    public SpatialRelation relate(Shape other) {
      if (other instanceof Point)
        return contains((Point) other) ? CONTAINS : DISJOINT;
      if (other instanceof Rectangle)
        return relate((Rectangle) other);
      throw new UnsupportedOperationException();
    }
  
    public SpatialRelation relate(Rectangle r) {
      //Check BBox for disjoint & within.
      SpatialRelation bboxR = bbox.relate(r);
      if (bboxR == DISJOINT || bboxR == WITHIN)
        return bboxR;
      //Either CONTAINS, INTERSECTS, or DISJOINT
  
      Point scratch = new PointImpl(0, 0, null);
      Point prC = r.getCenter();
      SpatialRelation result = linePrimary.relate(r, prC, scratch);
      if (result == DISJOINT)
        return DISJOINT;
      SpatialRelation resultOpp = linePerp.relate(r, prC, scratch);
      if (resultOpp == DISJOINT)
        return DISJOINT;
      if (result == resultOpp)//either CONTAINS or INTERSECTS
        return result;
      return INTERSECTS;
    }
  
    public boolean contains(Point p) {
      //TODO check bbox 1st?
      return linePrimary.contains(p) && linePerp.contains(p);
    }
  
    public Rectangle getBoundingBox() {
      return bbox;
    }
  
    @Override
    public boolean hasArea() {
      return buf > 0;
    }
  
    @Override
    public double getArea(SpatialContext ctx) {
      return linePrimary.getBuf() * linePerp.getBuf() * 4;
    }
  
    @Override
    public Point getCenter() {
      return getBoundingBox().getCenter();
    }
  
    public Point getA() {
      return pA;
    }
  
    public Point getB() {
      return pB;
    }
  
    public double getBuf() {
      return buf;
    }
  
    /**
     * INTERNAL
     */
    public InfBufLine getLinePrimary() {
      return linePrimary;
    }
  
    /**
     * INTERNAL
     */
    public InfBufLine getLinePerp() {
      return linePerp;
    }
  
    @Override
    public String toString() {
      return "BufferedLine(" + pA + ", " + pB + " b=" + buf + ")";
    }
  
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
  
      BufferedLine that = (BufferedLine) o;
  
      if (Double.compare(that.buf, buf) != 0) return false;
      if (!pA.equals(that.pA)) return false;
      if (!pB.equals(that.pB)) return false;
  
      return true;
    }
  
    @Override
    public int hashCode() {
      int result;
      long temp;
      result = pA.hashCode();
      result = 31 * result + pB.hashCode();
      temp = buf != +0.0d ? Double.doubleToLongBits(buf) : 0L;
      result = 31 * result + (int) (temp ^ (temp >>> 32));
      return result;
    }
  }
  