diff --git a/lib/xmet/geo.py b/lib/xmet/geo.py index e9cc4ef..b7ffa66 100644 --- a/lib/xmet/geo.py +++ b/lib/xmet/geo.py @@ -2,9 +2,22 @@ import re import math import shapely -from typing import Self +from typing import Self, Union + +def each_intermediate_point(p1: shapely.Point, + p2: shapely.Point, + intersection: Union[shapely.LineString, shapely.MultiLineString]): + typeof = type(intersection) + + if typeof is shapely.LineString: + pass + elif typeof is shapely.MultiLineString: + pass class PointSequence(list): + linestring: shapely.LineString + polygon: shapely.Polygon + @staticmethod def from_file(path: str) -> Self: ret = PointSequence() @@ -25,7 +38,8 @@ class PointSequence(list): def __init__(self, points: list=None): super().__init__() - self.poly = None + self.linestring = None + self.polygon = None if points is not None: for point in points: @@ -43,10 +57,11 @@ class PointSequence(list): return self[-1] == self[0] def close(self): - if self[-1] != self[0]: + if not self.is_closed(): self.append(self[0]) - self.poly = shapely.Polygon(self) + self.linestring = shapely.LineString(self) + self.polygon = shapely.Polygon(self) def nearest_index(self, point: shapely.Point) -> int: indices = list() @@ -58,6 +73,113 @@ class PointSequence(list): return indices[0][0] +class PolygonBuilder(): + point_first: shapely.Point + point_last: shapely.Point + + def __init__(self, sequence: PointSequence, bounds: PointSequence): + self.sequence = sequence + self.bounds = bounds + self.point_first = None + self.point_last = None + self.total = 0 + + def yield_point(self, point: shapely.Point): + """ + Yield the single point to the caller, while maintaining state of + whether this point is the first or last point seen by the polygon + builder. + """ + if self.point_first is None: + self.point_first = point + + self.point_last = point + + yield point + + self.total += 1 + + def each_intermediate_point(self, p1: shapely.Point, p2: shapely.Point): + count = len(self.bounds) + + i1 = self.bounds.nearest_index(p1) + count + i2 = self.bounds.nearest_index(p2) + count + + if i1 > i2: + return + + for i in range(i1, i2+1): + yield from self.yield_point(self.bounds[i % count]) + + def each_point_within(self): + last = None + + for point in self.sequence: + if last is None: + last = point + continue + + last_within = self.bounds.polygon.contains(last) + point_within = self.bounds.polygon.contains(point) + + # + # If the first point in the current line exists within the other + # geometry, then yield it. + # + if last_within: + yield from self.yield_point(last) + + # + # Check for intersections with the line in the other geometry. + # + inter = self.bounds.linestring.intersection(shapely.LineString([last, point])) + + if inter.geom_type == 'Point': + # + # If the intersection is a single point, then yield that + # point. + # + yield from self.yield_point(inter) + elif inter.geom_type == 'MultiPoint': + # + # If the intersection is multiple points, then yield those + # points, as well as all between on the other geometry, if and + # only if the intersection does not constitute the first and + # last point. + # + last_geom = None + + for geom in inter.geoms: + if last_geom is None: + last_geom = geom + continue + + yield from self.yield_point(last_geom) + yield from self.each_intermediate_point(last_geom, geom) + yield from self.yield_point(geom) + + last_geom = geom + + # + # If the second point in the current line exists within the other + # geometry, then yield that. + # + if point_within: + yield from self.yield_point(point) + + # + # If only two points have been yielded, then yield the intermediate + # between the end and the start (reversed). + # + if self.total == 2: + yield from self.each_intermediate_point(self.point_last, + self.point_first) + + last = point + + def process(self) -> shapely.Polygon: + pass + def heading(p1: shapely.Point, p2: shapely.Point) -> float: dx = p2.x - p1.x dy = p2.y - p1.y