I need to get rid of self-intersections in a shape. Shape is constructed from an array of points, so all segments of that shape are lines. (only lines, no curves and arcs
If Area
is not working for you, you could try using a GLUtessellator to decompose your Shape
into a set of triangles, or (using the GL_LINE_LOOP
option) just the boundary edges.
So, since I was unable to find anything like this on the web, I written my own algorithm.
It may be insanely ineffective, but it works fast enough for me.
Here it goes:
/**
* Takes a polygon, defined by a list of lines, and splits it into several
* paths on points of intersection. If non-self-intersected path is passed in,
* the same path is returned.
* @param path
* @return
*/
public static List<List<Line2D>> splitPath(List<Line2D> lines) {
List<List<Line2D>> splitted = new ArrayList<List<Line2D>>();
// find intersections.
loop1:
for (Line2D l1 : lines) {
for (Line2D l2 : lines) {
if (l1 == l2) continue;
Point2D intr;
if ((intr = linesIntersect(l1, l2)) != null) {
// creating two splitted subpaths
int i1 = lines.indexOf(l1);
int i2 = lines.indexOf(l2);
List<Line2D> subpath1 = new ArrayList<Line2D>();
subpath1.addAll(lines.subList(0, i1));
subpath1.add(new Line2D.Double(l1.getP1(), intr));
subpath1.add(new Line2D.Double(intr, l2.getP2()));
subpath1.addAll(lines.subList(i2 + 1, lines.size()));
splitted.addAll(splitPath(subpath1));
List<Line2D> subpath2 = new ArrayList<Line2D>();
subpath2.add(new Line2D.Double(intr, l1.getP2()));
subpath2.addAll(lines.subList(i1 + 1, i2));
subpath2.add(new Line2D.Double(l2.getP1(), intr));
splitted.addAll(splitPath(subpath2));
break loop1;
}
}
}
if (splitted.size() > 0) {
return splitted;
} else {
return Collections.singletonList(lines);
}
}
/**
* Returns point of intersection of this line segments.
* @param l1
* @param l2
* @return
*/
public static Point2D linesIntersect(Line2D l1, Line2D l2) {
if (l1.getP1().equals(l2.getP2()) || l1.getP2().equals(l2.getP1())) return null;
Point2D inter = lineIntersection(l1, l2);
if (inter == null) return null;
double infS = HEADER.infS;
double x = inter.getX();
if (((l1.getX1() > l1.getX2()) ? (x + infS > l1.getX2() && x - infS < l1.getX1()) : (x - infS < l1.getX2() && x + infS > l1.getX1())) &&
((l2.getX1() > l2.getX2()) ? (x + infS > l2.getX2() && x - infS < l2.getX1()) : (x - infS < l2.getX2() && x + infS > l2.getX1()))) {
return inter;
} else {
return null;
}
}
/**
* Returns point of lines intersection, or null if they are parallel.
* @param l1
* @param l2
* @return
*/
public static Point2D lineIntersection(Line2D l1, Line2D l2) {
double a1 = l1.getY2() - l1.getY1();
double b1 = l1.getX1() - l1.getX2();
double c1 = a1*l1.getX1() + b1*l1.getY1();
double a2 = l2.getY2() - l2.getY1();
double b2 = l2.getX1() - l2.getX2();
double c2 = a2*l2.getX1() + b2*l2.getY1();
double det = a1*b2 - a2*b1;
if (det != 0) {
double x = (b2*c1 - b1*c2)/det;
double y = (a1*c2 - a2*c1)/det;
return new Point2D.Double(x, y);
} else {
// lines are parallel
return null;
}
}
I bookmarked your question/answer in case I had to implement something similar myself, but then I found the GEOS project which has a simple way of achieving this. I'm calling GEOS from Python/Django, but the whole thing is based on JTS (Java Topology Suite) so I'd start there and treat the following Python as psuedo-code.
Basically, the UNION operation will split a line into simply connected parts if it is not simply connected (explained here), so UNIONing the line with it's first point does what we need:
line = LineString(list_of_lines_x_y_coordinates)
# union with first point splits into MultiLineString containing segments
segments = line.union(line[0])