package com.github.davidmoten.rtree.geometry; import java.awt.geom.Line2D; import java.awt.geom.Line2D.Float; import com.github.davidmoten.guavamini.Objects; import com.github.davidmoten.guavamini.Optional; import com.github.davidmoten.rtree.internal.util.ObjectsHelper; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineSegment; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.operation.predicate.RectangleIntersects; import com.vividsolutions.jts.util.GeometricShapeFactory; /** * A line segment. */ public final class Line implements Geometry { private final float x1; private final float y1; private final float x2; private final float y2; private Line(float x1, float y1, float x2, float y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } static Line create(float x1, float y1, float x2, float y2) { return new Line(x1, y1, x2, y2); } static Line create(double x1, double y1, double x2, double y2) { return new Line((float) x1, (float) y1, (float) x2, (float) y2); } @Override public double distance(Rectangle r) { if (r.contains(x1, y1) || r.contains(x2, y2)) { return 0; } else { double d1 = distance(r.x1(), r.y1(), r.x1(), r.y2()); if (d1 == 0) return 0; double d2 = distance(r.x1(), r.y2(), r.x2(), r.y2()); if (d2 == 0) return 0; double d3 = distance(r.x2(), r.y2(), r.x2(), r.y1()); double d4 = distance(r.x2(), r.y1(), r.x1(), r.y1()); return Math.min(d1, Math.min(d2, Math.min(d3, d4))); } } private double distance(float x1, float y1, float x2, float y2) { Float line = new Line2D.Float(x1, y1, x2, y2); double d1 = line.ptSegDist(this.x1, this.y1); double d2 = line.ptSegDist(this.x2, this.y2); Float line2 = new Line2D.Float(this.x1, this.y1, this.x2, this.y2); double d3 = line2.ptSegDist(x1, y1); if (d3 == 0) return 0; double d4 = line2.ptSegDist(x2, y2); if (d4 == 0) return 0; else return Math.min(d1, Math.min(d2, Math.min(d3, d4))); } @Override public Rectangle mbr() { return Geometries.rectangle(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2)); } @Override public boolean intersects(Rectangle r) { GeometryFactory gf = new GeometryFactory(); GeometricShapeFactory f = new GeometricShapeFactory(gf); f.setBase(new Coordinate(r.x1(), r.y1())); f.setWidth(r.x2() - r.x1()); f.setHeight(r.y2() - r.y1()); Polygon rect = f.createRectangle(); LineSegment line = new LineSegment(x1, y1, x2, y2); return RectangleIntersects.intersects(rect, line.toGeometry(gf)); } public float x1() { return x1; } public float y1() { return y1; } public float x2() { return x2; } public float y2() { return y2; } public boolean intersects(Line b) { Line2D line1 = new Line2D.Float(x1, y1, x2, y2); Line2D line2 = new Line2D.Float(b.x1(), b.y1(), b.x2(), b.y2()); return line2.intersectsLine(line1); } public boolean intersects(Point point) { return intersects(point.mbr()); } public boolean intersects(Circle circle) { // using Vector Projection // https://en.wikipedia.org/wiki/Vector_projection Vector c = Vector.create(circle.x(), circle.y()); Vector a = Vector.create(x1, y1); Vector cMinusA = c.minus(a); float radiusSquared = circle.radius() * circle.radius(); if (x1 == x2 && y1 == y2) { return cMinusA.modulusSquared() <= radiusSquared; } else { Vector b = Vector.create(x2, y2); Vector bMinusA = b.minus(a); float bMinusAModulus = bMinusA.modulus(); float lambda = cMinusA.dot(bMinusA) / bMinusAModulus; // if projection is on the segment if (lambda >= 0 && lambda <= bMinusAModulus) { Vector dMinusA = bMinusA.times(lambda / bMinusAModulus); // calculate distance to line from c using pythagoras' theorem return cMinusA.modulusSquared() - dMinusA.modulusSquared() <= radiusSquared; } else { // return true if and only if an endpoint is within radius of // centre return cMinusA.modulusSquared() <= radiusSquared || c.minus(b).modulusSquared() <= radiusSquared; } } } @Override public int hashCode() { return Objects.hashCode(x1, y1, x2, y2); } @Override public boolean equals(Object obj) { Optional<Line> other = ObjectsHelper.asClass(obj, Line.class); if (other.isPresent()) { return Objects.equal(x1, other.get().x1) && Objects.equal(x2, other.get().x2) && Objects.equal(y1, other.get().y1) && Objects.equal(y2, other.get().y2); } else return false; } private static final class Vector { final float x; final float y; static Vector create(float x, float y) { return new Vector(x, y); } Vector(float x, float y) { this.x = x; this.y = y; } float dot(Vector v) { return x * v.x + y * v.y; } Vector times(float value) { return create(value * x, value * y); } Vector minus(Vector v) { return create(x - v.x, y - v.y); } float modulus() { return (float) Math.sqrt(x * x + y * y); } float modulusSquared() { return x * x + y * y; } } }