deoverlap 3.2.2__tar.gz → 4.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,210 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "byteorder"
13
+ version = "1.5.0"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
16
+
17
+ [[package]]
18
+ name = "deoverlap-core"
19
+ version = "4.1.0"
20
+ dependencies = [
21
+ "rstar",
22
+ ]
23
+
24
+ [[package]]
25
+ name = "deoverlap-python"
26
+ version = "4.1.0"
27
+ dependencies = [
28
+ "deoverlap-core",
29
+ "pyo3",
30
+ ]
31
+
32
+ [[package]]
33
+ name = "hash32"
34
+ version = "0.3.1"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
37
+ dependencies = [
38
+ "byteorder",
39
+ ]
40
+
41
+ [[package]]
42
+ name = "heapless"
43
+ version = "0.8.0"
44
+ source = "registry+https://github.com/rust-lang/crates.io-index"
45
+ checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
46
+ dependencies = [
47
+ "hash32",
48
+ "stable_deref_trait",
49
+ ]
50
+
51
+ [[package]]
52
+ name = "heck"
53
+ version = "0.5.0"
54
+ source = "registry+https://github.com/rust-lang/crates.io-index"
55
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
56
+
57
+ [[package]]
58
+ name = "libc"
59
+ version = "0.2.189"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
62
+
63
+ [[package]]
64
+ name = "libm"
65
+ version = "0.2.16"
66
+ source = "registry+https://github.com/rust-lang/crates.io-index"
67
+ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
68
+
69
+ [[package]]
70
+ name = "num-traits"
71
+ version = "0.2.19"
72
+ source = "registry+https://github.com/rust-lang/crates.io-index"
73
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
74
+ dependencies = [
75
+ "autocfg",
76
+ "libm",
77
+ ]
78
+
79
+ [[package]]
80
+ name = "once_cell"
81
+ version = "1.21.4"
82
+ source = "registry+https://github.com/rust-lang/crates.io-index"
83
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
84
+
85
+ [[package]]
86
+ name = "portable-atomic"
87
+ version = "1.15.0"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
90
+
91
+ [[package]]
92
+ name = "proc-macro2"
93
+ version = "1.0.107"
94
+ source = "registry+https://github.com/rust-lang/crates.io-index"
95
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
96
+ dependencies = [
97
+ "unicode-ident",
98
+ ]
99
+
100
+ [[package]]
101
+ name = "pyo3"
102
+ version = "0.29.2"
103
+ source = "registry+https://github.com/rust-lang/crates.io-index"
104
+ checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
105
+ dependencies = [
106
+ "libc",
107
+ "once_cell",
108
+ "portable-atomic",
109
+ "pyo3-build-config",
110
+ "pyo3-ffi",
111
+ "pyo3-macros",
112
+ ]
113
+
114
+ [[package]]
115
+ name = "pyo3-build-config"
116
+ version = "0.29.2"
117
+ source = "registry+https://github.com/rust-lang/crates.io-index"
118
+ checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
119
+ dependencies = [
120
+ "target-lexicon",
121
+ ]
122
+
123
+ [[package]]
124
+ name = "pyo3-ffi"
125
+ version = "0.29.2"
126
+ source = "registry+https://github.com/rust-lang/crates.io-index"
127
+ checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
128
+ dependencies = [
129
+ "libc",
130
+ "pyo3-build-config",
131
+ ]
132
+
133
+ [[package]]
134
+ name = "pyo3-macros"
135
+ version = "0.29.2"
136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
137
+ checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
138
+ dependencies = [
139
+ "proc-macro2",
140
+ "pyo3-macros-backend",
141
+ "quote",
142
+ "syn",
143
+ ]
144
+
145
+ [[package]]
146
+ name = "pyo3-macros-backend"
147
+ version = "0.29.2"
148
+ source = "registry+https://github.com/rust-lang/crates.io-index"
149
+ checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
150
+ dependencies = [
151
+ "heck",
152
+ "proc-macro2",
153
+ "quote",
154
+ "syn",
155
+ ]
156
+
157
+ [[package]]
158
+ name = "quote"
159
+ version = "1.0.47"
160
+ source = "registry+https://github.com/rust-lang/crates.io-index"
161
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
162
+ dependencies = [
163
+ "proc-macro2",
164
+ ]
165
+
166
+ [[package]]
167
+ name = "rstar"
168
+ version = "0.12.2"
169
+ source = "registry+https://github.com/rust-lang/crates.io-index"
170
+ checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
171
+ dependencies = [
172
+ "heapless",
173
+ "num-traits",
174
+ "smallvec",
175
+ ]
176
+
177
+ [[package]]
178
+ name = "smallvec"
179
+ version = "1.16.2"
180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
181
+ checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
182
+
183
+ [[package]]
184
+ name = "stable_deref_trait"
185
+ version = "1.2.1"
186
+ source = "registry+https://github.com/rust-lang/crates.io-index"
187
+ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
188
+
189
+ [[package]]
190
+ name = "syn"
191
+ version = "2.0.119"
192
+ source = "registry+https://github.com/rust-lang/crates.io-index"
193
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
194
+ dependencies = [
195
+ "proc-macro2",
196
+ "quote",
197
+ "unicode-ident",
198
+ ]
199
+
200
+ [[package]]
201
+ name = "target-lexicon"
202
+ version = "0.13.5"
203
+ source = "registry+https://github.com/rust-lang/crates.io-index"
204
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
205
+
206
+ [[package]]
207
+ name = "unicode-ident"
208
+ version = "1.0.26"
209
+ source = "registry+https://github.com/rust-lang/crates.io-index"
210
+ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
@@ -0,0 +1,6 @@
1
+ [workspace]
2
+ members = ["core", "bindings/python"]
3
+ resolver = "2"
4
+
5
+ [profile.release]
6
+ debug = false
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.4
2
+ Name: deoverlap
3
+ Version: 4.1.0
4
+ Classifier: Programming Language :: Python :: 3
5
+ Classifier: Programming Language :: Python :: 3.10
6
+ Classifier: Programming Language :: Python :: 3.11
7
+ Classifier: Programming Language :: Python :: 3.12
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Classifier: Programming Language :: Python :: 3.14
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering :: GIS
14
+ Classifier: Topic :: Utilities
15
+ Requires-Dist: shapely>=2.0
16
+ Requires-Dist: tqdm
17
+ Requires-Dist: pytest ; extra == 'test'
18
+ Requires-Dist: matplotlib ; extra == 'test'
19
+ Requires-Dist: vpype>=1.13 ; extra == 'test'
20
+ Requires-Dist: vpype>=1.13 ; extra == 'vpype'
21
+ Provides-Extra: test
22
+ Provides-Extra: vpype
23
+ License-File: LICENSE
24
+ Summary: De-overlap Shapely geometries that sit within a tolerance of each other.
25
+ Author-email: Pietro Leoni <pietro.leoni@gmail.com>
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
28
+ Project-URL: Bug Tracker, https://github.com/piLeoni/deoverlap/issues
29
+ Project-URL: Homepage, https://github.com/piLeoni/deoverlap
30
+
31
+ # Deoverlap
32
+
33
+ De-overlap Shapely geometries that sit within a tolerance of each other.
34
+
35
+ Source, examples and issues: [github.com/piLeoni/deoverlap](https://github.com/piLeoni/deoverlap)
36
+
37
+ The common case is pen-plotter work: two strokes closer than a pen width
38
+ visually merge on paper, so only one of them should keep the ink. Unlike
39
+ endpoint-only “deduplicate” tools, this library builds a **corridor** around
40
+ each kept stroke and crops (or drops) later strokes that fall inside it.
41
+
42
+ ![A city map at pen width: before, what deoverlap removed, after](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/osm_zoom.png)
43
+
44
+ ![The whole card: removed ink in red, the dashed box is the zoom above](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/osm_map.png)
45
+
46
+ Oakland's MacArthur Maze drawn with a 0.5 mm pen. In the zoom (the dashed box
47
+ on the card), doubled carriageways and junctions darken where the pen passes
48
+ twice (left); deoverlap removes the red strokes (middle) and the ink becomes
49
+ one even layer (right). See [A real map](#a-real-map-the-macarthur-maze) below.
50
+
51
+ ```bash
52
+ pip install deoverlap
53
+ # with the vpype command:
54
+ pip install "deoverlap[vpype]"
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ ```python
60
+ from shapely.geometry import LineString
61
+ from deoverlap import deoverlap
62
+
63
+ geoms = [
64
+ LineString([(0, 0), (2, 0)]),
65
+ LineString([(1, 0.05), (3, 0.05)]), # parallel, 0.05 away
66
+ ]
67
+ result = deoverlap(geoms, tolerance=0.1)
68
+
69
+ print(len(result.kept), "surviving geometries")
70
+ print("wholly removed:", result.wholly_removed)
71
+ ```
72
+
73
+ `tolerance` is a distance in the same units as the geometries: strokes closer
74
+ than this to a kept stroke are cut. For a plotter, use the pen width.
75
+
76
+ The Python function and the vpype command take the same options with the same
77
+ defaults; `--self-overlap` on the command line is `self_overlap=True` in Python.
78
+
79
+ ## How it works
80
+
81
+ Geometries are processed in priority order. Each kept stroke gets a corridor
82
+ of radius `tolerance` around it, and every later stroke loses the parts that
83
+ fall inside a corridor.
84
+
85
+ ![Tangent circles and a line: the corridor mask (orange) around kept strokes (blue) crops the overlapping arcs (red)](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/hero.png)
86
+
87
+ In every figure, blue is kept, red is removed and the thin orange outline is
88
+ the corridor mask. They are rendered by `examples/make_figures.py`.
89
+
90
+ ## Prefer — which stroke wins
91
+
92
+ When two corridors collide, priority is explicit:
93
+
94
+ | `prefer=` | Behaviour |
95
+ |---|---|
96
+ | `longest` (default) | The stroke that covers more ground wins |
97
+ | `first` | Input order |
98
+ | `shortest` | Short marks / detail win |
99
+
100
+ ![The same three strokes under prefer=longest, first and shortest](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/prefer.png)
101
+
102
+ Original indices are preserved in `result.kept_parts` / `removed_parts` even
103
+ when processing order changes.
104
+
105
+ ## Angle — which strokes count as overlapping
106
+
107
+ Two strokes overlap only where their directions differ by at most `angle`
108
+ degrees (default 30). A plotter redrawing a crossing is usually fine; the pain
109
+ is parallel near-coincident runs. `angle=90` counts every nearby stroke, so
110
+ crossings get cut too.
111
+
112
+ ```python
113
+ result = deoverlap(geoms, tolerance=0.1, angle=30)
114
+ ```
115
+
116
+ Directions are compared **locally, edge by edge**, so a curving ramp is cropped
117
+ only where it actually runs alongside another road, whatever direction its two
118
+ ends point in.
119
+
120
+ Where two strokes meet at an angle θ, they overlap for roughly `pen / sin θ`:
121
+ about 2 pen widths at 30° and 4 at 15°. The default of 30° catches parallel
122
+ runs and trims shallow merges; 45° or 60° trim steeper merges too.
123
+
124
+ ![angle=90 cuts the crossings too; angle=30 only crops the parallel duplicate](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/angle.png)
125
+
126
+ ## Crop or drop
127
+
128
+ By default the overlap is cut away and the rest of the stroke is kept. With
129
+ `drop=0.5`, a stroke that would lose more than half its length is discarded
130
+ whole instead of leaving stubs.
131
+
132
+ ![drop=None keeps the protruding stub, drop=0.5 discards the mostly covered stroke](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/crop_vs_drop.png)
133
+
134
+ `min_length=` drops pieces shorter than that after cutting (points are never
135
+ removed by it).
136
+
137
+ ## Split pieces stay one object
138
+
139
+ When a ring is cut by a corridor it becomes two arcs. Those arcs are returned
140
+ as **one** multipart geometry under the original input index — a logical
141
+ stroke, not two anonymous objects:
142
+
143
+ ```python
144
+ ring = LineString([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])
145
+ cutter = LineString([(1, -1), (1, 3)])
146
+ result = deoverlap([cutter, ring], 0.15, prefer="first", angle=90)
147
+
148
+ result.kept_parts[1] # MultiLineString of both arcs
149
+ ```
150
+
151
+ Use `flatten_geometries(result.kept)` for a flat list of primitive pieces.
152
+
153
+ ![A ring cut by a line stays one entry in result.kept](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/groups.png)
154
+
155
+ ## Self-overlap
156
+
157
+ A thin road outline is often **one** LineString (left kerb → end cap → right
158
+ kerb). Normally deoverlap never compares a geometry to itself, so the two sides
159
+ stay as a heavy double stroke. With `self_overlap=True` every edge is its own
160
+ corridor, so opposite sides can suppress each other. Neighbouring edges never
161
+ cut each other, so joints are not nibbled, unless the path folds back on itself
162
+ like a hairpin.
163
+
164
+ ![A thin ribbon drawn as one polyline: untouched normally, one side suppressed with self_overlap](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/self_overlap.png)
165
+
166
+ ## Multi-stage with a carried mask
167
+
168
+ ```python
169
+ r1 = deoverlap(batch1, 0.1)
170
+ r2 = deoverlap(batch2, 0.1, mask=r1.mask)
171
+ ```
172
+
173
+ Stage 2 is clipped against everything stage 1 kept.
174
+
175
+ ## Result
176
+
177
+ `deoverlap(...)` returns a `DeoverlapResult`:
178
+
179
+ | Field | Meaning |
180
+ |---|---|
181
+ | `kept` | Surviving geometries, one per input that kept some ink |
182
+ | `removed` | Cut pieces, flattened (if `keep_duplicates=True`) |
183
+ | `kept_parts` | `{original_index: geometry}` |
184
+ | `removed_parts` | `{original_index: geometry}` (if `keep_duplicates=True`) |
185
+ | `wholly_removed` | Indices removed entirely |
186
+ | `mask` | Corridor polygons (for the next stage) |
187
+
188
+ ## Performance
189
+
190
+ The engine is written in Rust and ships as a compiled extension, so
191
+ `pip install` needs no Rust toolchain on the supported platforms. It builds no
192
+ polygons: the corridor of a straight edge is a capsule, and the part of
193
+ another edge inside it is computed exactly. On the MacArthur Maze map below
194
+ (1473 paths) a run takes about 30 ms.
195
+
196
+ ## vpype plugin
197
+
198
+ Installing `deoverlap[vpype]` registers a `deoverlap` command that is
199
+ **layer-safe** (honours `-l`) and uses corridor proximity rather than
200
+ endpoint-only matching:
201
+
202
+ ```bash
203
+ pip install "deoverlap[vpype]"
204
+ vpype read map.svg deoverlap -t 0.1mm -l 1,2,3 write out.svg
205
+ ```
206
+
207
+ | Flag | Default | Meaning |
208
+ |---|---|---|
209
+ | `-t`, `--tolerance` | `0.1mm` | Corridor radius, usually the pen width |
210
+ | `--prefer` | `longest` | `longest` \| `first` \| `shortest` |
211
+ | `--angle` | `30` | Max direction difference in degrees; `90` cuts crossings too |
212
+ | `--self-overlap` | off | Let a path overlap itself |
213
+ | `-m`, `--min-length` | `0mm` | Drop pieces shorter than this after cutting |
214
+ | `--drop` | off | Discard a stroke when more than this fraction would be cut |
215
+ | `-k`, `--keep-duplicates` | off | Keep removed pieces in a separate layer |
216
+ | `-p`, `--progress-bar` | off | Display a progress bar |
217
+ | `-l`, `--layer` | `all` | Target layer(s) |
218
+
219
+ `-t`, `-l`, `-k` and `-p` mean the same as in vpype's own commands and the
220
+ `deduplicate` plugin; `-m` matches `vpype filter`.
221
+
222
+ ### Walkthrough with the bundled example
223
+
224
+ `examples/parallel_strokes.svg` is a tiny card with two near-parallel pairs
225
+ (0.05 mm apart), one deliberate cross, and a dual-kerb rectangle drawn as a
226
+ **single** polyline. No OSM dump required — open it, run the commands, open the
227
+ outputs side by side.
228
+
229
+ ```bash
230
+ # Inspect path count / drawn length
231
+ vpype read examples/parallel_strokes.svg stat
232
+
233
+ # Crop near-parallel duplicates; leave the perpendicular cross alone
234
+ vpype read examples/parallel_strokes.svg \
235
+ deoverlap -t 0.1mm -l 1 \
236
+ write examples/out_parallel.svg
237
+
238
+ # Same, but also let the dual-kerb ring collapse against itself
239
+ vpype read examples/parallel_strokes.svg \
240
+ deoverlap -t 0.12mm --self-overlap -l 1 \
241
+ write examples/out_self_overlap.svg
242
+
243
+ vpype read examples/out_parallel.svg stat
244
+ vpype read examples/out_self_overlap.svg stat
245
+ ```
246
+
247
+ What to look for (in the SVG or in `stat`’s drawn length):
248
+
249
+ 1. **Before** — each parallel pair is a double stroke; the ring reads as a heavy
250
+ double outline.
251
+ 2. **`out_parallel.svg`** — each pair reduced to one survivor; the cross still
252
+ there; the ring still double (one geometry cannot overlap itself).
253
+ 3. **`out_self_overlap.svg`** — the ring’s opposite sides suppress each other,
254
+ so the dual kerb thins toward a single outline.
255
+
256
+ ### A real map: the MacArthur Maze
257
+
258
+ `examples/macarthur_maze.svg` is a 3 km square of Oakland's MacArthur Maze
259
+ interchange from OpenStreetMap, scaled onto a 100 mm card (1 mm ≈ 33 m). At
260
+ that scale dual carriageways, stacked ramps and frontage roads sit a fraction
261
+ of a millimetre apart, so a 0.5 mm pen draws the same paper two or three
262
+ times. Set the tolerance to the pen width: anything closer than that would
263
+ overlap on paper.
264
+
265
+ ```bash
266
+ vpype read examples/macarthur_maze.svg stat
267
+ vpype read examples/macarthur_maze.svg \
268
+ deoverlap -t 0.5mm --angle 90 -m 1mm \
269
+ write examples/out_maze.svg
270
+ vpype read examples/out_maze.svg stat
271
+ ```
272
+
273
+ `--angle 90` is the maximum: every nearby stroke counts, whatever its
274
+ direction. Side streets stop where the main road's ink begins, and at crossings
275
+ the shorter path is split around the longer one. The gap is exactly the other
276
+ road's ink width, so on paper the crossing still looks whole, just without the
277
+ dark spot of doubled ink.
278
+
279
+ Cutting leaves fragments; almost every piece under 1 mm on this card is one.
280
+ `-m 1mm` (two pen widths) drops them. The path count goes from 1473 to 860 and
281
+ the drawn length drops by 35%.
282
+
283
+ The images at the top of this page show this run. Their ink panels are blended
284
+ like real ink: every pass multiplies the colour, so the darker the blue, the
285
+ more times the pen went over the same spot. Before, the dark bands are doubled
286
+ carriageways and the dark dots are junctions, where a round pen tip lands on
287
+ ink that is already there. After, the ink is one even layer.
288
+
289
+ With the default `--angle 30` crossings are left alone: parallel runs are
290
+ still merged, but both roads are drawn through every junction, and the card
291
+ loses about 23% instead of 35%. The same zoom, crossings kept and cut:
292
+
293
+ ![The zoom at --angle 30 and --angle 90: at 30 the crossings keep both roads and leave dark spots, at 90 they are cut](https://raw.githubusercontent.com/piLeoni/deoverlap/main/docs/img/osm_angle.png)
294
+
295
+ Keep crossings when every stroke must stay continuous (e.g. for later
296
+ editing); cut them when only the ink on paper matters.
297
+
298
+ To try another place (needs `pip install osmnx`):
299
+
300
+ ```bash
301
+ python examples/fetch_osm_example.py --lat 51.5074 --lon -0.1278 --radius 1500 \
302
+ --out examples/my_place.svg
303
+ ```
304
+
305
+ Map data © OpenStreetMap contributors, available under the
306
+ [ODbL](https://www.openstreetmap.org/copyright).
307
+
308
+ ### Different settings per layer
309
+
310
+ Chain the command to give each layer its own settings:
311
+
312
+ ```bash
313
+ vpype read map.svg \
314
+ deoverlap -t 0.1mm -l 3,4 \
315
+ deoverlap -t 0.15mm --self-overlap -l 5 \
316
+ write opt.svg
317
+ ```
318
+
319
+ ## API
320
+
321
+ ```python
322
+ deoverlap(
323
+ geometries,
324
+ tolerance,
325
+ *,
326
+ prefer="longest", # longest | first | shortest
327
+ angle=30.0, # degrees, 0–90; 90 cuts crossings too
328
+ self_overlap=False,
329
+ min_length=0.0,
330
+ drop=None, # fraction 0–1; None always crops
331
+ keep_duplicates=False,
332
+ progress_bar=False,
333
+ mask=None,
334
+ ) -> DeoverlapResult
335
+ ```
336
+