From 8eebdf0bd3bafa45d17d388f6fa0831f77d54538 Mon Sep 17 00:00:00 2001 From: Dan Patterson Date: Fri, 6 Oct 2023 19:23:23 -0400 Subject: [PATCH] Oct 2023 backup of small changes --- arcpro_npg/npg/npg/npGeo.py | 30 +++- arcpro_npg/npg/npg/npg_clip_split.py | 2 +- arcpro_npg/npg/npg/npg_erase.py | 255 ++++++++++++++------------- arcpro_npg/npg/npg/npg_geom.py | 2 +- arcpro_npg/npg/npg/npg_helpers.py | 17 +- arcpro_npg/npg/npg/npg_utils.py | 34 +++- 6 files changed, 197 insertions(+), 143 deletions(-) diff --git a/arcpro_npg/npg/npg/npGeo.py b/arcpro_npg/npg/npg/npGeo.py index 933dcba..089e41f 100644 --- a/arcpro_npg/npg/npg/npGeo.py +++ b/arcpro_npg/npg/npg/npGeo.py @@ -31,9 +31,6 @@ from npg.npg_helpers import ( _angles_3pnt_, _area_centroid_, _bit_area_, _bit_crossproduct_, _bit_min_max_, _bit_length_, _rotate_, polyline_angles, uniq_1d) -# import npg_io -# import npg_prn - from npg.npgDocs import ( Geo_hlp, array_IFT_doc, dirr_doc, shapes_doc, parts_doc, get_shapes_doc, @@ -57,7 +54,7 @@ __all__ = [ 'Geo', 'is_Geo', - 'roll_coords', 'array_IFT', 'arrays_to_Geo', + 'roll_coords', 'roll_arrays', 'array_IFT', 'arrays_to_Geo', 'Geo_to_arrays', 'Geo_to_lists', '_fill_float_array', 'is_Geo', 'reindex_shapes', 'remove_seq_dupl', 'check_geometry', 'dirr' @@ -1050,10 +1047,31 @@ def polys_to_points(self, keep_order=True, as_structured=False): return uni def segment_polys(self, as_basic=True, shift_back=False, as_3d=False): - """Call `polys_to_segments`. See `od_pairs` also.""" + """Call `polys_to_segments`. + + See Also + -------- + `common_segments`, `od_pairs`, `fr_to_segments` and `to_fr_segments` + """ return geom.polys_to_segments(self, as_basic=as_basic, to_orig=shift_back, as_3d=as_3d) + def fr_to_segments(self, as_basic=True, shift_back=False, as_3d=False): + """Return `from-to` points segments for poly* features as 2D array. + + Call `polys_to_segments`. See `segment_polys`. + """ + return geom.polys_to_segments(self, as_basic=as_basic, + to_orig=shift_back, as_3d=as_3d) + + def to_fr_segments(self, as_basic=True, shift_back=False, as_3d=False): + """Return to-from points segments to compare to fr_to_segments.""" + b_vals = self.bits + # -- Do the concatenation + to_fr = np.concatenate( + [np.concatenate((b[1:], b[:-1]), axis=1) for b in b_vals], axis=0) + return to_fr + def close_polylines(self, out_kind=1): """Produce closed-loop polylines (1) or polygons (2) from polylines. @@ -2060,4 +2078,4 @@ def _sub_(arr, cols): # ---- == __main__ section == if __name__ == "__main__": """optional location for parameters""" - # in_fc = r"C:\Git_Dan\npgeom\Project_npg\npgeom.gdb\Polygons" + # in_fc = r"C:\arcpro_npg\Project_npg\npgeom.gdb\Polygons" diff --git a/arcpro_npg/npg/npg/npg_clip_split.py b/arcpro_npg/npg/npg/npg_clip_split.py index 06ebeb3..52542ef 100644 --- a/arcpro_npg/npg/npg/npg_clip_split.py +++ b/arcpro_npg/npg/npg/npg_clip_split.py @@ -45,7 +45,7 @@ import numpy as np import npg # noqa from npg.npg_helpers import a_eq_b -from npg_bool_hlp import _del_seq_pnts_, prep_overlay +from npg.npg_bool_hlp import _del_seq_pnts_, prep_overlay from npg.npg_plots import plot_polygons # noqa ft = {"bool": lambda x: repr(x.astype(np.int32)), diff --git a/arcpro_npg/npg/npg/npg_erase.py b/arcpro_npg/npg/npg/npg_erase.py index d4997e5..0968f7d 100644 --- a/arcpro_npg/npg/npg/npg_erase.py +++ b/arcpro_npg/npg/npg/npg_erase.py @@ -10,7 +10,7 @@ ---- Script : - npg_boolean.py + npg_erase.py Author : Dan_Patterson@carleton.ca @@ -18,7 +18,7 @@ ``_. Modified : - 2023-04-15 + 2023-09-26 Purpose ------- @@ -36,8 +36,9 @@ import copy import numpy as np import npg # noqa -from npg_bool_hlp import add_intersections # prep_overlay +from npg.npg_bool_hlp import add_intersections, _del_seq_pnts_ # prep_overlay from npg.npg_plots import plot_polygons # noqa +from npg.npg_prn import prn_ # noqa ft = {"bool": lambda x: repr(x.astype(np.int32)), "float_kind": '{: 6.2f}'.format} @@ -49,11 +50,25 @@ script = sys.argv[0] __all__ = ['erase_poly'] -__helpers__ = [] +__helpers__ = ['cut_pairs'] # ---- (1) difference polygons # +def cut_pairs(arr): + """Return cut lines from `onConP` or `id_plcl`.""" + c_segs = [] + p_segs = [] + for cn, v in enumerate(arr[1:, 0], 0): + prev = arr[cn, 0] + dff = v - prev + if dff == 1: + vals = [arr[cn, 1], arr[cn + 1, 1]] + c_segs.append([prev, v]) + p_segs.append(vals) + return c_segs, p_segs + + def erase_poly(poly, clp, as_geo=True): """Return the symmetrical difference between two polygons, `poly`, `clp`. @@ -116,27 +131,6 @@ def _out_p_(p_c, p_seen, p_outside): return pl_n[vals] return [] - def _chk_(_p, _c, _case): - """Boolean check of poly or clip points. - - Parameters - ---------- - _p, _c : integer - Previous or current point id values. - _case : list - Inside or outside point lists. - - Notes - ----- - This function is used to see if the previous (`_p`) or current (`_c`) - poly or clip points are inside or outside their counterpart. The same - function can be used for either case. - """ - for lst in _case: - if _p in lst and _c in lst: - return True, lst - return False, [] - def in_out_chk(_n, _p, _c, _seen, _outside, _inside): """Last ditch check in case p_p and p_c are separated by a segment. @@ -167,29 +161,6 @@ def in_out_chk(_n, _p, _c, _seen, _outside, _inside): in_bits.append(vals) return out_bits, in_bits - def on_pairs(col): - """Return sequential ids from the intersections not in or out.""" - segs = [] - for cn, v in enumerate(col[1:], 0): - prev = col[cn] - dff = v - prev - if dff == 1: - segs.append([prev, v]) - return segs - - def cut_pairs(arr): - """Return cut lines from `onConP` or `id_plcl`.""" - c_segs = [] - p_segs = [] - for cn, v in enumerate(arr[1:, 0], 0): - prev = arr[cn, 0] - dff = v - prev - if dff == 1: - vals = [arr[cn, 1], arr[cn + 1, 1]] - c_segs.append([prev, v]) - p_segs.append(vals) - return c_segs, p_segs - # -- Returns the intersections, the rolled input polygons, the new polygons # and how the points in both relate to one another. result = add_intersections(poly, clp, @@ -201,14 +172,19 @@ def cut_pairs(arr): # Get the intersections, new polys, points inside and outside and # x_pnt ids from `add_intersections`. Swap the order of the last. w0 = np.argsort(id_plcl[:, 1]) # get the order and temporarily sort + z = np.zeros((id_plcl.shape[0], 4), dtype=int) + z[:, :2] = id_plcl[:, [1, 0]][w0] + z[1:, 2] = z[1:, 0] - z[:-1, 0] + z[1:, 3] = z[1:, 1] - z[:-1, 1] + onConP = np.copy(z) + # onConP = id_plcl[:, [1, 0]][w0] # slice to rearrange the columns # -- cut lines, where one crosses the other - onConP = id_plcl[:, [1, 0]][w0] # slice to rearrange the columns # -- two point cut lines, which cross the other polygon - c_cut0, p_cut0 = cut_pairs(onConP) # use onConP col 0 since it issorted + c_cut0, p_cut0 = cut_pairs(onConP[:, :2]) # use onConP since it issorted p_cut1, c_cut1 = cut_pairs(id_plcl) # use id_plcl col 0 to save a sort c_cut = c_cut0 + c_cut1 # sorted(c_cut0 + c_cut1, key=lambda l:l[0]) p_cut = p_cut0 + p_cut1 # sorted(p_cut0 + p_cut1, key=lambda l:l[0]) - # -- cut lines that are more than two points and are either inside or + # -- cut lines that are more than two points are either inside or # outside the other polygon p_outside = copy.deepcopy(p_out) p_inside = copy.deepcopy(p_in) @@ -217,15 +193,20 @@ def cut_pairs(arr): # # Determine preceeding points to first clip. out = [] # p_seen, c_seen = [], [], [] - prev = onConP[0] # -- set the first `previous` for enumerate + prev = onConP[0, :2] # -- set the first `previous` for enumerate p_seen, c_seen = [], [] - in_segs = [] # collect `clipping` segments to use for clip. - kind_ = [] # -1 symmetrical diff, 0 erase, 1 clip, 2 hole - for cnt, p in enumerate(onConP[1:9], 1): # enumerate fromonConP[1:] - c_c, p_c = p # current ids, this is an intersection point + in_clp = [] # collect `clipping` segments to use for clip. + kind_ = [] # see below + # -1 symmetrical diff : features don't overlap clip out, poly out + # 0 erase : poly outside of clip + # 1 clip : clip in, poly in + # 2 hole : neither + # 3 identity : features or parts that overlap + for cnt, row in enumerate(onConP[1:], 1): # enumerate fromonConP[1:] + # current ids and differences... this is an intersection point + c_c, p_c, d0, d1 = row # row[:2], row[2], row[3] c_p, p_p = prev # previous ids - d0, d1 = p - prev # differences in ids - sub, bits, sub0, sub1 = [], [], [], [] + sub, bts, sub0, sub1 = [], [], [], [] # -- chk0, chk1, chk2, chk3 = [False, False, False, False] c_out_f = sum(c_outside, []) # flatten list of sub lists @@ -249,7 +230,10 @@ def cut_pairs(arr): # -- if d0 == 1: # this is a `cutting` segment inside `c_cut` _clp_ = cl_n[[c_p, c_c]] - in_segs.append(_clp_) + if chk2: + in_clp += [_clp_] + elif chk3: + in_clp += [pl_n[p_p: p_c + 1]] # poly inside clip # -- if d1 > 1: # poly inside and outside check r_ = in_out_chk(pl_n, p_p, p_c, p_seen, p_outside, p_inside) @@ -262,38 +246,36 @@ def cut_pairs(arr): kind_.append(0) if len(in_bits) > 0: # -- inside bits tmp = sum(in_bits, []) - if p_p == tmp[-1]: # check to see if it is start or end + if tmp[-1] - tmp[0] == 1: # edgly1-eclip last clp [76, 77] + bts = [] + elif p_p == tmp[-1]: # check to see if it is start or end bts = [pl_n[tmp]] + [_clp_] + [pl_n[tmp[0]][None, :]] else: bts = [_clp_] + [pl_n[tmp[::-1]]] - out.append(np.concatenate(bts, axis=0)) - p_seen += tmp - kind_.append(1) - # if chk2: # poly segment outside clp - # sub1 = _out_p_(p_c, p_seen, p_outside) - # # in_segs.append(_clp_) - # sub = np.concatenate((sub1, _clp_[::-1]), axis=0) - # kind_.append(0) - # elif chk3: # ply segment inside clp - # sub1 = _in_p_(p_c, p_seen, p_inside) - # # in_segs.append(sub1) - # sub = np.concatenate((_clp_, sub1[::-1]), axis=0) - # kind_.append(-1) + if bts: # diff > 1 + out.append(np.concatenate(bts, axis=0)) + p_seen += tmp + kind_.append(1) # -- elif d1 < 0: # not common, but accounted for (eg. E, d0_ polys) # fix this section if p_c + 1 in p_seen: # closes possible double cut triangle if [p_c, p_c + 1] in p_cut: to_add = pl_n[[p_c, p_c + 1, p_p, p_c]] + kind_.append(1) out.append(to_add) - bits = _out_p_(max([p_p, p_c]), p_seen, p_outside) - if len(bits) > 0: - s0 = np.concatenate((bits, bits[0][None, :]), axis=0) - kind_.append(-1) + bts = _out_p_(max([p_p, p_c]), p_seen, p_outside) + if len(bts) > 0: + # in_clp.extend([]) # fix !!! + s0 = np.concatenate((bts, bts[0][None, :]), axis=0) + kind_.append(0) # was -1 which is wrong out.append(s0) # -- try this for cnt = 7 if min([p_p, p_c]) in p_seen: # or [p_p - 1, p_p] in p_on + # in_clp.append([]) # fix !!! + kind_.append(1) # fix !!! s1 = np.concatenate((_clp_, pl_n[[p_p - 1, p_p]]), axis=0) + in_clp.append(s1) # ?? 2023-09-07 for E,d0_ first clp out.append(s1) # -- elif d1 == 1: # unexpected, but accounted for @@ -303,10 +285,11 @@ def cut_pairs(arr): elif d0 > 1: if chk0: # clp seg is outside sub0 = _out_c_(c_c, c_seen, c_outside) - in_segs.append(sub0) # 5-9 + # in_clp += [cl_n[[c_p, c_c]]] # add sorted(...) ?? + # in_clp.append(sub0) # 5-9 elif chk1: # clp seg is inside sub0 = _in_c_(c_c, c_seen, c_inside) - in_segs.append(sub0) + in_clp += [sub0] # -- if d1 < 0: # -- applies to E, d0_ because of wrapping crosses if chk0: # clip segment outside ply @@ -321,23 +304,22 @@ def cut_pairs(arr): if d1 == 1: # poly ids are sequential, clp is inside or outside sub1 = pl_n[[p_p, p_c]] if chk0: - in_segs.append(sub1) + in_clp += [sub1] sub = np.concatenate((sub0, sub1[::-1]), axis=0) + kind_.append(-1) elif chk1: - in_segs.append(sub0) sub = np.concatenate((sub1, sub0[::-1]), axis=0) - kind_.append(-1) + kind_.append(0) # ?? check # -- elif d1 > 1: # clp inside and outside check if chk0: # clip segment outside ply, chk3==True sub1 = _in_p_(p_c, p_seen, p_inside) if len(sub1) > 0: - in_segs.append(sub1) + in_clp += [sub1] # ?? sub1 = sub1[::-1] if len(sub1) > 0 else [] kind_.append(-1) elif chk1: # clip segment inside ply, chk2==True? sub1 = _out_p_(p_c, p_seen, p_outside) - # in_segs.append(sub0) if len(sub1) > 0: sub0 = sub0[::-1] if len(sub0) > 0 else [] kind_.append(0) @@ -345,58 +327,81 @@ def cut_pairs(arr): sub = np.concatenate((sub0, sub1), axis=0) if len(sub) > 0: out.append(sub) - # else: - # kind_ = kind_[:-1] # no sub, hence drop last `kind_` assignment # - val_lst = [cnt, prev, p, d0, d1, chk0, chk1, chk2, chk3, out] - print("cnt {}: {} {} {} {} {} {} {} {} \n {}".format(*val_lst)) - prev = p + """ + k_ = [] if len(kind_) == 0 else kind_[-1] + o_ = np.asarray([]) if len(out) == 0 else out[-1] + val_lst = [cnt, prev, row[:2], row[2:], chk0, chk1, chk2, chk3, k_] + print("cnt {}: {} {} {} {} {} {} {} {}".format(*val_lst)) + prn_(o_, deci=2, width=60, prefix=" ..") + """ + prev = [c_c, p_c] p_seen.append(p_c) c_seen.append(c_c) # # -- final = np.asarray(out, dtype='O') - clp_poly = np.concatenate([i for i in in_segs if len(i) > 0], axis=0) - idx = np.array(kind_) - erase_idx = np.nonzero(idx)[0] - symm_idx = np.nonzero(idx + 1)[0] - erase_poly = final[erase_idx] - symm_poly = final[symm_idx] - # -- if as_geo: return npg.arrays_to_Geo(final, kind=2, info=None, to_origin=False) - # return final, [out, subs, dups, pl_n, cl_n, xtras] - return final, clp_poly, erase_poly, symm_poly - - -def prePC(i0_, i1_, cN, j0_, j1_, pN, pinside, cinside): - """Determine pre `p` and `c` points.""" - preP, preC = [], [] - i1_ = 0 if i1_ in [i1_, cN] else i1_ # clp first/last point check - j1_ = 0 if j1_ in [j1_, pN] else j1_ # poly first/last point check + idx = np.array(kind_) + clp_ply = np.concatenate(in_clp, axis=0) # intersect as well + clp_ply = _del_seq_pnts_(clp_ply, True) + + idx_hole = np.nonzero(idx == 2)[0] # holes + idx_all = np.nonzero(idx < 2)[0] # symmetrical difference + idx_p_out = np.nonzero(idx == 0)[0] # pairwise erase + idx_c_out = np.nonzero(idx != 0)[0] # reverse pairwise erase + idx_c_in = np.nonzero(idx == 1)[0] # clp ?? reverse pairwise erase # - # -- add preceeding pinside points - if j0_ > 0 and j1_ < j0_: - preP = [m for m in range(j1_, j0_ + 1) if m in pinside] - # -- add preceeding cinside points - if i0_ > 0 and i1_ < i0_: - preC = [m for m in range(i1_, i0_ + 1) if m in cinside] - return preP, preC - - -def postPC(inC_0, cN, inP_0, pN, cinside, pinside): - """Determine post `p` and `c` points.""" - preC, preP = [], [] - # -- add trailing cinside points - if inC_0 != 0: - preC = [m for m in range(inC_0, cN + 1) if m in cinside] - # -- add trailing pinside points - if inP_0 != 0: - - preP = [m for m in range(inP_0, pN + 1) if m in pinside] - return preC, preP + hole_ply = final[idx_hole] if len(idx_hole) > 0 else [] + symm_ply = final[idx_all] + clp_ply2 = final[idx_c_in] if len(idx_c_in) > 0 else [] + erase_ply = final[idx_p_out] if len(idx_p_out) > 0 else [] + rev_erase = final[idx_c_out] if len(idx_c_out) > 0 else [] + # -- + return erase_ply, clp_ply, clp_ply2, hole_ply, symm_ply, rev_erase + +# -- Extras + + # def on_pairs(col): + # """Return sequential ids from the intersections not in or out.""" + # segs = [] + # for cn, v in enumerate(col[1:], 0): + # prev = col[cn] + # dff = v - prev + # if dff == 1: + # segs.append([prev, v]) + # return segs + + # def _chk_in_lst(_p, _c, _case): + # """Boolean check of poly or clip points. + + # Parameters + # ---------- + # _p, _c : integer + # Previous or current point id values. + # _case : list of lists + # Inside or outside point lists. + + # Notes + # ----- + # This function is used to see if the previous (`_p`) or current (`_c`) + # poly or clip points are inside or outside their counterpart. + # The same function can be used for either case. + # """ + # for lst in _case: + # if _p in lst and _c in lst: + # return True, lst + # return False, [] # ---- Final main section ---------------------------------------------------- if __name__ == "__main__": """optional location for parameters""" print(f"\nRunning... {script}\n") + + # out, final = clip_poly( + # all work as of 2023-03-19 + # out, final = clip_poly(edgy1, eclip) + # out, final = clip_poly(E, d0_) + # out, final = clip_poly(pl_, cl_) + # out, final = clip_poly(p00, c00) diff --git a/arcpro_npg/npg/npg/npg_geom.py b/arcpro_npg/npg/npg/npg_geom.py index d3440a9..b58e828 100644 --- a/arcpro_npg/npg/npg/npg_geom.py +++ b/arcpro_npg/npg/npg/npg_geom.py @@ -1233,7 +1233,7 @@ def in_hole_check(pnts, geo): return out -def _quad_(line): +def which_quad(line): """Return the quadrant a vector lies in. >>> q = _quad_(line) diff --git a/arcpro_npg/npg/npg/npg_helpers.py b/arcpro_npg/npg/npg/npg_helpers.py index 081f2ab..f6ee498 100644 --- a/arcpro_npg/npg/npg/npg_helpers.py +++ b/arcpro_npg/npg/npg/npg_helpers.py @@ -16,7 +16,7 @@ Dan_Patterson@carleton.ca Modified : - 2023-03-04 + 2023-10-02 Purpose ------- @@ -769,6 +769,17 @@ def a_eq_b(a, b, atol=1.0e-8, rtol=1.0e-5, return_pnts=False): return w.squeeze() +def _close_pnts_(a, b, tol=.0001, ret_whr=True): + """Alternate to `a_eq_b`. Returns indices or common points within `tol`.""" + ba = np.abs(b - a[:, None]) + whr = (ba.sum(axis=-1, keepdims=True) < tol).any(-1) + whr = np.nonzero(whr) + if ret_whr: + return whr + w0, w1 = whr[0], whr[1] + return a[w0], b[w1] + + def common_pnts(pnts, self, remove_common=True): """Check for coincident points between `pnts` and the Geo array. @@ -823,11 +834,11 @@ def compare_geom(arr, look_for, unique=True, invert=False, return_idx=False): >>> a = np.array([[ 5., 5.], [ 6., 6.], [10., 10.], [12., 12.]]) >>> b = np.array([[ 6., 6.], [12., 12.]]) - >>> compare_2d(a, b, invert=False) + >>> compare_geom(a, b, invert=False) ... array([[ 6., 6.], ... [12., 12.]]) - >>> compare_2d(a, b, invert=True) + >>> compare_geom(a, b, invert=True) ... array([[ 5., 5.], ... [10., 10.]]) """ diff --git a/arcpro_npg/npg/npg/npg_utils.py b/arcpro_npg/npg/npg/npg_utils.py index 33e7596..ddffe54 100644 --- a/arcpro_npg/npg/npg/npg_utils.py +++ b/arcpro_npg/npg/npg/npg_utils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# noqa: D205, D400 +# noqa: D205, D208, D400 r""" npg_utils @@ -42,7 +42,7 @@ 1 '''Do nothing''' 2 pass -get_modu : +get_modulue_info : retrieve module info **info** : @@ -270,7 +270,7 @@ def sub(): # ----------------------------------------------------------------------- # ---- (2) get_func .... code section ---- def get_func(func, line_nums=True, output=False): - """Get function information (ie. for a def). + r"""Get function information (ie. for a def). Parameters ---------- @@ -296,9 +296,30 @@ def get_func(func, line_nums=True, output=False): Import the module containing the function and put the object name in without quotes... - >>> import npgeom as npg - >>> npg.get_func(get_func) # returns this source code etc. - """ + >>> import npg + >>> npg.npg_utils.get_func(get_func) # returns this source code etc. + + or a more elaborate one:: + + >>> npg.npg_utils.get_func(npg.npGeo.array_IFT) + ----------------------------------------------------------------- + File path: ... C:\arcpro_npg\npg\npGeo.py + Function: .... array_IFT .... + Signature .... In [1]: array_IFT(in_arrays, shift_to_origin=False) + Line number... 1522 + Defaults: ... (False,) + kwdefaults: .. None + Variable names: + in_arrays, shift_to_origin, id_too, a_2d, subs, + cnt, p, kind, bits, sub, b_id, j, k, a_stack, ids, + part, id_prt, uni, idx, CL, i, too, frum, + pnt_nums, u, cnts, IFT, extent + Source code: + 1522 def array_IFT(in_arrays, shift_to_origin=False): + ... etc ... + + """ + # -- frmt = r""" ----------------------------------------------------------------- File path: ... {} @@ -309,7 +330,6 @@ def get_func(func, line_nums=True, output=False): kwdefaults: .. {} Variable names: {} - Source code: {} -----------------------------------------------------------------