Find the intersections between a polygon and a straight line. - Java java.lang

Java examples for java.lang:Math Geometry Line

Description

Find the intersections between a polygon and a straight line.

Demo Code



public class Main{
    /**//  w ww  .  j ava  2s .  c  om
     * Find the intersections between a polygon and a straight line.
     *
     * NOTE: This method is only guaranteed to work if the polygon is first
     * preprocessed so that "unneccesary" vertices are removed (i.e vertices on
     * the straight line between its neighbours).
     *
     * @param x X coordinates of polygon.
     * @param y Y coordinates of polygon.
     * @param x0 X first end point of line.
     * @param x0 Y first end point of line.
     * @param x0 X second end point of line.
     * @param x0 Y second end point of line.
     * @return Intersections [x,y,x,y...].
     */
    public static double[] findLinePolygonIntersections(double[] x,
            double[] y, double x0, double y0, double x1, double y1) {
        double x2, y2, x3, y3;
        double xi, yi;
        int nPoints = x.length;

        int nIntersections = 0;
        double[] intersections = new double[24]; // Result vector x,y,x,y,...
        double[] intersection = new double[2]; // Any given intersection x,y

        for (int i = 0; i < nPoints; i++) {
            int next = i == nPoints - 1 ? 0 : i + 1;

            // The line segment of the polyline to check
            x2 = x[i];
            y2 = y[i];
            x3 = x[next];
            y3 = y[next];

            boolean isIntersecting = false;

            // Ignore segments of zero length
            if (GeometryUtils.equals(x2, x3)
                    && GeometryUtils.equals(y2, y3)) {
                continue;
            }

            int type = GeometryUtils.findLineSegmentIntersection(x0, y0,
                    x1, y1, x2, y2, x3, y3, intersection);

            if (type == -2) { // Overlapping
                int p1 = i == 0 ? nPoints - 1 : i - 1;
                int p2 = next == nPoints - 1 ? 0 : next + 1;

                int side = GeometryUtils.sameSide(x0, y0, x1, y1, x[p1],
                        y[p1], x[p2], y[p2]);

                if (side < 0) {
                    isIntersecting = true;
                }
            } else if (type == 1) {
                isIntersecting = true;
            }

            // Add the intersection point
            if (isIntersecting) {

                // Reallocate if necessary
                if (nIntersections << 1 == intersections.length) {
                    double[] newArray = new double[nIntersections << 2];
                    System.arraycopy(intersections, 0, newArray, 0,
                            intersections.length);
                    intersections = newArray;
                }

                // Then add
                intersections[nIntersections << 1 + 0] = intersection[0];
                intersections[nIntersections << 1 + 1] = intersection[1];

                nIntersections++;
            }
        }

        if (nIntersections == 0) {
            return null;
        }

        // Reallocate result so array match number of intersections
        double[] finalArray = new double[nIntersections << 2];
        System.arraycopy(intersections, 0, finalArray, 0, finalArray.length);

        return finalArray;
    }
    /**
     * Check if two double precision numbers are "equal", i.e. close enough to a
     * given limit.
     *
     * @param a First number to check
     * @param b Second number to check
     * @param limit The definition of "equal".
     * @return True if the twho numbers are "equal", false otherwise
     */
    private static boolean equals(double a, double b, double limit) {
        return Math.abs(a - b) < limit;
    }
    /**
     * Check if two double precision numbers are "equal", i.e. close enough to a
     * prespecified limit.
     *
     * @param a First number to check
     * @param b Second number to check
     * @return True if the twho numbers are "equal", false otherwise
     */
    private static boolean equals(double a, double b) {
        return equals(a, b, 1.0e-5);
    }
    /**
     * Compute the intersection between two line segments, or two lines of
     * infinite length.
     *
     * @param x0 X coordinate first end point first line segment.
     * @param y0 Y coordinate first end point first line segment.
     * @param x1 X coordinate second end point first line segment.
     * @param y1 Y coordinate second end point first line segment.
     * @param x2 X coordinate first end point second line segment.
     * @param y2 Y coordinate first end point second line segment.
     * @param x3 X coordinate second end point second line segment.
     * @param y3 Y coordinate second end point second line segment.
     * @param intersection[2] Preallocated by caller to double[2]
     * @return -1 if lines are parallel (x,y unset), -2 if lines are parallel
     * and overlapping (x, y center) 0 if intesrection outside segments (x,y
     * set) +1 if segments intersect (x,y set)
     */
    public static int findLineSegmentIntersection(double x0, double y0,
            double x1, double y1, double x2, double y2, double x3,
            double y3, double[] intersection) {
        // TODO: Make limit depend on input domain
        final double LIMIT = 1e-5;
        final double INFINITY = 1e10;

        double x, y;

        //
        // Convert the lines to the form y = ax + b
        //

        // Slope of the two lines
        double a0 = GeometryUtils.equals(x0, x1, LIMIT) ? INFINITY
                : (y0 - y1) / (x0 - x1);
        double a1 = GeometryUtils.equals(x2, x3, LIMIT) ? INFINITY
                : (y2 - y3) / (x2 - x3);

        double b0 = y0 - a0 * x0;
        double b1 = y2 - a1 * x2;

        // Check if lines are parallel
        if (GeometryUtils.equals(a0, a1)) {
            if (!GeometryUtils.equals(b0, b1)) {
                return -1; // Parallell non-overlapping
            } else {
                if (GeometryUtils.equals(x0, x1)) {
                    if (Math.min(y0, y1) < Math.max(y2, y3)
                            || Math.max(y0, y1) > Math.min(y2, y3)) {
                        double twoMiddle = y0 + y1 + y2 + y3
                                - GeometryUtils.min(y0, y1, y2, y3)
                                - GeometryUtils.max(y0, y1, y2, y3);
                        y = (twoMiddle) / 2.0;
                        x = (y - b0) / a0;
                    } else {
                        return -1; // Parallell non-overlapping
                    }
                } else {
                    if (Math.min(x0, x1) < Math.max(x2, x3)
                            || Math.max(x0, x1) > Math.min(x2, x3)) {
                        double twoMiddle = x0 + x1 + x2 + x3
                                - GeometryUtils.min(x0, x1, x2, x3)
                                - GeometryUtils.max(x0, x1, x2, x3);
                        x = (twoMiddle) / 2.0;
                        y = a0 * x + b0;
                    } else {
                        return -1;
                    }
                }

                intersection[0] = x;
                intersection[1] = y;
                return -2;
            }
        }

        // Find correct intersection point
        if (GeometryUtils.equals(a0, INFINITY)) {
            x = x0;
            y = a1 * x + b1;
        } else if (GeometryUtils.equals(a1, INFINITY)) {
            x = x2;
            y = a0 * x + b0;
        } else {
            x = -(b0 - b1) / (a0 - a1);
            y = a0 * x + b0;
        }

        intersection[0] = x;
        intersection[1] = y;

        // Then check if intersection is within line segments
        double distanceFrom1;
        if (GeometryUtils.equals(x0, x1)) {
            if (y0 < y1) {
                distanceFrom1 = y < y0 ? GeometryUtils.length(x, y, x0, y0)
                        : y > y1 ? GeometryUtils.length(x, y, x1, y1) : 0.0;
            } else {
                distanceFrom1 = y < y1 ? GeometryUtils.length(x, y, x1, y1)
                        : y > y0 ? GeometryUtils.length(x, y, x0, y0) : 0.0;
            }
        } else {
            if (x0 < x1) {
                distanceFrom1 = x < x0 ? GeometryUtils.length(x, y, x0, y0)
                        : x > x1 ? GeometryUtils.length(x, y, x1, y1) : 0.0;
            } else {
                distanceFrom1 = x < x1 ? GeometryUtils.length(x, y, x1, y1)
                        : x > x0 ? GeometryUtils.length(x, y, x0, y0) : 0.0;
            }
        }

        double distanceFrom2;
        if (GeometryUtils.equals(x2, x3)) {
            if (y2 < y3) {
                distanceFrom2 = y < y2 ? GeometryUtils.length(x, y, x2, y2)
                        : y > y3 ? GeometryUtils.length(x, y, x3, y3) : 0.0;
            } else {
                distanceFrom2 = y < y3 ? GeometryUtils.length(x, y, x3, y3)
                        : y > y2 ? GeometryUtils.length(x, y, x2, y2) : 0.0;
            }
        } else {
            if (x2 < x3) {
                distanceFrom2 = x < x2 ? GeometryUtils.length(x, y, x2, y2)
                        : x > x3 ? GeometryUtils.length(x, y, x3, y3) : 0.0;
            } else {
                distanceFrom2 = x < x3 ? GeometryUtils.length(x, y, x3, y3)
                        : x > x2 ? GeometryUtils.length(x, y, x2, y2) : 0.0;
            }
        }

        return GeometryUtils.equals(distanceFrom1, 0.0)
                && GeometryUtils.equals(distanceFrom2, 0.0) ? 1 : 0;
    }
    /**
     * Check if two points are on the same side of a given line. Algorithm from
     * Sedgewick page 350.
     *
     * @param x0, y0, x1, y1 The line.
     * @param px0, py0 First point.
     * @param px1, py1 Second point.
     * @return <0 if points on opposite sides. =0 if one of the points is
     * exactly on the line >0 if points on same side.
     */
    private static int sameSide(double x0, double y0, double x1, double y1,
            double px0, double py0, double px1, double py1) {
        int sameSide = 0;

        double dx = x1 - x0;
        double dy = y1 - y0;
        double dx1 = px0 - x0;
        double dy1 = py0 - y0;
        double dx2 = px1 - x1;
        double dy2 = py1 - y1;

        // Cross product of the vector from the endpoint of the line to the point
        double c1 = dx * dy1 - dy * dx1;
        double c2 = dx * dy2 - dy * dx2;

        if (c1 != 0 && c2 != 0) {
            sameSide = c1 < 0 != c2 < 0 ? -1 : 1;
        } else if (dx == 0 && dx1 == 0 && dx2 == 0) {
            sameSide = !isBetween(y0, y1, py0) && !isBetween(y0, y1, py1) ? 1
                    : 0;
        } else if (dy == 0 && dy1 == 0 && dy2 == 0) {
            sameSide = !isBetween(x0, x1, px0) && !isBetween(x0, x1, px1) ? 1
                    : 0;
        }

        return sameSide;
    }
    /**
     * Check if two points are on the same side of a given line. Integer domain.
     *
     * @param x0, y0, x1, y1 The line.
     * @param px0, py0 First point.
     * @param px1, py1 Second point.
     * @return <0 if points on opposite sides. =0 if one of the points is
     * exactly on the line >0 if points on same side.
     */
    private static int sameSide(int x0, int y0, int x1, int y1, int px0,
            int py0, int px1, int py1) {
        return sameSide((double) x0, (double) y0, (double) x1, (double) y1,
                (double) px0, (double) py0, (double) px1, (double) py1);
    }
    /**
     * Return smallest of four numbers.
     *
     * @param a First number to find smallest among.
     * @param b Second number to find smallest among.
     * @param c Third number to find smallest among.
     * @param d Fourth number to find smallest among.
     * @return Smallest of a, b, c and d.
     */
    private static double min(double a, double b, double c, double d) {
        return Math.min(Math.min(a, b), Math.min(c, d));
    }
    /**
     * Return largest of four numbers.
     *
     * @param a First number to find largest among.
     * @param b Second number to find largest among.
     * @param c Third number to find largest among.
     * @param d Fourth number to find largest among.
     * @return Largest of a, b, c and d.
     */
    private static double max(double a, double b, double c, double d) {
        return Math.max(Math.max(a, b), Math.max(c, d));
    }
    /**
     * Return the length of a vector.
     *
     * @param v Vector to compute length of [x,y,z].
     * @return Length of vector.
     */
    public static double length(double[] v) {
        return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    }
    /**
     * Compute distance between two points.
     *
     * @param p0, p1 Points to compute distance between [x,y,z].
     * @return Distance between points.
     */
    public static double length(double[] p0, double[] p1) {
        double[] v = GeometryUtils.createVector(p0, p1);
        return length(v);
    }
    /**
     * Compute the length of the line from (x0,y0) to (x1,y1)
     *
     * @param x0, y0 First line end point.
     * @param x1, y1 Second line end point.
     * @return Length of line from (x0,y0) to (x1,y1).
     */
    public static double length(int x0, int y0, int x1, int y1) {
        return GeometryUtils.length((double) x0, (double) y0, (double) x1,
                (double) y1);
    }
    /**
     * Compute the length of the line from (x0,y0) to (x1,y1)
     *
     * @param x0, y0 First line end point.
     * @param x1, y1 Second line end point.
     * @return Length of line from (x0,y0) to (x1,y1).
     */
    public static double length(double x0, double y0, double x1, double y1) {
        double dx = x1 - x0;
        double dy = y1 - y0;

        return Math.sqrt(dx * dx + dy * dy);
    }
    /**
     * Compute the length of a polyline.
     *
     * @param x, y Arrays of x,y coordinates
     * @param nPoints Number of elements in the above.
     * @param isClosed True if this is a closed polygon, false otherwise
     * @return Length of polyline defined by x, y and nPoints.
     */
    public static double length(int[] x, int[] y, boolean isClosed) {
        double length = 0.0;

        int nPoints = x.length;
        for (int i = 0; i < nPoints - 1; i++) {
            length += GeometryUtils.length(x[i], y[i], x[i + 1], y[i + 1]);
        }

        // Add last leg if this is a polygon
        if (isClosed && nPoints > 1) {
            length += GeometryUtils.length(x[nPoints - 1], y[nPoints - 1],
                    x[0], y[0]);
        }

        return length;
    }
    /**
     * Return true if c is between a and b.
     */
    private static boolean isBetween(int a, int b, int c) {
        return b > a ? c >= a && c <= b : c >= b && c <= a;
    }
    /**
     * Return true if c is between a and b.
     */
    private static boolean isBetween(double a, double b, double c) {
        return b > a ? c >= a && c <= b : c >= b && c <= a;
    }
    /**
     * Construct the vector specified by two points.
     *
     * @param p0, p1 Points the construct vector between [x,y,z].
     * @return v Vector from p0 to p1 [x,y,z].
     */
    public static double[] createVector(double[] p0, double[] p1) {
        double v[] = { p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2] };
        return v;
    }
}

Related Tutorials