View Javadoc
1   /*******************************************************************************
2    * Copyright (c) 2015 MITRE
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Apache License, Version 2.0 which
5    * accompanies this distribution and is available at
6    *    http://www.apache.org/licenses/LICENSE-2.0.txt
7    ******************************************************************************/
8   
9   package org.locationtech.spatial4j.shape.impl;
10  
11  import org.locationtech.spatial4j.shape.Point;
12  import org.locationtech.spatial4j.shape.Rectangle;
13  import org.locationtech.spatial4j.shape.SpatialRelation;
14  
15  import static org.locationtech.spatial4j.shape.SpatialRelation.*;
16  
17  /**
18   * INERNAL: A buffered line of infinite length.
19   * Public for test access.
20   */
21  public class InfBufLine {
22  
23    /** Error epsilon. */
24    private static final double EPS = 10e-14;
25  
26    //TODO consider removing support for vertical line -- let caller
27    // do something else.  BufferedLine could have a factory method
28    // that returns a rectangle, for example.
29  
30    // line: y = slope * x + intercept
31  
32    private final double slope;//can be infinite for vertical line
33    //if slope is infinite, this is x intercept, otherwise y intercept
34    private final double intercept;
35  
36    private final double buf;
37  
38    private final double distDenomInv;//cached: 1 / Math.sqrt(slope * slope + 1)
39  
40    InfBufLine(double slope, Point point, double buf) {
41      assert !Double.isNaN(slope);
42      this.slope = slope;
43      if (Double.isInfinite(slope)) {
44        intercept = point.getX();
45        distDenomInv = Double.NaN;
46      } else {
47        intercept = point.getY() - slope * point.getX();
48        distDenomInv = 1 / Math.sqrt(slope * slope + 1);
49      }
50      this.buf = buf;
51    }
52  
53    SpatialRelation relate(Rectangle r, Point prC, Point scratch) {
54      assert r.getCenter().equals(prC);
55  
56      int cQuad = quadrant(prC);
57  
58      Point nearestP = scratch;
59      cornerByQuadrant(r, oppositeQuad[cQuad], nearestP);
60      boolean nearestContains = contains(nearestP);
61  
62      if (nearestContains) {
63        Point farthestP = scratch;
64        nearestP = null;//just to be safe (same scratch object)
65        cornerByQuadrant(r, cQuad, farthestP);
66        boolean farthestContains = contains(farthestP);
67        if (farthestContains)
68          return CONTAINS;
69        return INTERSECTS;
70      } else {// not nearestContains
71        if (quadrant(nearestP) == cQuad)
72          return DISJOINT;//out of buffer on same side as center
73        return INTERSECTS;//nearest & farthest points straddle the line
74      }
75    }
76  
77    boolean contains(Point p) {
78      return (distanceUnbuffered(p) <= buf + EPS);
79    }
80  
81    /** INTERNAL AKA lineToPointDistance */
82    public double distanceUnbuffered(Point c) {
83      if (Double.isInfinite(slope))
84        return Math.abs(c.getX() - intercept);
85      // http://math.ucsd.edu/~wgarner/math4c/derivations/distance/distptline.htm
86      double num = Math.abs(c.getY() - slope * c.getX() - intercept);
87      return num * distDenomInv;
88    }
89  
90  //  /** Amount to add or subtract to intercept to indicate where the
91  //   * buffered line edges cross the y axis.
92  //   * @return
93  //   */
94  //  double interceptBuffOffset() {
95  //    if (Double.isInfinite(slope))
96  //      return slope;
97  //    if (buf == 0)
98  //      return 0;
99  //    double slopeDivBuf = slope / buf;
100 //    return Math.sqrt(buf*buf + slopeDivBuf*slopeDivBuf);
101 //  }
102 
103   /** INTERNAL: AKA lineToPointQuadrant */
104   public int quadrant(Point c) {
105     //check vertical line case 1st
106     if (Double.isInfinite(slope)) {
107       //when slope is infinite, intercept is x intercept instead of y
108       return c.getX() > intercept ? 1 : 2; //4 : 3 would work too
109     }
110     //(below will work for slope==0 horizontal line too)
111     //is c above or below the line
112     double yAtCinLine = slope * c.getX() + intercept;
113     boolean above = c.getY() >= yAtCinLine;
114     if (slope > 0) {
115       //if slope is a forward slash, then result is 2 | 4
116       return above ? 2 : 4;
117     } else {
118       //if slope is a backward slash, then result is 1 | 3
119       return above ? 1 : 3;
120     }
121   }
122 
123   //TODO ? Use an Enum for quadrant?
124 
125   /* quadrants 1-4: NE, NW, SW, SE. */
126   private static final int[] oppositeQuad= {-1,3,4,1,2};
127 
128   public static void cornerByQuadrant(Rectangle r, int cornerQuad, Point out) {
129     double x = (cornerQuad == 1 || cornerQuad == 4) ? r.getMaxX() : r.getMinX();
130     double y = (cornerQuad == 1 || cornerQuad == 2) ? r.getMaxY() : r.getMinY();
131     out.reset(x, y);
132   }
133 
134   public double getSlope() {
135     return slope;
136   }
137 
138   public double getIntercept() {
139     return intercept;
140   }
141 
142   public double getBuf() {
143     return buf;
144   }
145 
146   /** 1 / Math.sqrt(slope * slope + 1) */
147   public double getDistDenomInv() {
148     return distDenomInv;
149   }
150 
151   @Override
152   public String toString() {
153     return "InfBufLine{" +
154         "buf=" + buf +
155         ", intercept=" + intercept +
156         ", slope=" + slope +
157         '}';
158   }
159 }