A set of pure python tools for working with linestrings. This library is only useful if you can't use the popular shapely
library for some reason (shapely
uses the compiled GEOS library written in C++ and is therefore much faster)
This project has no dependencies outside the standard library, and therefore has no binaries to compile.
Note the implementation of the offset algorithm is not yet completed
from nicks_line_tools.Vector2 import Vector2
from nicks_line_tools.linestring_offset import linestring_offset
ls = [Vector2(1, 1), Vector2(5, 6), Vector2(12, 8)]
ls_offset = linestring_offset(ls, 5)
- No support for arcs
- No handling of malformed input geometry
- Probably cryptic error messages when things go wrong
Implementation loosely inspired by Xu-Zheng Liu, Jun-Hai Yong, Guo-Qin Zheng, Jia-Guang Sun. An offset algorithm for polyline curves. Computers in Industry, Elsevier, 2007, 15p. inria-00518005 This was the first google result for 'line offset algorithm'
Having implemented the paper as close as I can, I am a bit annoyed with it. The algorithm is really described well up untill algorithim 4 step 1b. After that it is very hard to decypher. I did get it working, but I think I had to re-invent the last few bits of the algorithim, just going on the gist of what the paper describes. I did not implement algorithms 2 and 3, as they deal with curved (arc) segments.
Type definitions
from typing import List, Tuple
from nicks_line_tools.Vector2 import Vector2
# define type aliases for convenience:
LineString = List[Vector2]
MultiLineString = List[LineString]
LineSegment = Tuple[Vector2, Vector2]
LineSegmentList = List[Tuple[Vector2, Vector2]]
Parameter = float
# declare type of variables used:
input_linestring: LineString
offset_ls: LineString
offset_segments: LineSegmentList
raw_offset_ls: LineString
Function Type Definitions (pseudocode)
intersect = (tool: LineString, target: LineString) -> (point_of_intersection: Optional[Vector2], distance_along_target: List[Parameter])
project = (tool: Vector2, target: LineString) -> (nearest_point_on_target_to_tool: Vector2, distance_along_target: Parameter)
interpolate = (distance_along_target: Parameter, target: LineString) -> (point_on_target: Vector2)
Given a LineString
, call it the input_linestring
; the goal is to find the offset_ls
which is parallel to input_linestring
and offset by the distance d
- Pretreatment steps are not implemented... these mostly deal with arcs and malformed input geometry
- No check is performed to prevent execution when
d==0
- Create an empty
LineSegmentList
calledoffset_segments
- For each
LineSegment
ofinput_linestring
- Take each segment
(a,b)
ofinput_linestring
and compute the vector from the start to the end of the segment
ab = b - a
- rotate this vector by -90 degrees to obtain the 'left normal'
ab_left = Vector2(-ab.y, ab.x)
- normalise
ab_left
by dividing each component by the magnitude ofab_left
. - multiply the vector by the scalar
d
to obtain thesegment_offset_vector
- add the
segment_offset_vector
toa
andb
to getoffset_a
andoffset_b
- append
(offset_a, offset_b)
tooffset_segments
- Take each segment
- Create an empty
LineString
calledraw_offset_ls
- Append
offset_segments[0][0]
toraw_offset_ls
- For each pair of consecutive segments
(a,b),(c,d)
inoffset_segments
- If
(a,b)
is co-linear with(c,d)
then appendb
to raw_offset_ls, and go to the next pair of segments. - Otherwise, find the intersection point
p
of the infinite lines that are collinear with(a,b)
and(c,d)
;
Pay attention to the location ofp
relative to each of the segments:- if
p
is within the segment it is a True Intersection Point or TIP - If
p
is outside the segment it is called a False Intersection Point of FIP.
FIPs are further classified as follows:- Positive FIP or PFIP if
p
is after the end of a segment, or - Negative FIP or NFIP if
p
is before the start of a segment.
- Positive FIP or PFIP if
- if
- If
p
is a TIP for both(a,b)
and(c,d)
appendp
toraw_offset_ls
- If
p
is a FIP for both(a,b)
and(c,d)
- If
p
is a PFIP for(a,b)
then appendp
toraw_offset_ls
- Otherwise, append
b
thenc
toraw_offset_ls
- If
- Otherwise, append
b
thenc
toraw_offset_ls
- If
- Remove zero length segments in
raw_offset_ls
Note: mitre limits are not mentioned in the original paper and have not been implemented in this package (yet). Extending line segments that are close to parallel will generate an intersection point far from the origin which would cause problems with floating-point precision.
TODO: It should be possible to maintain a partial mapping between the segments of
input_ls
and
-
Find
raw_offset_ls_twin
by repeating Algorithms 0.1 and 1 but offset theinput_linestring
in the opposite direction (-d
) -
Find
intersection_points
betweenraw_offset_ls
andraw_offset_ls
raw_offset_ls
andraw_offset_ls_twin
-
Find
split_offset_mls
by splittingraw_offset_ls
at each point inintersection_points
-
If
intersection_points
was empty, then addraw_offset_ls
tosplit_offset_mls
and skip to Algorithm 4.2. -
Delete each
LineString
insplit_offset_mls
if it intersects theinput_linestring
unless the intersection is with the first or lastLineSegment
ofinput_linestring
- If we find such an intersection point that is on the first or last
LineSegment
ofinput_linestring
then add the intersection point to a list calledcut_targets
- If we find such an intersection point that is on the first or last
- For each point
p
incut_targets
- construct a circle of diameter
d
with its center atp
- delete all parts of any linestring in
split_offset_mls
which falls within this circle - Empty the
cut_targets
list
- For each linestring
item_ls
insplit_offset_mls
- For each segment
(a,b)
initem_ls
- For each segment
(u,v)
ofinput_linestring
- Find
a_proj
andb_proj
by projectinga
andb
onto segment(u,v)
- Adjust the projected points such that
a_proj
andb_proj
lie at or betweenu
andv
- Find
a_dist
by computingmagnitude(a_proj - a)
- Find
b_dist
by computingmagnitude(b_proj - b)
- if either
a_dist
orb_dist
is less than the absolute value ofd
then- if
a_dist > b_dist
adda_proj
tocut_targets
- Otherwise, add
b_proj
tocut_targets
- if
- Find
- For each segment
- Repeat Algorithm 4.1.2
- Remove zero length segments and empty linestrings etc (TODO: not yet implemented)
- Join remaining linestrings that are touching to form new linestring(s) (TODO: not yet implemented)