Skip to content

Commit cf86134

Browse files
committed
Added readme
1 parent ccc456b commit cf86134

File tree

1 file changed

+294
-0
lines changed

1 file changed

+294
-0
lines changed

README.MD

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# polybool-java
2+
3+
Java port of [https://github.com/velipso/polybooljs](https://github.com/velipso/polybooljs).
4+
5+
Boolean operations on polygons (union, intersection, difference, xor).
6+
7+
# Features
8+
9+
1. Clips polygons for all boolean operations
10+
2. Removes unnecessary vertices
11+
3. Handles segments that are coincident (overlap perfectly, share vertices, one inside the other,
12+
etc)
13+
4. Uses formulas that take floating point irregularities into account (via configurable epsilon)
14+
5. Provides an API for constructing efficient sequences of operations
15+
6. Support for GeoJSON `"Polygon"` and `"MultiPolygon"` types (experimental)
16+
17+
# Installing
18+
19+
TODO: Complete this section once published to 'central'.
20+
21+
# Example
22+
23+
```java
24+
import com.menecats.polybool.Epsilon;
25+
import com.menecats.polybool.PolyBool;
26+
import com.menecats.polybool.models.Polygon;
27+
28+
import static com.menecats.polybool.helpers.PolyBoolHelper.*;
29+
30+
public class PolyBoolExample {
31+
public static void main(String[] args) {
32+
Epsilon eps = epsilon();
33+
34+
Polygon intersection = PolyBool.intersect(
35+
eps,
36+
polygon(
37+
region(
38+
point(50, 50),
39+
point(150, 150),
40+
point(190, 50)
41+
),
42+
region(
43+
point(130, 50),
44+
point(290, 150),
45+
point(290, 50)
46+
)
47+
),
48+
polygon(
49+
region(
50+
point(110, 20),
51+
point(110, 110),
52+
point(20, 20)
53+
),
54+
region(
55+
point(130, 170),
56+
point(130, 20),
57+
point(260, 20),
58+
point(260, 170)
59+
)
60+
)
61+
);
62+
63+
System.out.println(intersection);
64+
// Polygon { inverted: false, regions: [
65+
// [[50.0, 50.0], [110.0, 50.0], [110.0, 110.0]],
66+
// [[178.0, 80.0], [130.0, 50.0], [130.0, 130.0], [150.0, 150.0]],
67+
// [[178.0, 80.0], [190.0, 50.0], [260.0, 50.0], [260.0, 131.25]]
68+
// ]}
69+
}
70+
}
71+
```
72+
73+
![PolyBool Example](https://github.com/voidqk/polybooljs/raw/master/example.png)
74+
75+
## Basic Usage
76+
77+
```java
78+
Epsilon eps = new Epsilon();
79+
80+
Polygon poly = PolyBool.union(eps, poly1, poly2);
81+
Polygon poly = PolyBool.intersect(eps, poly1, poly2);
82+
Polygon poly = PolyBool.difference(eps, poly1, poly2); // poly1 - poly2
83+
Polygon poly = PolyBool.differenceRev(eps, poly1, poly2); // poly2 - poly1
84+
Polygon poly = PolyBool.xor(eps, poly1, poly2);
85+
```
86+
87+
Where `poly1`, `poly2`, and the return value are `Polygon` objects.
88+
89+
# GeoJSON (experimental)
90+
91+
There are also functions for converting between the native polygon format and
92+
[GeoJSON](https://tools.ietf.org/html/rfc7946).
93+
94+
Note: These functions are currently **experimental**, and I'm hoping users can provide feedback.
95+
Please comment in [this issue on GitHub](https://github.com/voidqk/polybooljs/issues/7) -- including
96+
letting me know if it's working as expected. I don't use GeoJSON, but I thought I would take a
97+
crack at conversion functions.
98+
99+
Use the following functions:
100+
101+
```java
102+
Geometry<?> geojson = PolyBool.polygonToGeoJSON(poly);
103+
Polygon poly = PolyBool.polygonFromGeoJSON(geojson);
104+
```
105+
106+
Only `"Polygon"` and `"MultiPolygon"` types are supported.
107+
108+
# Core API
109+
110+
```java
111+
Epsilon eps = new Epsilon();
112+
113+
Segments segments = PolyBool.segments(eps, polygon);
114+
Combined combined = PolyBool.combine(eps, segments1, segments2);
115+
116+
Segments segments = PolyBool.selectUnion(combined);
117+
Segments segments = PolyBool.selectIntersect(combined);
118+
Segments segments = PolyBool.selectDifference(combined);
119+
Segments segments = PolyBool.selectDifferenceRev(combined);
120+
Segments segments = PolyBool.selectXor(combined);
121+
122+
Polygon polygon = PolyBool.polygon(eps, segments);
123+
```
124+
125+
Depending on your needs, it might be more efficient to construct your own sequence of operations
126+
using the lower-level API. Note that `PolyBool.union`, `PolyBool.intersect`, etc, are just thin
127+
wrappers for convenience.
128+
129+
There are three types of objects you will encounter in the core API:
130+
131+
1. Polygons (discussed above, this is a list of regions and an `inverted` flag)
132+
2. Segments
133+
3. Combined Segments
134+
135+
The basic flow chart of the API is:
136+
137+
![PolyBool API Flow Chart](https://github.com/voidqk/polybooljs/raw/master/flowchart.png)
138+
139+
You start by converting Polygons to Segments using `PolyBool.segments(eps, poly)`.
140+
141+
You convert Segments to Combined Segments using `PolyBool.combine(eps, seg1, seg2)`.
142+
143+
You select the resulting Segments from the Combined Segments using one of the selection operators
144+
`PolyBool.selectUnion(combined)`, `PolyBool.selectIntersect(combined)`, etc. These selection
145+
functions return Segments.
146+
147+
Once you're done, you convert the Segments back to Polygons using `PolyBool.polygon(eps, segments)`.
148+
149+
Each transition is costly, so you want to navigate wisely. The selection transition is the least
150+
costly.
151+
152+
## Advanced Example 1
153+
154+
Suppose you wanted to union a list of polygons together. The naive way to do it would be:
155+
156+
```java
157+
// works but not efficient
158+
159+
Polygon result = polygons[0];
160+
for (int i = 1; i < polygons.length; i++)
161+
result = PolyBool.union(eps, result, polygons[i]);
162+
163+
return result;
164+
```
165+
166+
Instead, it's more efficient to use the core API directly, like this:
167+
168+
```java
169+
// works AND efficient
170+
Segments segments = PolyBool.segments(eps, polygons[0]);
171+
for (int i = 1; i < polygons.length; i++) {
172+
Segments seg2 = PolyBool.segments(eps, polygons[i]);
173+
Combined comb = PolyBool.combine(eps, segments, seg2);
174+
segments = PolyBool.selectUnion(comb);
175+
}
176+
return PolyBool.polygon(eps, segments);
177+
```
178+
179+
## Advanced Example 2
180+
181+
Suppose you want to calculate all operations on two polygons. The naive way to do it would be:
182+
183+
```java
184+
// works but not efficient
185+
Map<String, Polygon> ops = new HashMap<>();
186+
187+
ops.put("union", PolyBool.union (eps, poly1, poly2));
188+
ops.put("intersect", PolyBool.intersect (eps, poly1, poly2));
189+
ops.put("difference", PolyBool.difference (eps, poly1, poly2));
190+
ops.put("differenceRev", PolyBool.differenceRev(eps, poly1, poly2));
191+
ops.put("xor", PolyBool.xor (eps, poly1, poly2));
192+
193+
return operations;
194+
```
195+
196+
Instead, it's more efficient to use the core API directly, like this:
197+
198+
```java
199+
// works AND efficient
200+
Segments seg1 = PolyBool.segments(eps, poly1);
201+
Segments seg2 = PolyBool.segments(eps, poly2);
202+
Combined comb = PolyBool.combine(eps, seg1, seg2);
203+
204+
Map<String, Polygon> ops= new HashMap<>();
205+
206+
ops.put("union", PolyBool.polygon(eps, PolyBool.selectUnion (eps, poly1, poly2)));
207+
ops.put("intersect", PolyBool.polygon(eps, PolyBool.selectIntersect (eps, poly1, poly2)));
208+
ops.put("difference", PolyBool.polygon(eps, PolyBool.selectDifference (eps, poly1, poly2)));
209+
ops.put("differenceRev", PolyBool.polygon(eps, PolyBool.selectDifferenceRev(eps, poly1, poly2)));
210+
ops.put("xor", PolyBool.polygon(eps, PolyBool.selectXor (eps, poly1, poly2)));
211+
212+
return ops;
213+
```
214+
215+
## Advanced Example 3
216+
217+
As an added bonus, just going from Polygon to Segments and back performs simplification on the
218+
polygon.
219+
220+
Suppose you have garbage polygon data and just want to clean it up. The naive way to do it would
221+
be:
222+
223+
```java
224+
// union the polygon with nothing in order to clean up the data
225+
// works but not efficient
226+
Polygon cleaned = PolyBool.union(eps, polygon, new Polygon());
227+
```
228+
229+
Instead, skip the combination and selection phase:
230+
231+
```java
232+
// works AND efficient
233+
Polygon cleaned = PolyBool.polygon(eps, PolyBool.segments(eps, polygon));
234+
```
235+
236+
# Epsilon
237+
238+
Due to the beauty of floating point reality, floating point calculations are not exactly perfect.
239+
This is a problem when trying to detect whether lines are on top of each other, or if vertices are
240+
exactly the same.
241+
242+
Normally you would expect this to work:
243+
244+
```java
245+
if (A == B) {
246+
/* A and B are equal */;
247+
} else{
248+
/* A and B are not equal */;
249+
}
250+
```
251+
252+
But for inexact floating point math, instead we use:
253+
254+
```java
255+
if (Math.abs(A - B) < epsilon) {
256+
/* A and B are equal */;
257+
} else {
258+
/* A and B are not equal */;
259+
}
260+
```
261+
262+
You can set the epsilon while you invoke polybool functions by creating an `Epsilon` instance
263+
264+
```java
265+
Epsilon eps = new Epsilon();
266+
267+
PolyBool.segments(eps, poly);
268+
```
269+
270+
You can specify a custom epsilon value while you instantiate an `Epsilon` object or you can change it on an existing one
271+
272+
```java
273+
Epsilon eps = new Epsilon(myCustomEpsilonValue);
274+
275+
eps.epsilon(anotherCustomEpsilonValue);
276+
277+
PolyBool.segments(eps, poly);
278+
```
279+
280+
The default epsilon value is `0.0000000001 (1e-10)`.
281+
282+
If your polygons are really really large or really really tiny, then you will probably have to come
283+
up with your own epsilon value -- otherwise, the default should be fine.
284+
285+
If `PolyBool` detects that your epsilon is too small or too large, it will throw an error:
286+
287+
```
288+
PolyBool: Zero-length segment detected; your epsilon is probably too small or too large
289+
```
290+
291+
## Experimental Epsilon changes
292+
293+
There is an `ExperimentalEpsilon` class that implements some experimantal changes from the
294+
PR [#8](https://github.com/velipso/polybooljs/pull/8) that aims to fix some bugs, but is not fully tested.

0 commit comments

Comments
 (0)