def vor_poly(in_fc, gdb, name, out_kind): """Return the Voronoi/Theissen poly* features.""" tmp = MultipartToSinglepart(in_fc, r"memory\in_fc_temp") g, oids, shp_kind, k, m, SR = _in_(tmp, info="voronoi") out = [] L, B, R, T = g.aoi_extent() # full extent for infinity circle xc, yc = np.array([(R - L) / 2., (T - B) / 2.]) radius = max((R - L), (T - B)) * 10 inf_circ = circle(radius, xc=xc, yc=yc) pnts = [np.vstack((g.XY, inf_circ))] for ps in pnts: if len(ps) > 2: ps = np.unique(ps, axis=0) avg = np.mean(ps, axis=0) p = ps - avg tri = Voronoi(p) for region in tri.regions: if -1 not in region: polygon = np.array([tri.vertices[i] + avg for i in region]) if len(polygon) >= 3: out.append(polygon) x, y = g.LL out = arrays_to_Geo(out, kind=2, info="voronoi") g0 = out.translate(dx=x, dy=y) # tmp = temp_fc(g0, "tmp", shp_kind, SR) ext_poly = np.array([[L, B], [L, T], [R, T], [R, B], [L, B]]) ext_poly = arrays_to_Geo([ext_poly], kind=2, info="extent") ext_poly = ext_poly.translate(dx=x, dy=y) tmp1 = temp_fc(ext_poly, "tmp1", shp_kind, SR) final = gdb.replace("\\", "/") + "/" + name Clip(tmp, tmp1, final) # arcpy.analysis.Clip return
def hex_pointy(dx=1, dy=1, x_cols=1, y_rows=1, orig_x=0, orig_y=0, kind=2, asGeo=True): """Create pointy hexagons. Also called ``traverse hexagons``. Parameters ---------- See `rectangles` for shared parameter explanation. """ p_rad = np.deg2rad([150., 90, 30., -30., -90., -150., 150.]) X = np.cos(p_rad) * dx Y = np.sin(p_rad) * dy # scaled hexagon about 0, 0 seed = np.array(list(zip(X, Y))) dx = dx * np.sqrt(3.) / 2.0 dy = dy * 1.5 hexs = [seed + [dx * i * 2, 0] for i in range(0, x_cols)] m = len(hexs) for j in range(1, y_rows): # create the other rows hexs += [hexs[h] + [dx * (j % 2), dy * j] for h in range(m)] if asGeo: frmt = "dx {}, dy {}, x_cols {}, y_rows {}, LB ({},{})" txt = frmt.format(dx, dy, x_cols, y_rows, orig_x, orig_y) return arrays_to_Geo(hexs, kind=2, info=txt) return hexs
def ellipse(x_radius=1.0, y_radius=1.0, theta=10., xc=0.0, yc=0.0, kind=2, asGeo=True): """Produce an ellipse depending on parameters. Parameters ---------- radius : number Distance from centre in the X and Y directions. theta : number Angle of densification of the shape around 360 degrees. """ angles = np.deg2rad(np.arange(180.0, -180.0 - theta, step=-theta)) x_s = x_radius * np.cos(angles) + xc # X values y_s = y_radius * np.sin(angles) + yc # Y values # pnts = np.array(list(zip(x_s, y_s))) # the slow way pnts = np.zeros((x_s.shape[0], 2), x_s.dtype) pnts[:, 0] = x_s pnts[:, 1] = y_s if asGeo: if not isinstance(pnts, list): pnts = [pnts] frmt = "x_rad {}, y_rad {}, theta {}, x_c {}, y_c {}" txt = frmt.format(x_radius, y_radius, theta, xc, yc) return arrays_to_Geo(pnts, kind=2, info=txt) return pnts
def hex_flat(dx=1, dy=1, x_cols=1, y_rows=1, orig_x=0, orig_y=0, kind=2, asGeo=True): """Generate the points for the flat-headed hexagon. Parameters ---------- See `rectangles` for shared parameter explanation. """ f_rad = np.deg2rad([180., 120., 60., 0., -60., -120., -180.]) X = np.cos(f_rad) * dy Y = np.sin(f_rad) * dy # scaled hexagon about 0, 0 seed = np.array(list(zip(X, Y))) # array of coordinates dx = dx * 1.5 dy = dy * np.sqrt(3.) / 2.0 hexs = [seed + [dx * i, dy * (i % 2)] for i in range(0, x_cols)] m = len(hexs) for j in range(1, y_rows): # create the other rows hexs += [hexs[h] + [0, dy * 2 * j] for h in range(m)] if asGeo: frmt = "dx {}, dy {}, x_cols {}, y_rows {}, LB ({},{})" txt = frmt.format(dx, dy, x_cols, y_rows, orig_x, orig_y) return arrays_to_Geo(hexs, kind=2, info=txt) return hexs
def arc_sector(outer=10, inner=9, start=1, stop=6, step=0.1, asGeo=True): """Form an arc sector bounded by a distance specified by two radii. Parameters ---------- outer : number outer radius of the arc sector inner : number inner radius start : number start angle of the arc stop : number end angle of the arc step : number the angle densification step Requires -------- `arc_` is used to produce the arcs, the top arc is rotated clockwise and the bottom remains in the order produced to help form closed-polygons. """ s_s = [start, stop] s_s.sort() start, stop = s_s top = arc_(outer, start, stop, step, 0.0, 0.0) top = top[::-1] bott = arc_(inner, start, stop, step, 0.0, 0.0) close = top[0] pnts = np.concatenate((top, bott, [close]), axis=0) if asGeo: return arrays_to_Geo(pnts, kind=2) return pnts
def arc_(radius=100, start=0, stop=1, step=0.1, xc=0.0, yc=0.0, asGeo=True): """Create an arc from a specified radius, centre and start/stop angles. Parameters ---------- radius : number cirle radius from which the arc is obtained start, stop, step : numbers angles in degrees xc, yc : number center coordinates in projected units as_list : boolean False, returns an array. True yields a list Returns ------- Points on the arc as an array >>> # arc from 0 to 90 in 5 degree increments with radius 2 at (0, 0) >>> a0 = arc_(radius=2, start=0, stop=90, step=5, xc=0.0, yc=0.0) """ start, stop = sorted([start, stop]) angle = np.deg2rad(np.arange(start, stop, step)) x_s = radius * np.cos(angle) # X values y_s = radius * np.sin(angle) # Y values pnts = np.array([x_s, y_s]).T + [xc, yc] if asGeo: return arrays_to_Geo(pnts, kind=2) return pnts
def merge_(this, to_this): """ Merge `this` geometry and `to_this` geometry. The direction is important. Parameters ---------- this : array(s) or a Geo array The geometry to merge to the existing geometry (`to_this`). to_this : Geo array The Geo array to receive the new geometry. Notes ----- The `this` array can be a single array, a list of arrays or a Geo array. If you want to append object array(s) (dtype= 'O'), then convert to a list of arrays or a list of lists first. During the merge operation, overlapping geometries are not intersected. Returns ------- A new Geo array. this = np.array([[0, 8.], [5., 13.], [5., 8.], [0., 8]]) b = this + [5, 2] this = [a, b] to_this = s0 """ a = this # --- rename to simplify the input names b = to_this # merge a to b, or this to_this if not hasattr(b, 'IFT'): b = npGeo.arrays_to_Geo(b) b_XY = b.XY b_IFT = b.IFT if hasattr(this, 'IFT'): if a.K != b.K: print("\nGeo array `kind` is not the same.\n") return None a_XY = a.XY a_IFT = a.IFT else: a = np.asarray(a) if a.ndim == 2: a = [a] a_XY, a_IFT, extent = npGeo.array_IFT(a) a_XY = a_XY + extent[0] last = b.IFT[-1, :] add_ = [] for i, row in enumerate(a_IFT, 1): add_.append([last[0] + i, last[2] + row[1], last[2] + row[2]] + list(row[3:])) add_ = np.atleast_2d(add_) new_ift = np.vstack((b_IFT, add_)) xys = np.vstack((b_XY, a_XY)) kind = b.K sr = b.SR out = npGeo.Geo(xys, IFT=new_ift, Kind=kind, Extent=None, Info="", SR=sr) return out
def rectangle(dx=1, dy=-1, x_cols=1, y_rows=1, orig_x=0, orig_y=1, kind=2, asGeo=True): """Create a point array to represent a series of rectangles or squares. Parameters ---------- dx, dy : number x direction increment, +ve moves west to east, left/right. y direction increment, -ve moves north to south, top/bottom. x_cols, y_rows : integers The number of columns and rows to produce. orig_x, orig_y : number Planar coordinates assumed. You can alter the location of the origin by specifying the correct combination of (dx, dy) and (orig_x, orig_y). The defaults produce a clockwise, closed-loop geometry, beginning and ending in the upper left. kind, asGeo : These relate to Geo arrays Example ------- Stating the obvious... squares form when dx == dy. X = [0.0, 0.0, dx, dx, 0.0] # X, Y values for a unit square Y = [0.0, dy, dy, 0.0, 0.0] Cells are constructed clockwise from the bottom-left. The rectangular grid is constructed from the top-left. Specifying an origin (upper left) of (0, 2) yields a bottom-right corner of (3,0) when the following are used. >>> z = rectangle(dx=1, dy=1, x_cols=3, y_rows=2, orig_x=0, orig_y=2, ... kind=2, asGeo=False) The first `cell` will be in the top-left and the last `cell` in the bottom-right. """ seed = np.array([[0.0, 0.0], [0.0, dy], [dx, dy], [dx, 0.0], [0.0, 0.0]]) a = [ seed + [j * dx, i * dy] # make the shapes for i in range(0, y_rows) # cycle through the rows for j in range(0, x_cols) ] # cycle through the columns a = np.asarray(a) + [orig_x, orig_y - dy] if asGeo: frmt = "dx {}, dy {}, x_cols {}, y_rows {}, LB ({},{})" txt = frmt.format(dx, dy, x_cols, y_rows, orig_x, orig_y) return arrays_to_Geo(a, kind=2, info=txt) return a
def circle_ring(outer=100, inner=0, theta=10, rot=0, scale=1, xc=0.0, yc=0.0, asGeo=True): """Create a multi-ring buffer around a center point (xc, yc). Parameters ---------- outer, inner : number Outer and inner radius in planar units theta : number See below. rot : number Rotation angle, used for non-circles. scale : number Used to scale the y-coordinates. Notes ----- Angles to use to densify the circle:: - 360+ circle - 120 triangle - 90 square - 72 pentagon - 60 hexagon - 45 octagon - etc """ top = circle(outer, clockwise=True, theta=theta, rot=rot, scale=scale, xc=xc, yc=yc) if inner == 0.0: return top bott = circle(inner, clockwise=False, theta=theta, rot=rot, scale=scale, xc=xc, yc=yc) pnts = np.concatenate((top, bott), axis=0) if asGeo: return arrays_to_Geo(pnts, kind=2) return pnts
def circle(radius=100, clockwise=True, theta=1, rot=0.0, scale=1, xc=0.0, yc=0.0, asGeo=True): """Produce a circle/ellipse depending on parameters. Parameters ---------- radius : number In projected units. clockwise : boolean True for clockwise (outer rings), False for counter-clockwise (for inner rings). theta : number Angle spacing. If theta=1, angles between -180 to 180, are returned in 1 degree increments. The endpoint is excluded. rot : number Rotation angle in degrees... used if scaling is not equal to 1. scale : number For ellipses, change the scale to <1 or > 1. The resultant y-values will favour the x or y-axis depending on the scaling. Returns ------- List of coordinates for the circle/ellipse. Notes ----- You can also use np.linspace if you want to specify point numbers. >>> np.linspace(start, stop, num=50, endpoint=True, retstep=False) >>> np.linspace(-180, 180, num=720, endpoint=True, retstep=False) """ if clockwise: angles = np.deg2rad(np.arange(180.0, -180.0 - theta, step=-theta)) else: angles = np.deg2rad(np.arange(-180.0, 180.0 + theta, step=theta)) x_s = radius * np.cos(angles) # X values y_s = radius * np.sin(angles) * scale # Y values pnts = np.array([x_s, y_s]).T if rot != 0: rot_mat = rot_matrix(angle=rot) pnts = (np.dot(rot_mat, pnts.T)).T pnts = pnts + [xc, yc] if asGeo: return arrays_to_Geo(pnts, kind=2) return pnts
def geojson_Geo(pth, kind=2, info=None, to_origin=False): """Convert GeoJSON file to Geo array using `npGeo.arrays_to_Geo`. Parameters ---------- pth : string Full path to the geojson file. kind : integer Polygon, Polyline or Point type are identified as either 2, 1, or 0. info : text Supplementary information. """ coords = load_geojson(pth) # a_2d, ift, extents = npGeo.array_IFT(coords) return npGeo.arrays_to_Geo(coords, kind=kind, info=info, to_origin=to_origin)
def triangle(dx=1, dy=1, x_cols=1, y_rows=1, orig_x=0, orig_y=1, kind=2, asGeo=True): """Create a row of meshed triangles. The triangles are essentially bisected squares and not equalateral. The triangles per row will not be terminated in half triangles to `square off` the area of coverage. This is to ensure that all geometries have the same area and point construction. Parameters ---------- See `rectangles` for shared parameter explanation. """ a, dx, b = dx / 2.0, dx, dx * 1.5 # X, Y values for a unit triangle, point up and point down seedU = np.array([[0.0, 0.0], [a, dy], [dx, 0.0], [0.0, 0.0]]) seedD = np.array([[a, dy], [b, dy], [dx, 0.0], [a, dy]]) seed = np.array([seedU, seedD]) a = [ seed + [j * dx, i * dy] # make the shapes for i in range(0, y_rows) # cycle through the rows for j in range(0, x_cols) ] # cycle through the columns a = np.asarray(a) s1, s2, s3, s4 = a.shape a = a.reshape(s1 * s2, s3, s4) if asGeo: frmt = "dx {}, dy {}, x_cols {}, y_rows {}, LB ({},{})" txt = frmt.format(dx, dy, x_cols, y_rows, orig_x, orig_y) return arrays_to_Geo(a, kind=2, info=txt) return a
def extent_to_poly(extent, kind=2): """Create a polygon/polyline feature from an array of x,y values. The array returned is ordered clockwise with the first and last point repeated to form a closed-loop. Parameters ---------- extent : array-like The extent is specified as four float values in the form of L(eft), B(ottom), R(ight), T(op) eg. np.array([5, 5, 10, 10]) or a pair of points [LB, RT] kind : integer A value of 1 for a polyline, or 2 for a polygon. """ shp = extent.shape if shp not in [(2, 2), (4, )]: print("Check the docs...\n{}".format(extent_to_poly.__doc__)) return None L, B, R, T = extent.ravel() L, R = min(L, R), max(L, R) B, T = min(B, T), max(B, T) ext = np.array([[L, B], [L, T], [R, T], [R, B], [L, B]]) return npGeo.arrays_to_Geo([ext], kind=kind, info="extent to poly")
def dissolve(a, asGeo=True): """Dissolve polygons sharing edges. Parameters ---------- a : Geo array A Geo array is required. Use ``arrays_to_Geo`` to convert a list of lists/arrays or an object array representing geometry. asGeo : boolean True, returns a Geo array. False returns a list of arrays. Notes ----- >>> from npgeom.npg_plots import plot_polygons # to plot the geometry `_isin_2d_`, `find`, `adjacent` equivalent:: (b0[:, None] == b1).all(-1).any(-1) """ def _adjacent_(a, b): """Check adjacency between 2 polygon shapes.""" s = np.sum((a[:, None] == b).all(-1).any(-1)) if s > 0: return True return False def _cycle_(b0, b1): """Cycle through the bits.""" def _find_(a, b): """Find. Abbreviated form of ``adjacent``, to use for slicing.""" return (a[:, None] == b).all(-1).any(-1) idx01 = _find_(b0, b1) if idx01.sum() == 0: return None if idx01[0] == 1: # you can't split between the first and last pnt. b0, b1 = b1, b0 idx01 = _find_(b0, b1) dump = b0[idx01] sp0 = np.nonzero(idx01)[0] sp1 = np.any(np.isin(b1, dump, invert=True), axis=1) z0 = np.array_split(b0, sp0[1:]) z1 = b1[sp1] return np.concatenate((z0[0], z1, z0[-1]), axis=0) def _combine_(r, shps): """Combine the shapes.""" missed = [] processed = False for i, shp in enumerate(shps): adj = _adjacent_(r, shp[1:-1]) # shp[1:-1]) if adj: new = _cycle_(r, shp[:-1]) # shp[:-1]) ** today r = new processed = True else: missed.append(shp) if len(shps) == 2 and not processed: missed.append(r) return r, missed # done # --- check for appropriate Geo array. if not hasattr(a, "IFT") or a.is_multipart(): msg = """function : dissolve A `Singlepart` Geo array is required. Use ``arrays_to_Geo`` to convert arrays to a Geo array and use ``multipart_to_singlepart`` if needed. """ print(msg) return None # --- get the outer rings, roll the coordinates and run ``_combine_``. a = a.outer_rings(True) a = a.roll_shapes() a.IFT[:, 0] = np.arange(len(a.IFT)) out = [] ids = a.IDs shps = a.get_shapes(ids, False) r = shps[0] missed = shps N = len(shps) cnt = 0 while cnt <= N: r1, missed1 = _combine_(r, missed[1:]) if r1 is not None and N >= 0: out.append(r1) if len(missed1) == 0: N = 0 else: N = len(missed1) r = missed1[0] missed = missed1 cnt += 1 # final kick at the can if len(out) > 1: r, missed = _combine_(out[0], out[1:]) if missed is not None: out = [r] + missed else: out = r if asGeo: out = npGeo.arrays_to_Geo(out, 2, "dissolved", False) out = npGeo.roll_coords(out) return out # , missed