Source code for upxo.geoEntities.muledge2d

"""
2D multi-edge collection entity for UPXO.

Usage
-----
    from upxo.geoEntities.muledge2d import muledge2d

Classes
-------
muledge2d : Ordered collection of connected 2D edges with spatial operations.

Notes
-----
A ``muledge2d`` stores an ordered, optionally closed chain of ``edge2d``
objects, backed by a shared ``MPoint2d`` multi-point object for fast spatial
queries.
"""
from upxo._sup import dataTypeHandlers as dth
from upxo.geoEntities.mulpoint2d import MPoint2d as mulpoint2d
# from mulpoint2d import mulpoint2d
from upxo.geoEntities.point2d import Point2d as point2d
from upxo.geoEntities.edge2d import edge2d
import matplotlib.pyplot as plt
import numpy as np
from collections import deque
from upxo._sup import gops

np.seterr(divide='ignore')


[docs] class muledge2d(): """ Ordered collection of connected 2D edges with spatial and geometric operations. Represents a chain of ``edge2d`` objects sharing endpoints. Supports multiple construction pathways (coordinate list, coordinate-pair list, point objects) and exposes properties such as lengths, slopes, centroid, and roughness over the full chain. Usage ----- from upxo.geoEntities.muledge2d import muledge2d """ ROUND_ZERO_DEC_PLACE = 10 EPS = 0.000000000001 __slots__ = ('dim', # Dimensionality 'ppairs', # list of UPXO point objects 'cpairs', # Coordinates of the constituent points 'clist', # List of coordinates of points 'pmids', # UPXO point2d memory ID list 'pmid_pairs', # List of UPXO point2d memory ID pairs 'pindices', # pindices of the coords which make the muledge2d 'eindices', # pindices of the edges which make the muledge2d 'points', # Constituent UPXO point objects 'mpoint', # Multi-point with all points 'empoints', # Edge wise multi-points 'edges', # Constituent UPXO edge2d objects 'ordered', # Specifies ordering of coordinates 'closed', # Specifies whether already a ring or not 'psense', # Specifies whether CW or CCW point ordering 'esense', # Specifies whether CW or CCW edge ordering 'plean', # Lean of UPXO point objects 'mplean', # Lean of UPXO Multi-POint objects 'elean', # Lean of UPXO Edge objects 'lean', # Lean of the self object '__rings__' # ring objects ) def __init__(self, method='cpairs_list', ordered=True, closed=False, psense='ccw', esense='ccw', clist=[], cpairs_list=[], points=[], edges=None, edge_base='upxo', pindices=[], eindices=[], make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore', ): """ Parameters ---------- method : str Construction strategy. ``'clist'`` builds from a flat coordinate list; ``'cpairs_list'`` from coordinate pairs; ``'edges'`` from existing edge objects; ``'points'`` from Point2d objects. ordered : bool, optional Whether the edge chain is ordered (head-to-tail). Default ``True``. closed : bool, optional Whether the chain forms a closed loop. Default ``False``. psense : str, optional Point orientation sense. Default ``'counter_clockwise'``. esense : str, optional Edge orientation sense. Default ``'counter_clockwise'``. clist : list Flat coordinate list ``[[x0,y0], [x1,y1], ...]``. cpairs_list : list Coordinate pair list ``[[[x0,y0],[x1,y1]], ...]``. points : list Sequence of ``Point2d`` objects. edges : list, optional Pre-built edge objects (used when ``method='edges'``). edge_base : str, optional Backend of the supplied edges: ``'upxo'``, ``'shapely'``, ``'vtk'``, or ``'gmsh'``. Default ``'upxo'``. pindices : list, optional Point-pairing indices used to construct ``edge2d`` objects. make_mp : bool, optional Build a ``mulpoint2d`` from all points. Default ``True``. make_emp : bool, optional Build per-edge ``mulpoint2d`` objects. Default ``True``. lean : str, optional Lean of the multi-edge object. Default ``'ignore'``. plean : str, optional Lean applied to point objects. Default ``'ignore'``. mplean : str, optional Lean applied to the multi-point object. Default ``'ignore'``. elean : str, optional Lean applied to edge objects. Default ``'ignore'``. melean : str, optional Lean applied to the multi-edge-point object. Default ``'ignore'``. """ # --------------------------------- self.dim = 2 # Dimensionality of the multi-edge object # --------------------------------- if lean in ('ignore'): self.ordered = ordered self.closed = closed self.psense = psense self.esense = esense self.lean = lean self.plean = plean self.mplean = mplean self.elean = elean self.__rings__ = [] # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if method == 'clist' and self.ordered: ''' clist = [[0, 0], [1, 0], [0, 0], [1, 1.01], [2, 2]] from muledge2d import muledge2d me = muledge2d(method='clist', ordered=True, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) ''' # Make clist unique _mp_ = mulpoint2d(method='xy_pair_list', coordxy=clist) clist = deque([]) for _x, _y in zip(_mp_.locx, _mp_.locy): clist.append([_x, _y]) # --------------------------- self.clist = clist npoints = len(clist) # --------------------------- # Build pindices self.pindices = [[i, i+1] for i in range(len(self.clist)-1)] # --------------------------- # Build UPXO points self.points = deque([point2d(clist[i][0], clist[i][1], lean=plean) for i in range(npoints)]) # --------------------------- # Build pmids self.pmids = deque([id(p) for p in self.points]) # --------------------------- # Build pmid_pairs self.pmid_pairs = [[self.pmids[i[0]], self.pmids[i[1]]] for i in self.pindices] # --------------------------- # AVOID USING PPAIRS AT ALL COST. # TO BE DEPRACATED self.ppairs = [[self.points[i[0]], self.points[i[1]]] for i in self.pindices] # --------------------------- # Build edges and eindices self.edges = deque([edge2d(method='up2d', pnta=self.points[i[0]], pntb=self.points[i[1]], edge_lean=self.elean, ) for i in self.pindices]) self.eindices = deque([i for i in range(npoints)]) # --------------------------- # Build multi-point object self.mpoint = mulpoint2d(method='up2d_list', point_objects=self.points, lean=self.mplean) # --------------------------- # Build edge wise multi-point self.empoints = [mulpoint2d(method='up2d_list', point_objects=[self.points[i[0]], self.points[i[1]]], lean=self.mplean) for i in self.pindices] # --------------------------- if make_mp: self.dbbuild_mpoint() # --------------------------- if make_emp: self.dbbuild_empoint() # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if method == 'clist' and not self.ordered: ''' # EXAMPLE-1: specifying "pindices" clist = [[0, 0], [1, 0], [1, 1.01], [2, 2]] from muledge2d import muledge2d pindices = [ [0, 3], [0, 1], [2, 3], [1, 2]] me = muledge2d(method='clist', ordered=False, pindices = pindices, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) >> me.points >> Out[409]: deque([upxo.p2d(0.0, 0.0), upxo.p2d(1.0, 0.0), upxo.p2d(1.0, 1.01), upxo.p2d(2.0, 2.0)]) me.edges >> Out[410]: [upxo.e2d[(0.0, 0.0)⚯(2.0, 2.0)⊢⊣2.8284], upxo.e2d[(0.0, 0.0)⚯(1.0, 0.0)⊢⊣1.0], upxo.e2d[(1.0, 1.01)⚯(2.0, 2.0)⊢⊣1.4072], upxo.e2d[(1.0, 0.0)⚯(1.0, 1.01)⊢⊣1.01]] #--------------------------------- # EXAMPLE-2: specifying "pindices" and "eindices" clist = [ [0, 0], [1, 0], [1, 1.01], [2, 2] ] from muledge2d import muledge2d pindices = [ [0, 3], [0, 1], [2, 3], [1, 2] ] eindices = [2, 0, 1, 3] me = muledge2d(method='clist', ordered=False, pindices = pindices, eindices = eindices, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) >> me.points >> Out[414]: deque([upxo.p2d(0.0, 0.0), upxo.p2d(1.0, 0.0), upxo.p2d(1.0, 1.01), upxo.p2d(2.0, 2.0)]) me.edges >> Out[415]: [upxo.e2d[(1.0, 1.01)⚯(2.0, 2.0)⊢⊣1.4072], upxo.e2d[(0.0, 0.0)⚯(2.0, 2.0)⊢⊣2.8284], upxo.e2d[(0.0, 0.0)⚯(1.0, 0.0)⊢⊣1.0], upxo.e2d[(1.0, 0.0)⚯(1.0, 1.01)⊢⊣1.01]] #--------------------------------- # EXAMPLE-3: This will give an error as data is incosistant clist = [[0, 0], [1, 0], [0, 0], [1, 1.01], [2, 2]] from muledge2d import muledge2d pindices = [ [0, 3], [0, 1], [2, 3], [1, 2]] me = muledge2d(method='clist', ordered=False, pindices = pindices, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) >> Please enter valid clist data #--------------------------------- ''' _npoints_ = len(clist) # Make clist unique _mp_ = mulpoint2d(method='xy_pair_list', coordxy=clist) if _mp_.npoints == _npoints_: clist = deque([]) for _x, _y in zip(_mp_.locx, _mp_.locy): clist.append([_x, _y]) proceed = True else: print('Please enter valid clist data') proceed = False _pindices_flat_ = [] for pind in pindices: _pindices_flat_.append(pind[0]) _pindices_flat_.append(pind[1]) _max_ = np.array(_pindices_flat_).max()+1 if proceed: if _max_ == _npoints_: proceed = True else: print('Please enter valid pindices data') proceed = False if proceed: # --------------------------- npoints = len(clist) self.clist = clist # --------------------------- # Build pindices self.pindices = pindices # --------------------------- # Build UPXO points self.points = deque([point2d(x=coord[0], y=coord[1], lean=plean) for coord in clist] ) # --------------------------- # Build pmids self.pmids = deque([id(p) for p in self.points]) # --------------------------- # Build pmid_pairs self.pmid_pairs = [[self.pmids[i[0]], self.pmids[i[1]]] for i in self.pindices] # --------------------------- # Build edges edges = [edge2d(method='up2d', pnta=self.points[i[0]], pntb=self.points[i[1]], edge_lean=self.elean, ) for i in self.pindices] if eindices: self.edges = deque([edges[i] for i in eindices]) self.eindices = eindices else: self.edges = deque(edges) self.eindices = list(range(npoints)) # --------------------------- # Build multi-point object self.mpoint = mulpoint2d(method='up2d_list', point_objects=self.points, lean=self.mplean) # --------------------------- # Build edge wise multi-point self.empoints = [mulpoint2d(method='up2d_list', point_objects=[self.points[i[0]], self.points[i[1]]], lean=self.mplean) for i in self.pindices] # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if method == 'cpairs_list' and self.ordered: ''' ***USER MUST ENSURE POINTS ARE IN RIGHT ORDER BOTH IN THE COORDINATE PAIRS AND ACROSS THE COORDINATE PAIRS LIST*** What does 'ordered' data format mean in this case? : Coordinate pair arrangement is ordered for: (1) Sense of the edge (direction) (2) Adjacent edges have relavant data which are also adjcent (in cpairs_list). EXAMPLE cpairs_list = [[[1, 1], [-1, 1]], [[-1, 1], [-1, -1]], [[-1, -1], [1, -1]], ] from muledge2d import muledge2d me = muledge2d(method='cpairs_list', ordered=True, closed=False, cpairs_list=cpairs_list, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) me.plotme() me.plotmp() ''' self.clist = deque([]) self.cpairs = np.array(cpairs_list) self.pindices = deque([]) self.ppairs = [] if not closed: pnta = point2d(self.cpairs[0][0][0], self.cpairs[0][0][1], lean=plean ) pntb = point2d(self.cpairs[0][1][0], self.cpairs[0][1][1], lean=plean ) self.ppairs.append([pnta, pntb]) self.pindices.append([0, 1]) self.clist.extend([list(self.cpairs[0][0]), list(self.cpairs[0][1])]) for i, _ in enumerate(self.cpairs[1:], start=1): pnta = self.ppairs[i-1][1] pntb = point2d(self.cpairs[i][1][0], self.cpairs[i][1][1], lean=plean ) self.ppairs.append([pnta, pntb]) self.pindices.append([i, i+1]) self.clist.append(list(self.cpairs[i][1])) else: # If the mul-edge is indeed closed pass # ------------------------------------------- # Build the DB of all poinnts in this multi-edge object self.dbbuild_points(method='ppairs') # ------------------------------------------- self.eindices = list(range(len(self.ppairs))) # --------------------------- if make_mp: self.dbbuild_mpoint() # --------------------------- if make_emp: self.dbbuild_empoint() # \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if method == 'cpairs_list' and not self.ordered: ''' ***USER MUST ENSURE POINTS ARE IN RIGHT ORDER BOTH IN THE COORDINATE PAIRS AND ACROSS THE COORDINATE PAIRS LIST*** Sense will be assumed to be from first to the second point i.e. from pnta to pntb Ordering will done as per pindices EXAMPLE cpairs_list = [[[1, 1], [-1, 1]], [[-1, -1], [1, -1]], [[-1, 1], [-1, -1]], ] eindices = [0, 2, 1] # NOTE: Notice that 2nd 3rd edges are swapped from the # previous example (under branch for ordered=True) from muledge2d import muledge2d me = muledge2d(method='cpairs_list', ordered=False, closed=False, psense='ccw', esense='ccw', cpairs_list=cpairs_list, eindices=eindices, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) me.plotme() me.plotmp() ''' self.clist = deque([]) self.cpairs = np.array(cpairs_list) ppairs = [] _pindices = [] if not closed: pnta = point2d(self.cpairs[0][0][0], self.cpairs[0][0][1], lean=plean ) pntb = point2d(self.cpairs[0][1][0], self.cpairs[0][1][1], lean=plean ) ppairs.append([pnta, pntb]) _pindices.append([0, 1]) self.clist.extend([list(self.cpairs[0][0]), list(self.cpairs[0][1])]) for i, _ in enumerate(self.cpairs[1:], start=1): pnta = ppairs[i-1][1] pntb = point2d(self.cpairs[i][1][0], self.cpairs[i][1][1], lean=plean ) ppairs.append([pnta, pntb]) _pindices.append([i, i+1]) self.clist.append(list(self.cpairs[i][1])) else: # If the mul-edge is indeed closed pass # ------------------------------------------- self.ppairs = [ppairs[i] for i in eindices] # ------------------------------------------- # Build the DB of all poinnts in this multi-edge object self.dbbuild_points(method='ppairs') # ------------------------------------------- self.pindices = [_pindices[i] for i in eindices] self.eindices = eindices # ------------------------------------------- # Build the memory ID data-bases (only for developers) self.dbbuild_pmid() self.dbbuild_pmid_pairs() self.dbbuild_edges() # --------------------------- if make_mp: self.dbbuild_mpoint() # --------------------------- if make_emp: self.dbbuild_empoint() # =========================================== if method == 'up2d_list' and self.ordered: ''' points = [point2d(x=0, y=0, lean='ignore'), point2d(x=1, y=0, lean='ignore'), point2d(x=0, y=1, lean='ignore'), point2d(x=1, y=1.01, lean='ignore'), point2d(x=2, y=2, lean='ignore'), point2d(x=2, y=2, lean='ignore'),] from muledge2d import muledge2d me = muledge2d(method='up2d_list', ordered=True, closed=False, points=points, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) ''' # Make points unique _mp_ = mulpoint2d(method='up2d_list', point_objects=points) # --------------------------- # saa points self.points = _mp_.points # --------------------------- # Make clist self.clist = deque([[p.x, p.y] for p in self.points]) # --------------------------- npoints = len(self.clist) # --------------------------- # Build pindices self.pindices = [[i, i+1] for i in range(npoints-1)] # --------------------------- # Build pmids self.pmids = deque([id(p) for p in self.points]) # --------------------------- # Build pmid_pairs self.pmid_pairs = [[self.pmids[i[0]], self.pmids[i[1]]] for i in self.pindices] # --------------------------- # Build edges and eindices self.edges = deque([edge2d(method='up2d', pnta=self.points[i[0]], pntb=self.points[i[1]], edge_lean=self.elean, ) for i in self.pindices] ) self.eindices = deque([i for i in range(npoints)]) # --------------------------- # Build multi-point object self.mpoint = mulpoint2d(method='up2d_list', point_objects=self.points, lean=self.mplean) # --------------------------- # Build edge wise multi-point self.empoints = [mulpoint2d(method='up2d_list', point_objects=[self.points[i[0]], self.points[i[1]]], lean=self.mplean) for i in self.pindices] # --------------------------- if method == 'ue2d_list' and self.ordered: pass # ============================ for p in self.points: p.links['me'].append(self) # ######################################################### def __att__(self): """Return a string listing of all attributes of this multi-edge.""" return gops.att(self) def __repr__(self): """Return ``id, e-<N> p-<M>, closed/open, sense, ordered`` summary.""" string = [] string.append(str(id(self)) + ', ') string.append(f'e-{len(self.edges)} p-{len(self.points)}, ') if self.closed: string.append('closed, ') else: string.append('not closed, ') string.append(f'e-{self.esense}, p-{self.psense}, ') if self.ordered: string.append('p-ordered, ') else: string.append('p-not ordered, ') return ''.join(string) def __len__(self): """Return the number of edges in the multi-edge chain.""" return len(self.edges) # #########################################################
[docs] def dbbuild_plean(self): """Rebuild ``self.plean`` from the lean of each point in the chain.""" self.plean = deque([p.lean for p in self.points])
[docs] def dbbuild_elean(self): """Rebuild ``self.elean`` from the lean of each edge in the chain.""" self.elean = deque([e.lean for e in self.edges])
[docs] def dbbuild_pmid(self): """Rebuild ``self.pmids`` from ``id()`` of each point in the chain.""" self.pmids = deque([id(p) for p in self.points])
[docs] def dbbuild_pmid_pairs(self): """Rebuild ``self.pmid_pairs`` from ``id()`` of each point-pair.""" self.pmid_pairs = [[id(pp[0]), id(pp[1])] for pp in self.ppairs]
[docs] def dbbuild_points(self, method='ppairs', data=None): """Rebuild ``self.points`` from the current point-pairs.""" if method == 'ppairs': self.points = deque([self.ppairs[0][0]]) for pp in self.ppairs: self.points.append(pp[1])
[docs] def dbbuild_mpoint(self): """Rebuild ``self.mpoint`` as a ``mulpoint2d`` from all chain points.""" self.mpoint = mulpoint2d(method='up2d_list', point_objects=self.points, lean=self.mplean)
[docs] def dbbuild_empoint(self): """Rebuild ``self.empoints`` as per-edge ``mulpoint2d`` objects.""" self.empoints = [mulpoint2d(method='up2d_list', point_objects=pp, lean=self.mplean) for pp in self.ppairs]
[docs] def dbbuild_edges(self): """Rebuild ``self.edges`` as ``edge2d`` objects from ``self.ppairs``.""" self.edges = deque([edge2d(method='up2d', pnta=pp[0], pntb=pp[1], edge_lean=self.elean) for pp in self.ppairs])
# #########################################################
[docs] def edit_edge_add_point(self, eind, obj): """ Adds point (obj) between pnta and pntb and make two edges Original edge gets edited, new one will be returned. returns the additional edge. If EDGE be the edge and OBJ be the point object being added, then the resulting edges will be:: edge 1: EDGE.pnta -- OBJ edge 2: OBJ -- EDGE.pntb Parameters ---------- eind : int Specifies which edge to be edited obj : Point2d Specifies the UPXO point2d object to be inserted Returns ------- None. """ ''' PROOF OF CONCEPT CODE pnta, pntb, obj = point2d(0,0), point2d(1,0), point2d(0.5,0) edge = edge2d(pnta=pnta, pntb=pntb, edge_lean='ignore') edge.update_point('b', obj, method='up2d') new_edge = edge2d(pnta=obj, pntb=pntb, edge_lean='ignore') [edge, new_edge] 1. MAKE ME.EDGES --- A DEQUE OBJECT: EASIER TO WORK WITH INSERTING ''' if dth.unique_of_datatypes([obj])[0] == "<class 'UPXO-point.point2d'>": # self.edges[eind]. pass
# ######################################################### @property def centroid(self): ''' Return the UPXO centroid point ''' _x, _y = self.mpoint.centroid return point2d(_x, _y, lean='ignore') @property def centroids(self): ''' Return centroids of constituent edges of me. ''' return list(map(lambda e: e.centroid, self.edges)) @property def lengths(self): ''' Returns lengths of constituent edges of me. ''' return list(map(lambda e: e.length, self.edges)) @property def slopes(self): ''' Returns slopes of constituent edges of me. ''' return list(map(lambda e: e.slope, self.edges)) @property def length(self): ''' Returns total length of constituent edges of me. ''' return np.sum(self.lengths) @property def mean_length(self): ''' Returns mean length of constituent edges of me. ''' return np.mean(self.lengths) @property def mean_slope(self): ''' Returns mean slope of constituent edges of me. ''' return np.mean(self.slopes) @property def roughness(self): """Roughness measure of the chain. Not yet implemented.""" raise NotImplementedError("roughness is not yet implemented.") @property def angles180(self): """ Orientation angles of each vertex coordinate in [−180°, 180°]. Returns ------- list of float Angles computed from the positive x-axis for each coordinate in ``self.clist``, signed by the y-component. """ _norm_ = np.linalg.norm angles = [] for coord in self.clist: if _norm_(coord) >= self.EPS: dot_product = np.dot([1, 0], coord/_norm_(coord)) angle = np.arccos(dot_product)*57.29577951308232 if abs(coord[1]) >= self.EPS: angle = angle * coord[1]/abs(coord[1]) else: angle = 360.0 angles.append(angle) return angles @property def angles(self): """ Orientation angles of each vertex coordinate in [0°, 360°]. Returns ------- list of float Angles in degrees measured counter-clockwise from the positive x-axis for each coordinate in ``self.clist``. """ angles = [] _norm_ = np.linalg.norm for coord in self.clist: if _norm_(coord) >= self.EPS: dot_product = np.dot([1, 0], coord/_norm_(coord)) angle = np.arccos(dot_product)*57.29577951308232 y = coord[1] if abs(y) >= self.EPS: if y/abs(y) < 0: angle = 360.0-angle else: angle = 360.0 angles.append(angle) return angles # #########################################################
[docs] def make_ring(self, saa=True, throw=False, close_index=0): """ Close this multi-edge into a ring by connecting the last point to the first. Parameters ---------- saa : bool, optional Save-and-apply: modify ``self`` in place. Default ``True``. throw : bool, optional Return a new closed object instead of modifying in place. Default ``False``. close_index : int, optional Index of the point to close back to. Default ``0``. Returns ------- None Notes ----- Closing updates: ``points``, ``edges``, ``cpairs``, ``ppairs``, ``empoints``, ``pindices``, ``pmids``, ``pmid_pairs``, and ``self.closed``. Examples -------- .. code-block:: python from upxo.geoEntities.muledge2d import muledge2d cpairs_list = [[[1, 1], [-1, 1]], [[-1, 1], [-1, -1]], [[-1, -1], [1, -1]]] me = muledge2d(method='cpairs_list', ordered=True, closed=False, cpairs_list=cpairs_list, lean='ignore') me.make_ring() me.is_ring() """ if saa: if self.ordered: # 1. Close the points self.points.append(self.points[0]) # 2. Close the edges self.edges.append(self.edges[0]) # 3. Close the cpairs self.cpairs = np.append(self.cpairs, np.array([[self.cpairs[-1][1], self.cpairs[0][0]]] ), axis=0 ) # 4. Close the ppairs self.ppairs.append([self.ppairs[-1][1], self.ppairs[0][0]]) # 5. Close the empoints self.empoints.append(self.empoints[0]) # 6. Close the pindices if not self.ordered: self.pindices.append[0] # 7. Close the pmids self.pmids.append(self.pmids[0]) # 8. Close the pmid_pairs self.pmid_pairs.append([self.pmid_pairs[-1][1], self.pmid_pairs[0][0]]) # 9. Update the closed self.closed = True if saa: if not self.ordered: pass
[docs] def is_ring(self, fast=True): """ Return ``True`` if this multi-edge forms a closed ring. Parameters ---------- fast : bool, optional When ``True`` (default), compare point memory ids (fast). When ``False``, compare point coordinate equality. Returns ------- bool ``True`` if the first and last points coincide. Notes ----- A ring requires: all edges share the same sense; edges are adjacent from ``pnta`` of edge 0 to ``pntb`` of edge −1; and ``pnta`` of edge 0 equals ``pntb`` of edge −1. """ if self.ordered: if fast: if self.pmids[0] == self.pmids[-1]: to_return = True else: to_return = False else: if self.points[0] == self.points[-1]: to_return = True else: to_return = False return to_return
# #########################################################
[docs] def pop_point_by_index(self, n): """ Delete the n-th point from the multi-edge chain. Parameters ---------- n : int Index of the point to remove (within ``range(len(self.points))``). Returns ------- None Examples -------- .. code-block:: python from upxo.geoEntities.muledge2d import muledge2d clist = [[0.0, 0.0], [0.5, 0.0], [1.0, 0.0], [1.5, 0.5], [1.0, 1.01], [2.0, 2.0], [2.5, 0.0]] me = muledge2d(method='clist', ordered=True, closed=False, clist=clist, make_mp=True, lean='ignore') me.pop_point_by_index(0) """ ''' PROOF OF CONCEPT CODE - 1 ------------------------- edges = [edge2d(pnta=point2d(0.00, 0.00), pntb=point2d(0.50, 0.00)), edge2d(pnta=point2d(0.50, 0.00), pntb=point2d(1.00, 0.00)), edge2d(pnta=point2d(1.00, 0.00), pntb=point2d(1.50, 0.50)), edge2d(pnta=point2d(1.50, 0.50), pntb=point2d(1.00, 1.01)), edge2d(pnta=point2d(1.00, 1.01), pntb=point2d(2.00, 2.00)), edge2d(pnta=point2d(2.00, 2.00), pntb=point2d(2.50, 0.00)), ] pmid_pairs = np.array([[111, 222], [222, 333], [333, 444], [444, 555], [555, 666], [666, 777] ]) to_pop = 222 all_pmids = np.hstack((pmid_pairs.T[0], pmid_pairs.T[1])) pmids_unique = np.sort(list(set(all_pmids))) connections = [np.count_nonzero(all_pmids==i) for i in pmids_unique] pmid_connections = np.array([pmids_unique, connections]) for i in np.array(np.where(pmid_pairs==to_pop)).T: pmid_pairs[i[0]][i[1]] = -1 # Get number of points to keep intact in each edge _where_ = np.where keep_n = [] for pair in pmid_pairs: keep_n.append(2-list(pair).count(-1)) print(keep_n) # Identify which edges have points to be edited edges_to_edit = [i for i,_ in enumerate(keep_n) if _!=2] # Identify which edges have points to be edited along with points edit_edge = [] for i, pair in enumerate(pmid_pairs): edit_edge.append(list(np.where(pair==-1)[0])) # Start editing the edges remove = [] if edges_to_edit: for en in edges_to_edit: if en < len(edges)-1: edge = edges[en] point_to_edit = edit_edge[en][0] if edit_edge[en+1]: next_edge = edges[en+1] next_point_to_edit = edit_edge[en+1][0] if point_to_edit == 0: if next_point_to_edit == 0: edge.pnta.x = next_edge.pntb.x edge.pnta.y = next_edge.pntb.y elif next_point_to_edit == 1: edge.pnta.x = next_edge.pnta.x edge.pnta.y = next_edge.pnta.y elif point_to_edit == 1: if next_point_to_edit == 0: edge.pntb.x = next_edge.pntb.x edge.pntb.y = next_edge.pntb.y elif next_point_to_edit == 1: edge.pntb.x = next_edge.pnta.x edge.pntb.y = next_edge.pnta.y else: remove.append(en) else: if edit_edge[en]: remove.append(en) edges for i, rem_n in enumerate(remove, start=0): del edges[rem_n-i] edges PROOF OF CONCEPT CODE - 2 ------------------------- clist = [[0.00, 0.00], [0.50, 0.00], [1.00, 0.00], [1.50, 0.50], [1.00, 1.01], [2.00, 2.00], [2.50, 0.00] ] from muledge2d import muledge2d me = muledge2d(method='clist', ordered=True, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore' ) me.edges n = 1 me.points PMID = me.pmids[n] to_pop = PMID pmid_pairs = np.array(deepcopy(me.pmid_pairs)) for i in np.array(np.where(pmid_pairs==to_pop)).T: pmid_pairs[i[0]][i[1]] = -1 # Get number of points to keep intact in each edge _where_ = np.where keep_n = [] for pair in pmid_pairs: keep_n.append(2-list(pair).count(-1)) print(keep_n) # Identify which edges have points to be edited edges_to_edit = [i for i,_ in enumerate(keep_n) if _!=2] # Identify which edges have points to be edited along with points edit_edge = [] for i, pair in enumerate(pmid_pairs): edit_edge.append(list(np.where(pair==-1)[0])) # Start editing the edges nedges = len(me.edges) edges = me.edges remove = [] if edges_to_edit: for en in edges_to_edit: if en < nedges-1: edge = me.edges[en] point_to_edit = edit_edge[en][0] if edit_edge[en+1]: next_edge = me.edges[en+1] next_point_to_edit = edit_edge[en+1][0] if point_to_edit == 0: if next_point_to_edit == 0: edge.pnta.x = next_edge.pntb.x edge.pnta.y = next_edge.pntb.y elif next_point_to_edit == 1: edge.pnta.x = next_edge.pnta.x edge.pnta.y = next_edge.pnta.y elif point_to_edit == 1: if next_point_to_edit == 0: edge.pntb.x = next_edge.pntb.x edge.pntb.y = next_edge.pntb.y elif next_point_to_edit == 1: edge.pntb.x = next_edge.pnta.x edge.pntb.y = next_edge.pnta.y else: remove.append(en) else: if edit_edge[en]: remove.append(en) for i, rem_n in enumerate(remove, start=0): del me.edges[rem_n-i] ''' _where_ = np.where # ############################################################## # EDIT --> MPOINT # Update mpoint self.mpoint.pop_points_indices(n) # ############################################################## # EDIT --> POINTS del self.points[n] # ############################################################## # EDIT --> EDGES # Get the mid of point to be removed to_pop = self.pmids[n] # ----------------------------------------- # Copy pmid_pairs and mark points to be removed from copy import deepcopy pmid_pairs = np.array(deepcopy(self.pmid_pairs)) for i in np.array(_where_(pmid_pairs == to_pop)).T: pmid_pairs[i[0]][i[1]] = -1 # ----------------------------------------- # Get number of points to keep intact in each edge keep_n = [] for pair in pmid_pairs: keep_n.append(2-list(pair).count(-1)) # ----------------------------------------- # Identify which edges have points to be edited edges_to_edit = [i for i, _ in enumerate(keep_n) if _ != 2] # ----------------------------------------- # Identify which edges have points to be edited along with points edit_edge = [] for i, pair in enumerate(pmid_pairs): edit_edge.append(list(_where_(pair == -1)[0])) # ----------------------------------------- # Start editing the edges remove = [] if edges_to_edit: for en in edges_to_edit: if en < len(self.edges)-1: point_to_edit = edit_edge[en][0] if edit_edge[en+1]: next_edge = self.edges[en+1] next_point_to_edit = edit_edge[en+1][0] if point_to_edit == 0: this_empoint = self.empoints[en].points[0] if next_point_to_edit == 0: self.edges[en].pnta.x = next_edge.pntb.x self.edges[en].pnta.y = next_edge.pntb.y this_empoint.x = next_edge.pntb.x this_empoint.y = next_edge.pntb.y elif next_point_to_edit == 1: self.edges[en].pnta.x = next_edge.pnta.x self.edges[en].pnta.y = next_edge.pnta.y this_empoint.x = next_edge.pnta.x this_empoint.y = next_edge.pnta.y elif point_to_edit == 1: this_empoint = self.empoints[en].points[1] if next_point_to_edit == 0: self.edges[en].pntb.x = next_edge.pntb.x self.edges[en].pntb.y = next_edge.pntb.y this_empoint.x = next_edge.pntb.x this_empoint.y = next_edge.pntb.y elif next_point_to_edit == 1: self.edges[en].pntb.x = next_edge.pnta.x self.edges[en].pntb.y = next_edge.pnta.y this_empoint.x = next_edge.pnta.x this_empoint.y = next_edge.pnta.y else: remove.append(en) else: if edit_edge[en]: remove.append(en) # ----------------------------------------- for i, rem_n in enumerate(remove, start=0): del self.edges[rem_n-i] del self.empoints[rem_n-i] for empoint in self.empoints: empoint.recompute(flag_mids=True, flag_dist=True, flag_basics=True, flag_npoint=True, flag_locxy=True, flag_centroid=True) # ############################################################## # RE-BUILD --> pmids del self.pmids[n] # RE-BUILD --> clist del self.clist[n] # RE-BUILD --> pindices self.pindices = [[i, i+1] for i in range(len(self.clist)-1)] # RE-BUILD --> eindices self.eindices = deque([i for i, _ in enumerate(self.points)]) # RE-BUILD --> pmid_pairs self.pmid_pairs = [[self.pmids[i[0]], self.pmids[i[1]]] for i in self.pindices]
[docs] def pop_points_by_indices(self, n): """ Delete multiple points from the multi-edge chain by index. Parameters ---------- n : int or list of int Index or list of indices of points to remove. Returns ------- None Examples -------- .. code-block:: python from upxo.geoEntities.muledge2d import muledge2d clist = [[0.00, 0.00], [0.50, 0.00], [1.00, 0.00], [1.50, 0.50], [1.00, 1.01], [2.00, 2.00], [2.50, 0.00]] me = muledge2d(method='clist', ordered=True, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore') n = [0, 2, 4, 6] me.pop_points_by_indices(n) """ if type(n) not in dth.dt.ITERABLES: n = [n] if np.prod(np.array(n) >= 0) == 1 and max(n) <= len(self.points): self.pop_point_by_index(n[0]) if len(n) > 1: for i, _ in enumerate(n[1:], start=1): self.pop_point_by_index(n[i]-i) else: print('Enter valid indices')
[docs] def pop_point_by_coord(self, coord=None, tdist=0.1): """ Delete point object at coord from the mul-edge object Parameters ---------- coord : list, tuple, deque, or numpy.ndarray A single co-ordinate pair Returns ------- None. Examples -------- .. code-block:: python from upxo.geoEntities.muledge2d import muledge2d clist = [[0.00, 0.00], [0.50, 0.00], [1.00, 0.00], [1.50, 0.50], [1.00, 1.01], [2.00, 2.00], [2.50, 0.00]] me = muledge2d(method='clist', ordered=True, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore', plean='ignore', mplean='ignore', elean='ignore', melean='ignore') me.pop_point_by_coord(coord=[0.1, 0.0], tdist=1.0) """ _point_ = point2d(x=coord[0], y=coord[1], lean='ignore') equalities = list(_point_.__eq__(self.points, tdist=tdist, use_self_tdist=False, point_types='upxo' ) ) if any(equalities): npoints = equalities.count(True) point_loc = list(np.where(np.array(equalities))[0]) if npoints >= len(self.points)-2: print('Too many points within tolerance distance') print('No points will be removed') print('You may try reducing tdist if not already zero') else: self.pop_points_by_indices(point_loc) else: print('Point at coord is not part of this muledge2d') print('No points have been removed')
# #########################################################
[docs] def insert_point_by_index(self, obj, index=None): """ Insert one point into the multi-edge at the given index position. Parameters ---------- obj : Point2d or list of float Point to insert. A coordinate pair ``[x, y]`` is automatically promoted to a Point2d. index : int, optional Position in the point list where the new point will be inserted. Default is None. Returns ------- None Examples -------- .. code-block:: python from upxo.geoEntities.muledge2d import muledge2d from upxo.geoEntities.point2d import Point2d clist = [[0, 0], [0.5, 0], [1, 0], [1.5, 0.5]] me = muledge2d(method='clist', ordered=True, clist=clist, lean='ignore') me.insert_point_by_index(Point2d(0, 5), index=1) """ # Check validity of input input_valid = False if obj.__class__.__name__ == 'point2d': input_valid = True elif dth.IS_CPAIR(obj): obj = point2d(obj[0], obj[1], lean=self.plean) input_valid = True # Proceed only if point does not coincide with existing point(s), # and inpuyt is valid if not any(obj.distance(otype='up2d_list', obj=self.points) <= self.EPS) and input_valid: # Update the mul-point object self.mpoint.__add__(obj=[obj], indices=index) # Points would have updated automatically # Update the clist self.clist.insert(index, [obj.x, obj.y]) # ======================== self.dbbuild_pmid() self.pindices = [[i, i+1] for i in range(len(self.clist)-1)] self.eindices = deque([i for i, _ in enumerate(self.points)]) # ======================== self.pmid_pairs = [[self.pmids[i[0]], self.pmids[i[1]]] for i in self.pindices] # ############################################################## # UPDATE EDGES io = id(obj) # 1. Identify edge and point to edit CP = [] # Location 0 is the edge location # Location 1 is the point location in the edge for i, pmp in enumerate(self.pmid_pairs): if io in pmp: CP.append([i, [pmp[0] == io, pmp[1] == io].index(True) ]) # 2. Update edge and insert new edge # NOTE: ALL CASES HAVE NOT BEEN DEALT WITH. SHOULD BE OK FOR NOW if CP[0][1] == 1: #print(len(self.edges)) #print(self.edges[CP[1][0]]) if CP[1][0] < len(self.edges): # Update edge self.edges[CP[0][0]].pntb, NE = obj, edge2d(pnta=obj, pntb=self.edges[CP[1][0]].pnta) elif CP[1][0] == len(self.edges): self.edges[CP[0][0]].pntb, NE = obj, edge2d(pnta=obj, pntb=self.edges[CP[0][0]].pntb) # Update properties of modified edge self.edges[CP[0][0]].post_deformation_updates() self.edges.insert(CP[1][0], NE) # ############################################################## # RE-BUILD --> pindices self.pindices = [[i, i+1] for i in range(len(self.clist)-1)] # ------------------------------- # RE-BUILD --> eindices self.eindices = deque([i for i, _ in enumerate(self.points)]) # ------------------------------- else: if input_valid: print('Input point already exists in the Multi-Edge') else: print('Please enter point in either UPXO or coordinate pair format')
# #########################################################
[docs] def fine(self, level): """ Subdivide all edges by inserting their centroids, ``level`` times. Parameters ---------- level : int Number of subdivision passes. Must be in ``[0, 2]``. Returns ------- None Examples -------- .. code-block:: python from upxo.geoEntities.muledge2d import muledge2d me = muledge2d(method='clist', ordered=True, clist=[[0, 0], [1, 0], [2, 0]], lean='ignore') me.fine(level=1) """ if type(level) == int and level >= 0 and level <= 2: for _ in range(level): centroids = self.centroids insertion_locations = list(range(1, len(self.edges)+1)) for i, loc in enumerate(insertion_locations): if i == 0: self.insert_point_by_index(centroids[i], index=loc) else: self.insert_point_by_index(centroids[i], index=loc+insertion_locations[i-1]) else: print('Please enter valid level. Max level allowed = 2')
[docs] def insert_point(self, obj, index=None, insertion_check='edge'): """ Insert a single point into the multi-edge at the specified position. Parameters ---------- obj : Point2d The point to insert. index : int, optional Position at which to insert the point. Default is ``None``. insertion_check : str, optional Strategy used to validate the insertion location. ``'edge'`` checks containment on an existing edge; ``'index'`` uses the raw index directly. Default is ``'edge'``. Returns ------- None Limitations ----------- - Implementation is incomplete; only the multi-point bookkeeping is partially done. Edge rebuilding is a stub (``pass``). - ``insertion_check='edge'`` only works for non-intersecting edge chains. Examples -------- .. code-block:: python from upxo.geoEntities.point2d import Point2d from upxo.geoEntities.muledge2d import muledge2d clist = [[0.0, 0.0], [0.5, 0.0], [1.0, 0.0], [1.5, 0.5]] me = muledge2d(method='clist', ordered=True, closed=False, clist=clist, make_mp=True, make_emp=True, lean='ignore') me.insert_point(Point2d(0.25, 0.0), index=1) """ # --------------------------- # Number of points in the me.mpoint mpoint_npoints_bf = len(me.mpoint) # --------------------------- # Update mul-point me.mpoint.__add__([obj], indices=[index]) # --------------------------- # Number of points in the me.mpoint mpoint_npoints_af = len(me.mpoint) # --------------------------- # Check if all points were added if mpoint_npoints_af == mpoint_npoints_bf+1: # The point was added for e in me.edges: if not e.contains_point(obj=obj, method='parallelity', tdist=0.0)[0]: # Point pass elif mpoint_npoints_af == mpoint_npoints_bf: # Point coord was already in me.mpoint and was not added pass # --------------------------- # saa points me.points = me.mpoint.points # --------------------------- # Update clist me.clist = deque([[p.x, p.y] for p in me.points]) # --------------------------- npoints = len(me.clist) # --------------------------- # RE-BUILD --> pindices me.pindices = [[i, i+1] for i in range(npoints-1)] # --------------------------- me.pmids = deque([id(p) for p in me.points]) # --------------------------- if insertion_check == 'edge': # Works only for non-intersecting edges pass if insertion_check == 'index': pass # --------------------------- # --------------------------- # --------------------------- # Update points # Update pindices # Update ppairs # Update clist and Update cpairs # Update pmids # Update pmid_pairs # Update plean # Update mpoint # Update edges # Update elean # Update empoints # Update the ring object pass
[docs] def insert_coord_at(self, coord, indices=[0, 1]): """Insert a raw coordinate into the chain at the given edge indices. Not yet implemented.""" # Update clist and Update cpairs # Update points # Update pindices # Update ppairs # Update pmids # Update pmid_pairs # Update plean # Update mpoint # Update edges # Update elean # Update empoints # Update the ring object raise NotImplementedError("insert_coord_at is not yet implemented.")
[docs] def insert_point_bw(self, k=0.5): """Insert a point at fractional position ``k`` along each edge. Not yet implemented.""" # Make coord # Make point object # Update points # Update pindices # Update ppairs # Update clist and Update cpairs # Update pmids # Update pmid_pairs # Update plean # Update mpoint # Update edges # Update elean # Update empoints # Update the ring object raise NotImplementedError("insert_point_bw is not yet implemented.")
# #########################################################
[docs] def move_nthpoint(self, n=0, xyincr=[0, 0], overlap_action='exit'): """ Translate the n-th point by a coordinate increment. Parameters ---------- n : int, optional Index of the point to move. Default ``0``. xyincr : list of float, optional Translation increment ``[dx, dy]``. Default ``[0, 0]``. overlap_action : str, optional Behaviour when the new position coincides with an existing point. ``'exit'`` rejects the move; ``'remove_edge2'`` removes the resulting dangling edge. Default ``'exit'``. Returns ------- None Limitations ----------- - ``'remove_edge2'`` branch is not yet fully implemented. """ # Move the point object # Update the cpairs # Check if new point coincides with any in self.points # If so, re-compute the multi-point # Update points # Update pindices # Update ppairs # Update clist and Update cpairs # Update pmids # Update pmid_pairs # Update plean # Update mpoint # Update edges # Update elean # Update empoints # Update the ring object n = 0 _point_ = me.points[n].translate(method='xyincr', xyincr=[1,0], saa=False, make_new=True, throw=True) overlaps = [_point_ == point for point in me.points] if not any(overlaps): self.points[n].translate(method='xyincr', xyincr=xyincr, saa=True, make_new=False, throw=False) self.clist[n] = [self.points[n].x, self.points[n].y] else: if overlap_action == 'exit': print('New position overlaps with an existing point.') print('Move operation rejected') print('Please choose overlap_action to replace, if you') print(' choose to continue updating the muledge') elif overlap_action == 'remove_edge2': print('Point belonging to the current edge will be edited') print(' Resulting hanging edge will be removed') # ------------------------------------- overlap_pmids = [me.pmids[i] for i in np.where(overlaps)[0]]
# ------------------------------------- # pop points at np.where(overlaps)[0] locations
[docs] def move_pointat(self, coord=[0, 0]): """Move the point at ``coord`` to a new position. Not yet implemented.""" # Update points # Update pindices # Update ppairs # Update clist and Update cpairs # Update pmids # Update pmid_pairs # Update plean # Update mpoint # Update edges # Update elean # Update empoints # Update the ring object raise NotImplementedError("move_pointat is not yet implemented.")
# #########################################################
[docs] def explode(self, k, method='centroid'): """Explode the chain outward from its centroid by factor ``k``. Not yet implemented.""" # . Explode the multi-point # . Update self.cpairs form multi-point # . Update all multi-point properties. points need not be updated. # Update mpoint # Update points # Update pindices # Update ppairs # Update clist and Update cpairs # Update pmids # Update pmid_pairs # Update plean # Update edges # Update elean # Update empoints # Update the ring object raise NotImplementedError("explode is not yet implemented.")
[docs] def move(self): """Translate the entire multi-edge chain. Not yet implemented.""" # Translate the multi-point # Update cpairs from multi-point # Update mpoint # Update points # Update pindices # Update ppairs # Update clist and Update cpairs # Update pmids # Update pmid_pairs # Update plean # Update edges # Update elean # Update empoints # Update the ring object raise NotImplementedError("move is not yet implemented.")
[docs] def stretch(self): """Stretch the chain along an axis. Not yet implemented.""" # Stretch the multi-point # Update cpairs from multi-point # Update mpoint # Update points # Update pindices # Update ppairs # Update clist and Update cpairs # Update pmids # Update pmid_pairs # Update plean # Update edges # Update elean # Update empoints # Update the ring object raise NotImplementedError("stretch is not yet implemented.")
# #########################################################
[docs] def plotme(self, i=None, j=None, show_coord=True): """ Plot a slice of the edge chain using Matplotlib. Parameters ---------- i : int, optional Start index of the edge slice. Defaults to ``0``. j : int, optional End index (exclusive) of the edge slice. Defaults to ``len(self.edges)``. show_coord : bool, optional Annotate each node with its ``(x, y)`` coordinates. Default ``True``. """ if not i: i = 0 if not j: j = len(self.edges) if type(i) == int and i >= 0 and i < len(self.edges): if j and type(j) == int and j <= len(self.edges): from itertools import islice edges = list(islice(self.edges, i, j)) for e in edges: plt.plot([e.pnta.x, e.pntb.x], [e.pnta.y, e.pntb.y], 'bo', linestyle='-') if show_coord: for _x_, _y_ in zip(self.mpoint.locx[i:j+1], self.mpoint.locy[i:j+1]): plt.text(_x_, _y_, '(%4.2f, %4.2f)' % (_x_, _y_), horizontalalignment='center', verticalalignment='bottom' ) else: print('Enter valid slice indices') else: print('Enter valid slice indices')
[docs] def plotmp(self): """Plot all multi-point nodes with coordinate annotations using Matplotlib.""" plt.plot(self.mpoint.locx, self.mpoint.locy, 'ks') for _x_, _y_ in zip(self.mpoint.locx, self.mpoint.locy): plt.text(_x_, _y_, '(%4.2f, %4.2f)' % (_x_, _y_), horizontalalignment='center', verticalalignment='bottom' )