From 7055fa41d643ea4a6f34244c262ee33a9e0052fc Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Thu, 4 Jul 2019 11:29:37 +0200 Subject: [PATCH 01/10] add gridintersect to flopy utils --- flopy/utils/gridintersect.py | 1247 ++++++++++++++++++++++++++++++++++ 1 file changed, 1247 insertions(+) create mode 100644 flopy/utils/gridintersect.py diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py new file mode 100644 index 0000000000..8c74b36cc2 --- /dev/null +++ b/flopy/utils/gridintersect.py @@ -0,0 +1,1247 @@ +import matplotlib.pyplot as plt +import numpy as np +from .geometry import transform +try: + import shapely + from shapely.geometry import (LineString, MultiLineString, MultiPoint, + MultiPolygon, Point, Polygon, box) + from shapely.strtree import STRtree + from shapely.affinity import translate, rotate +except ModuleNotFoundError: + print("Shapely is needed for grid intersect operations! Please install shapely.") + + +class GridIntersect: + """Class for intersecting shapely shapes (Point, Linestring, Polygon, + or their Multi variants) with MODFLOW grids. Contains optimized search + routines for structured grids. + + Notes: + - It is faster to intersect each individual shape in a collection + than it is to intersect with the whole collection at once. This is + because the STRtree query uses the bounding box of the whole collection + to identify potential intersecting grid cells. + - Building the STRtree can take a while for large grids. Once built the + intersect routines (for individual shapes) should be pretty fast. + - The optimized routines for structured grids will generally outperform + the shapely routines because of the reduced overhead of building and parsing + the queried STR-tree. For Polygons, shapely is sometimes faster than + the optimized structured routines. + - The STR-tree query is based on the bounding box of the shape, if the + bounding box of the shape covers nearly the entire grid, the query + won't be able to limit the search space much resulting in slower + performance. + + """ + + def __init__(self, mfgrid, method="strtree"): + """Intersect shapes (Point, Linestring, Polygon) with a + modflow grid. + + Parameters + ---------- + mfgrid : flopy modflowgrid + MODFLOW grid as implemented in flopy + method : str, optional + either "strtree" which builds an STRTree (most flexible) + or "structured" which uses optimized methods that only work + for structured grids, by default "strtree" + + """ + + self.mfgrid = mfgrid + + if method == "strtree": + if mfgrid.grid_type == "structured": + self.gridshapes = self._rect_grid_to_shape_list() + elif mfgrid.grid_type == "unstructured": + raise NotImplementedError() + elif mfgrid.grid_type == "vertex": + self.gridshapes = self._vtx_grid_to_shape_list() + + self.strtree = STRtree(self.gridshapes) + + self.intersect_point = self._intersect_point_shapely + self.intersect_linestring = self._intersect_linestring_shapely + self.intersect_polygon = self._intersect_polygon_shapely + + elif method == "structured" and mfgrid.grid_type == "structured": + self.strtree = None + self.intersect_point = self._intersect_point_structured + self.intersect_linestring = self._intersect_linestring_structured + self.intersect_polygon = self._intersect_polygon_structured + + else: + raise NotImplementedError( + "Method 'structured' only works for structured grids.") + + def _rect_grid_to_shape_list(self): + """internal method, convert structured grid to list of + shapely polygons + + Returns + ------- + list + list of shapely Polygons + + """ + shplist = [] + for i in range(self.mfgrid.nrow): + for j in range(self.mfgrid.ncol): + xy = self.mfgrid.get_cell_vertices(i, j) + p = Polygon(xy) + p.name = (i, j) + shplist.append(p) + return shplist + + def _usg_grid_to_shape_list(self): + """internal method, convert unstructred grid to list of shapely polygons + + Returns + ------- + list + list of shapely Polygons + """ + + # shplist = [] + # TODO: add logic here + # return shplist + raise NotImplementedError() + + def _vtx_grid_to_shape_list(self): + """internal method, convert vertex grid to list of shapely polygons + + Returns + ------- + list + list of shapely Polygons + + """ + + shplist = [] + if isinstance(self.mfgrid._cell2d, np.recarray): + for icell in self.mfgrid._cell2d.icell2d: + points = [] + for iv in self.mfgrid._cell2d[["icvert_0", "icvert_1", "icvert_2"]][icell]: + points.append((self.mfgrid._vertices.xv[iv], + self.mfgrid._vertices.yv[iv])) + # close the polygon, if necessary + if points[0] != points[-1]: + points.append(points[0]) + p = Polygon(points) + p.name = icell + shplist.append(p) + elif isinstance(self.mfgrid._cell2d, list): + for icell in range(len(self.mfgrid._cell2d)): + points = [] + for iv in self.mfgrid._cell2d[icell][-3:]: + points.append((self.mfgrid._vertices[iv][1], + self.mfgrid._vertices[iv][2])) + # close the polygon, if necessary + if points[0] != points[-1]: + points.append(points[0]) + p = Polygon(points) + p.name = icell + shplist.append(p) + return shplist + + def _sort_strtree_result(self, shapelist): + """internal method, sort strtree query result by node id + + Parameters + ---------- + shapelist : list + list of shapely Polygons + + Returns + ------- + list + sorted list of Polygons + + """ + def sort_key(o): + return o.name + shapelist.sort(key=sort_key) + return shapelist + + def _intersect_point_shapely(self, shp, sort_by_cellid=True): + """intersect grid with Point or MultiPoint + + Parameters + ---------- + shp : Point or MultiPoint + shapely Point or MultiPoint to intersect with grid. Note, + it is generally faster to loop over a MultiPoint and intersect + per point than to intersect a MultiPoint directly. + sort_by_cellid : bool, optional + flag whether to sort cells by id, used to ensure node + with lowest id is returned, by default True + keep_all_ix : bool, optional + if false, each point can only intersect with one grid cell, + by default the grid cell with the lowest node id is returned. + if true, return all intersecting grid cells, i.e. + a point on an internal boundary in the grid will + intersect with both adjacent grid cells, by default False + + Returns + ------- + numpy.recarray + a record array containing information about the intersection + + """ + ixshapes = self.strtree.query(shp) + if sort_by_cellid: + ixshapes = self._sort_strtree_result(ixshapes) + + isectshp = [] + cellids = [] + vertices = [] + + for r in ixshapes: + intersect = shp.intersection(r) + + # Either Point or MultiPoint or Empty + # If MultiPoint, means more than one point in cell + + if intersect.is_empty: + continue + elif intersect.geom_type == "Point" or intersect.geom_type == "MultiPoint": + pt = intersect.__geo_interface__["coordinates"] + if pt in vertices: + continue + isectshp.append(intersect) + vertices.append(pt) + cellids.append(r.name) + + rec = np.recarray(len(isectshp), names=["cellids", "vertices", "ixshapes"], + formats=["O", "O", "O"]) + rec.ixshapes = isectshp + rec.vertices = vertices + rec.cellids = cellids + + return rec + + def _intersect_linestring_shapely(self, shp, keepzerolengths=False, sort_by_cellid=True): + """intersect with LineString or MultiLineString + + Parameters + ---------- + shp : shapely.geometry.LineString or MultiLineString + LineString to intersect with the grid + sort_by_cellid : bool, optional + flag whether to sort cells by id, used to ensure node + with lowest id is returned, by default True + keep_all_ix : bool, optional + if false, each linestring can only intersect with one grid cell, + by default the grid cell with the lowest node id is returned. + if true, return all intersecting grid cells, i.e. + a linestring on an internal boundary in the grid will + intersect with both adjacent grid cells, by default False + + Returns + ------- + numpy.recarray + a record array containing information about the intersection + + """ + result = self.strtree.query(shp) + if sort_by_cellid: + result = self._sort_strtree_result(result) + + isectshp = [] + cellids = [] + vertices = [] + lengths = [] + + for r in result: + intersect = shp.intersection(r) + + # Results in: + # - LineString: + # - MultiLineString: + # - GeometryCollection (Empty, or collection of LineString and Point) + + if "LineString" in intersect.geom_type: + # result is a LineString or MultiLineString + # keep_all_ix determines what is stored + verts = intersect.__geo_interface__["coordinates"] + # if not keep_all_ix: + if verts in vertices: + continue + isectshp.append(intersect) + lengths.append(intersect.length) + vertices.append(verts) + cellids.append(r.name) + + elif intersect.geom_type == "GeometryCollection": + # result is either empty or mix of geometry types + # keep_all_ix determines what is stored + + # no intersect + if intersect.is_empty: + continue + + # if not keep_all_ix, continue if length == 0.0 + # if not keep_all_ix: + if intersect.length == 0.0: + continue + + # loop over collection + for geom in intersect.geoms: + verts = geom.__geo_interface__["coordinates"] + # if not keep_all_ix: + if verts in vertices: + continue + vertices.append(verts) + if "LineString" in geom.geom_type: + lengths.append(geom.length) + else: + lengths.append(np.nan) + isectshp.append(geom) + # else: # Point + # if keep_all_ix: + # verts = intersect.__geo_interface__["coordinates"] + # vertices.append(verts) + # lengths.append(np.nan) + # isectshp.append(intersect) + # cellids.append(r.name) + + rec = np.recarray(len(isectshp), names=["cellids", "vertices", "lengths", "ixshapes"], + formats=["O", "O", "f8", "O"]) + rec.ixshapes = isectshp + rec.vertices = vertices + rec.lengths = lengths + rec.cellids = cellids + + return rec + + def _intersect_polygon_shapely(self, shp, sort_by_cellid=True): + """intersect with Polygon or MultiPolygon + + Parameters + ---------- + shp : shapely.geometry.Polygon or MultiPolygon + shape to intersect with the grid + sort_by_cellid : bool, optional + flag whether to sort cells by id, used to ensure node + with lowest id is returned, by default True + keep_all_ix : bool, optional + if False, only intersections with area > 0 are returned. + if True, return all intersecting grid cells, i.e. + a polygon touching the boundary of a grid cell will + return a Point at the point they meet, by default False + + Returns + ------- + numpy.recarray + a record array containing information about the intersection + + """ + ixshapes = self.strtree.query(shp) + if sort_by_cellid: + ixshapes = self._sort_strtree_result(ixshapes) + + isectshp = [] + cellids = [] + vertices = [] + areas = [] + + for r in ixshapes: + intersect = shp.intersection(r) + + # Results in + # - GeomCollection (Combination of Points, LineStrings and Polygons + # and Emptys) -> store as multiple entries in rec_array (only Polygons or MultiPolygons) + # - MultiPolygon (several Polygons) -> store as single entry in rec array + # - Polygon (single Polygon) -> store as single entry in rec array + + if "Polygon" in intersect.geom_type: + # this means we know result has area and is Polygon or + # MultiPolygon which are treated the same. + isectshp.append(intersect) + areas.append(intersect.area) + vertices.append(intersect.__geo_interface__["coordinates"]) + cellids.append(r.name) + + elif intersect.geom_type == "GeometryCollection": + # result is either empty or mix of geometry types + # keep_all_ix determines what is stored + + # no intersect + if intersect.is_empty: + continue + + # continue if area == 0.0 + if intersect.area == 0.0: + continue + + # yes intersect, loop over collection + # TODO: can probably be simplified as we know result + # has area and is a Polygon + for geom in intersect: + isectshp.append(geom) + if "Polygon" in geom.geom_type: + areas.append(geom.area) + else: + areas.append(np.nan) + if "coordinates" in geom.__geo_interface__.keys(): + vertices.append( + geom.__geo_interface__["coordinates"]) + else: + vertices.append(np.nan) + cellids.append(r.name) + + # else: # Point or LineString + # if keep_all_ix: + # isectshp.append(intersect) + # if "Polygon" in intersect.geom_type: + # areas.append(intersect.area) + # else: + # areas.append(np.nan) + # if "coordinates" in intersect.__geo_interface__.keys(): + # vertices.append( + # intersect.__geo_interface__["coordinates"]) + # else: + # vertices.append(np.nan) + # cellids.append(r.name) + + rec = np.recarray(len(isectshp), names=["cellids", "vertices", "areas", "ixshapes"], + formats=["O", "O", "f8", "O"]) + rec.ixshapes = isectshp + rec.vertices = vertices + rec.areas = areas + rec.cellids = cellids + + return rec + + def _intersect_point_structured(self, shp): + """intersection method for intersecting points with structured grids + + Parameters + ---------- + shp : shapely.geometry.Point or MultiPoint + point shape to intersect with grid + + Returns + ------- + numpy.recarray + a record array containing information about the intersection + + """ + nodelist = [] + + Xe, Ye = self.mfgrid.xyedges + + try: + iter(shp) + except TypeError: + shp = [shp] + + ixshapes = [] + for p in shp: + # if grid is rotated or offset transform point to local coords + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + rx, ry = transform(p.x, p.y, self.mfgrid.xoffset, + self.mfgrid.yoffset, + self.mfgrid.angrot_radians, + inverse=True) + else: + rx = p.x + ry = p.y + + # two dimensional point + jpos = ModflowGridIndices.find_position_in_array(Xe, rx) + ipos = ModflowGridIndices.find_position_in_array(Ye, ry) + + if jpos is not None and ipos is not None: + nodelist.append((ipos, jpos)) + ixshapes.append(p) + + # three dimensional point + if p._ndim == 3: + # find k + kpos = ModflowGridIndices.find_position_in_array( + self.mfgrid.botm[:, ipos, jpos], p.z) + if kpos is not None: + nodelist.append(kpos, ipos, jpos) + + # remove duplicates + tempnodes = [] + tempshapes = [] + for node, ixs in zip(nodelist, ixshapes): + if node not in tempnodes: + tempnodes.append(node) + tempshapes.append(ixs) + else: + tempshapes[-1] = MultiPoint([tempshapes[-1], ixs]) + + ixshapes = tempshapes + nodelist = tempnodes + + rec = np.recarray(len(nodelist), names=["cellids", "ixshapes"], + formats=["O", "O"]) + rec.cellids = nodelist + rec.ixshapes = ixshapes + return rec + + def _intersect_linestring_structured(self, shp, keepzerolengths=False): + """method for intersecting linestrings with structured grids + + Parameters + ---------- + shp : shapely.geometry.Linestring or MultiLineString + linestring to intersect with grid + keepzerolengths : bool, optional + if True keep intersection results with length=0, in + other words, grid cells the linestring does not cross + but does touch, by default False + + Returns + ------- + numpy.recarray + a record array containing information about the intersection + + """ + # get local extent of grid + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + xmin = np.min(self.mfgrid.xyedges[0]) + xmax = np.max(self.mfgrid.xyedges[0]) + ymin = np.min(self.mfgrid.xyedges[1]) + ymax = np.max(self.mfgrid.xyedges[1]) + else: + xmin, xmax, ymin, ymax = self.mfgrid.extent + pl = box(xmin, ymin, xmax, ymax) + + # rotate and translate linestring to local coords + if (self.mfgrid.xoffset != 0. or self.mfgrid.yoffset != 0.): + shp = translate(shp, xoff=-self.mfgrid.xoffset, + yoff=-self.mfgrid.yoffset) + if self.mfgrid.angrot != 0.: + shp = rotate(shp, -self.mfgrid.angrot, origin=(0., 0.)) + + # clip line to mfgrid bbox + lineclip = shp.intersection(pl) + + if lineclip.length == 0.: # linestring does not intersect modelgrid + return np.recarray(0, names=["cellids", "vertices", "lengths", "ixshapes"], + formats=["O", "O", "f8", "O"]) + if lineclip.geom_type is 'MultiLineString': # there are multiple lines + nodelist, lengths, vertices = [], [], [] + ixshapes = [] + for ls in lineclip: + n, l, v, ix = self._get_nodes_intersecting_linestring(ls) + nodelist += n + lengths += l + # if necessary, transform coordinates back to real world coordinates + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + v_realworld = [] + for pt in v: + rx, ry = transform([pt[0]], [pt[1]], self.mfgrid.xoffset, + self.mfgrid.yoffset, + self.mfgrid.angrot_radians, + inverse=False) + v_realworld.append([rx, ry]) + ix_realworld = rotate( + ix, self.mfgrid.angrot, origin=(0., 0.)) + ix_realworld = translate( + ix_realworld, self.mfgrid.xoffset, self.mfgrid.yoffset) + else: + v_realworld = v + ix_realworld = ix + vertices += v_realworld + ixshapes += ix_realworld + else: # linestring is fully within grid + nodelist, lengths, vertices, ixshapes = self._get_nodes_intersecting_linestring( + lineclip) + # if necessary, transform coordinates back to real world coordinates + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + v_realworld = [] + for pt in vertices: + rx, ry = transform([pt[0]], [pt[1]], self.mfgrid.xoffset, + self.mfgrid.yoffset, + self.mfgrid.angrot_radians, + inverse=False) + v_realworld.append([rx, ry]) + vertices = v_realworld + + ix_shapes_realworld = [] + for ixs in ixshapes: + ixs = rotate(ixs, self.mfgrid.angrot, origin=(0., 0.)) + ixs = translate(ixs, self.mfgrid.xoffset, + self.mfgrid.yoffset) + ix_shapes_realworld.append(ixs) + ixshapes = ix_shapes_realworld + + # bundle linestrings in same cell + tempnodes = [] + templengths = [] + tempverts = [] + tempshapes = [] + unique_nodes = list(set(nodelist)) + if len(unique_nodes) < len(nodelist): + for inode in unique_nodes: + templengths.append( + sum([l for l, i in zip(lengths, nodelist) if i == inode])) + tempverts.append( + [v for v, i in zip(vertices, nodelist) if i == inode]) + tempshapes.append( + [ix for ix, i in zip(ixshapes, nodelist) if i == inode]) + + nodelist = unique_nodes + lengths = templengths + vertices = tempverts + ixshapes = tempshapes + + # eliminate any nodes that have a zero length + if not keepzerolengths: + tempnodes = [] + templengths = [] + tempverts = [] + tempshapes = [] + for i in range(len(nodelist)): + if lengths[i] > 0: + tempnodes.append(nodelist[i]) + templengths.append(lengths[i]) + tempverts.append(vertices[i]) + tempshapes.append(ixshapes[i]) + nodelist = tempnodes + lengths = templengths + vertices = tempverts + ixshapes = tempshapes + + rec = np.recarray(len(nodelist), names=["cellids", "vertices", "lengths", "ixshapes"], + formats=["O", "O", "f8", "O"]) + rec.vertices = vertices + rec.lengths = lengths + rec.cellids = nodelist + rec.ixshapes = ixshapes + + return rec + + def _get_nodes_intersecting_linestring(self, linestring): + """helper function, intersect the linestring with the a structured + grid and return a list of node indices and the length of the + line in that node. + + Parameters + ---------- + linestring: shapely.geometry.LineString or MultiLineString + shape to intersect with the grid + + Returns + ------- + nodelist, lengths, vertices: lists + lists containing node ids, lengths of intersects and the + start and end points of the intersects + + """ + nodelist = [] + lengths = [] + vertices = [] + ixshapes = [] + + # start at the beginning of the line + x, y = linestring.xy + + # linestring already in local coords but + # because intersect_point does transform again + # we transform back to real world here if necessary + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + x0, y0 = transform([x[0]], [y[0]], self.mfgrid.xoffset, + self.mfgrid.yoffset, self.mfgrid.angrot_radians, + inverse=False) + else: + x0 = [x[0]] + y0 = [y[0]] + + (i, j) = self.intersect_point(Point(x0[0], y0[0])).cellids[0] + Xe, Ye = self.mfgrid.xyedges + xmin = Xe[j] + xmax = Xe[j + 1] + ymax = Ye[i] + ymin = Ye[i + 1] + pl = box(xmin, ymin, xmax, ymax) + intersect = linestring.intersection(pl) + # if linestring starts in cell, exits, and re-enters + # a MultiLineString is returned. + ixshapes.append(intersect) + length = intersect.length + lengths.append(length) + if intersect.geom_type == "MultiLineString": + x, y = [], [] + for igeom in intersect.geoms: + x.append(igeom.xy[0]) + y.append(igeom.xy[1]) + x = np.concatenate(x) + y = np.concatenate(y) + else: + x = intersect.xy[0] + y = intersect.xy[1] + verts = [(i[0], i[1]) for i in zip(x, y)] + vertices.append(verts) + nodelist.append((i, j)) + + n = 0 + while True: + (i, j) = nodelist[n] + node, length, verts, ixshape = self._check_adjacent_cells_intersecting_line( + linestring, (i, j), nodelist) + + for inode, ilength, ivert, ix in zip(node, length, verts, ixshape): + if inode is not None: + if ivert not in vertices: + nodelist.append(inode) + lengths.append(ilength) + vertices.append(ivert) + ixshapes.append(ix) + + if n == len(nodelist) - 1: + break + n += 1 + + return nodelist, lengths, vertices, ixshapes + + def _check_adjacent_cells_intersecting_line(self, linestring, i_j, nodelist): + """helper method that follows a line through a structured grid + + Parameters + ---------- + linestring : shapely.geometry.LineString + shape to intersect with the grid + i_j : tuple + tuple containing (nrow, ncol) + nodelist : list of tuples + list of node ids that have already been added + as intersections + + Returns + ------- + node, length, verts: lists + lists containing nodes, lengths and vertices of + intersections with adjacent cells relative to the + current cell (i, j) + + """ + i, j = i_j + + Xe, Ye = self.mfgrid.xyedges + + node = [] + length = [] + verts = [] + ixshape = [] + + # check to left + if j > 0: + ii = i + jj = j - 1 + if (ii, jj) not in nodelist: + xmin = Xe[jj] + xmax = Xe[jj + 1] + ymax = Ye[ii] + ymin = Ye[ii + 1] + pl = box(xmin, ymin, xmax, ymax) + if linestring.intersects(pl): + intersect = linestring.intersection(pl) + ixshape.append(intersect) + length.append(intersect.length) + if intersect.geom_type == "MultiLineString": + x, y = [], [] + for igeom in intersect.geoms: + x.append(igeom.xy[0]) + y.append(igeom.xy[1]) + x = np.concatenate(x) + y = np.concatenate(y) + else: + x = intersect.xy[0] + y = intersect.xy[1] + verts.append([(i[0], i[1]) for i in zip(*intersect.xy)]) + node.append((ii, jj)) + + # check to right + if j < self.mfgrid.ncol - 1: + ii = i + jj = j + 1 + if (ii, jj) not in nodelist: + xmin = Xe[jj] + xmax = Xe[jj + 1] + ymax = Ye[ii] + ymin = Ye[ii + 1] + pl = box(xmin, ymin, xmax, ymax) + if linestring.intersects(pl): + intersect = linestring.intersection(pl) + ixshape.append(intersect) + length.append(intersect.length) + if intersect.geom_type == "MultiLineString": + x, y = [], [] + for igeom in intersect.geoms: + x.append(igeom.xy[0]) + y.append(igeom.xy[1]) + x = np.concatenate(x) + y = np.concatenate(y) + else: + x = intersect.xy[0] + y = intersect.xy[1] + verts.append([(i[0], i[1]) for i in zip(*intersect.xy)]) + node.append((ii, jj)) + + # check to back + if i > 0: + ii = i - 1 + jj = j + if (ii, jj) not in nodelist: + xmin = Xe[jj] + xmax = Xe[jj + 1] + ymax = Ye[ii] + ymin = Ye[ii + 1] + pl = box(xmin, ymin, xmax, ymax) + if linestring.intersects(pl): + intersect = linestring.intersection(pl) + ixshape.append(intersect) + length.append(intersect.length) + if intersect.geom_type == "MultiLineString": + x, y = [], [] + for igeom in intersect.geoms: + x.append(igeom.xy[0]) + y.append(igeom.xy[1]) + x = np.concatenate(x) + y = np.concatenate(y) + else: + x = intersect.xy[0] + y = intersect.xy[1] + verts.append([(i[0], i[1]) for i in zip(*intersect.xy)]) + node.append((ii, jj)) + + # check to front + if i < self.mfgrid.nrow - 1: + ii = i + 1 + jj = j + if (ii, jj) not in nodelist: + xmin = Xe[jj] + xmax = Xe[jj + 1] + ymax = Ye[ii] + ymin = Ye[ii + 1] + pl = box(xmin, ymin, xmax, ymax) + if linestring.intersects(pl): + intersect = linestring.intersection(pl) + ixshape.append(intersect) + length.append(intersect.length) + if intersect.geom_type == "MultiLineString": + x, y = [], [] + for igeom in intersect.geoms: + x.append(igeom.xy[0]) + y.append(igeom.xy[1]) + x = np.concatenate(x) + y = np.concatenate(y) + else: + x = intersect.xy[0] + y = intersect.xy[1] + verts.append([(i[0], i[1]) for i in zip(x, y)]) + node.append((ii, jj)) + + return node, length, verts, ixshape + + def _intersect_rectangle_structured(self, rectangle): + """intersect a rectangle with a structured grid to retrieve + node ids of intersecting grid cells. + + Note: only works in local coordinates (i.e. non-rotated grid + with origin at (0, 0)) + + Parameters + ---------- + rectangle : list of tuples + list of lower-left coordinate and upper-right + coordinate: [(xmin, ymin), (xmax, ymax)] + + Returns + ------- + nodelist: list of tuples + list of tuples containing node ids with which + the rectangle intersects + + """ + + nodelist = [] + + # return if rectangle does not contain any cells + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + minx = np.min(self.mfgrid.xyedges[0]) + maxx = np.max(self.mfgrid.xyedges[0]) + miny = np.min(self.mfgrid.xyedges[1]) + maxy = np.max(self.mfgrid.xyedges[1]) + local_extent = [minx, maxx, miny, maxy] + else: + local_extent = self.mfgrid.extent + + xmin, xmax, ymin, ymax = local_extent + bgrid = box(xmin, ymin, xmax, ymax) + (rxmin, rymin), (rxmax, rymax) = rectangle + b = box(rxmin, rymin, rxmax, rymax) + + if not b.intersects(bgrid): + # return with nodelist as an empty list + return [] + + Xe, Ye = self.mfgrid.xyedges + + jmin = ModflowGridIndices.find_position_in_array(Xe, xmin) + if jmin is None: + if xmin <= Xe[0]: + jmin = 0 + elif xmin >= Xe[-1]: + jmin = self.mfgrid.ncol - 1 + + jmax = ModflowGridIndices.find_position_in_array(Xe, xmax) + if jmax is None: + if xmax <= Xe[0]: + jmax = 0 + elif xmax >= Xe[-1]: + jmax = self.mfgrid.ncol - 1 + + imin = ModflowGridIndices.find_position_in_array(Ye, ymax) + if imin is None: + if ymax >= Ye[0]: + imin = 0 + elif ymax <= Ye[-1]: + imin = self.mfgrid.nrow - 1 + + imax = ModflowGridIndices.find_position_in_array(Ye, ymin) + if imax is None: + if ymin >= Ye[0]: + imax = 0 + elif ymin <= Ye[-1]: + imax = self.mfgrid.nrow - 1 + + for i in range(imin, imax + 1): + for j in range(jmin, jmax + 1): + nodelist.append((i, j)) + + return nodelist + + def _intersect_polygon_structured(self, shp): + """intersect polygon with a structured grid. Uses + bounding box of the Polygon to limit search space. + + Parameters + ---------- + shp : shapely.geometry.Polygon + polygon to intersect with the grid + + Returns + ------- + numpy.recarray + a record array containing information about the intersection + + """ + + # initialize the result lists + nodelist = [] + areas = [] + vertices = [] + ixshapes = [] + + # transform polygon to local grid coordinates + if (self.mfgrid.xoffset != 0. or self.mfgrid.yoffset != 0.): + shp = translate(shp, xoff=-self.mfgrid.xoffset, + yoff=-self.mfgrid.yoffset) + if self.mfgrid.angrot != 0.: + shp = rotate(shp, -self.mfgrid.angrot, origin=(0., 0.)) + + # use the bounds of the polygon to restrict the cell search + minx, miny, maxx, maxy = shp.bounds + rectangle = ((minx, miny), (maxx, maxy)) + nodes = self._intersect_rectangle_structured(rectangle) + + for (i, j) in nodes: + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + cell_coords = [(self.mfgrid.xyedges[0][j], + self.mfgrid.xyedges[1][i]), + (self.mfgrid.xyedges[0][j+1], + self.mfgrid.xyedges[1][i]), + (self.mfgrid.xyedges[0][j+1], + self.mfgrid.xyedges[1][i+1]), + (self.mfgrid.xyedges[0][j], + self.mfgrid.xyedges[1][i+1])] + else: + cell_coords = self.mfgrid.get_cell_vertices(i, j) + node_polygon = Polygon(cell_coords) + if shp.intersects(node_polygon): + intersect = shp.intersection(node_polygon) + if intersect.area > 0.: + nodelist.append((i, j)) + areas.append(intersect.area) + + # if necessary, transform coordinates back to real world coordinates + if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. + or self.mfgrid.yoffset != 0.): + v_realworld = [] + for pt in intersect.__geo_interface__["coordinates"]: + rx, ry = transform([pt[0]], [pt[1]], self.mfgrid.xoffset, + self.mfgrid.yoffset, + self.mfgrid.angrot_radians, + inverse=False) + v_realworld.append([rx, ry]) + intersect_realworld = rotate(intersect, self.mfgrid.angrot, + origin=(0., 0.)) + intersect_realworld = translate(intersect_realworld, + self.mfgrid.xoffset, + self.mfgrid.yoffset) + else: + v_realworld = intersect.__geo_interface__[ + "coordinates"] + intersect_realworld = intersect + ixshapes.append(intersect_realworld) + vertices.append(v_realworld) + + rec = np.recarray(len(nodelist), names=["cellids", "vertices", "areas", "ixshapes"], + formats=["O", "O", "f8", "O"]) + rec.vertices = vertices + rec.areas = areas + rec.cellids = nodelist + rec.ixshapes = ixshapes + + return rec + + def plot_polygon(self, rec, ax=None, **kwargs): + """method to plot the polygon intersection results from + the resulting numpy.recarray. + + Note: only works when recarray has 'intersects' column! + + Parameters + ---------- + rec : numpy.recarray + record array containing intersection results (the resulting shapes) + ax : matplotlib.pyplot.axes, optional + axes to plot onto, if not provided, creates a new figure + **kwargs: + passed to the plot function + + Returns + ------- + ax: matplotlib.pyplot.axes + returns the axes handle + + """ + try: + from descartes import PolygonPatch + except ModuleNotFoundError: + raise ModuleNotFoundError( + "descartes module needed for plotting polygons") + + if ax is None: + _, ax = plt.subplots() + + for i, ishp in enumerate(rec.ixshapes): + ppi = PolygonPatch(ishp, facecolor="C{}".format(i % 10), **kwargs) + ax.add_patch(ppi) + + return ax + + def plot_linestring(self, rec, ax=None, **kwargs): + """method to plot the linestring intersection results from + the resulting numpy.recarray. + + Note: only works when recarray has 'intersects' column! + + Parameters + ---------- + rec : numpy.recarray + record array containing intersection results (the resulting shapes) + ax : matplotlib.pyplot.axes, optional + axes to plot onto, if not provided, creates a new figure + **kwargs: + passed to the plot function + + Returns + ------- + ax: matplotlib.pyplot.axes + returns the axes handle + + """ + if ax is None: + _, ax = plt.subplots() + + for i, ishp in enumerate(rec.ixshapes): + if ishp.type == "MultiLineString": + for part in ishp: + ax.plot(part.xy[0], part.xy[1], ls="-", + c="C{}".format(i % 10), **kwargs) + else: + ax.plot(ishp.xy[0], ishp.xy[1], ls="-", + c="C{}".format(i % 10), **kwargs) + + return ax + + def plot_point(self, rec, ax=None, **kwargs): + """method to plot the point intersection results from + the resulting numpy.recarray. + + Note: only works when recarray has 'intersects' column! + + Parameters + ---------- + rec : numpy.recarray + record array containing intersection results (the resulting shapes) + ax : matplotlib.pyplot.axes, optional + axes to plot onto, if not provided, creates a new figure + **kwargs: + passed to the scatter function + + Returns + ------- + ax: matplotlib.pyplot.axes + returns the axes handle + + """ + if ax is None: + _, ax = plt.subplots() + + x = [ip.x for ip in rec.ixshapes] + y = [ip.y for ip in rec.ixshapes] + + ax.scatter(x, y, **kwargs) + + return ax + + +class ModflowGridIndices: + ''' + Collection of methods that can be used to find cell indices for a + structured, but irregularly spaced MODFLOW grid. + ''' + + @staticmethod + def find_position_in_array(arr, x): + ''' + If arr has x positions for the left edge of a cell, then return the cell + index containing x. + + Arguments: + + *arr*: A one dimensional array (such as Xe) that contains coordinates + for the left cell edge. + + *x*: The x position to find in arr. + ''' + jpos = None + + if x == arr[-1]: + return len(arr) - 2 + + if x < min(arr[0], arr[-1]): + return None + + if x > max(arr[0], arr[-1]): + return None + + # go through each position + for j in range(len(arr)-1): + xl = arr[j] + xr = arr[j+1] + frac = (x - xl) / (xr - xl) + if 0. <= frac <= 1.0: + # if min(xl, xr) <= x < max(xl, xr): + jpos = j + return jpos + + return jpos + + @staticmethod + def kij_from_nodenumber(nodenumber, nlay, nrow, ncol): + ''' + Convert the modflow node number to a zero-based layer, row and column + format. Return (k0, i0, j0). + + Arguments: + + *nodenumber*: The cell nodenumber, ranging from 1 to number of + nodes. + + *nlay*: The number of layers. + + *nrow*: The number of rows. + + *ncol*: The number of columns. + + ''' + if nodenumber > nlay * nrow * ncol: + raise Exception('Error in function kij_from_nodenumber...') + n = nodenumber - 1 + k = int(n / nrow / ncol) + i = int((n - k * nrow * ncol) / ncol) + j = n - k * nrow * ncol - i * ncol + return (k, i, j) + + @staticmethod + def nodenumber_from_kij(k, i, j, nrow, ncol): + ''' + Calculate the nodenumber using the zero-based layer, row, and column + values. The first node has a value of 1. + + Arguments: + + *k*: The model layer number as a zero-based value. + + *i*: The model row number as a zero-based value. + + *j*: The model column number as a zero-based value. + + *nrow*: The number of model rows. + + *ncol*: The number of model columns. + ''' + return k * nrow * ncol + i * ncol + j + 1 + + @staticmethod + def nn0_from_kij(k, i, j, nrow, ncol): + ''' + Calculate the zero-based nodenumber using the zero-based layer, row, + and column values. The first node has a value of 0. + + Arguments: + + *k*: The model layer number as a zero-based value. + + *i*: The model row number as a zero-based value. + + *j*: The model column number as a zero-based value. + + *nrow*: The number of model rows. + + *ncol*: The number of model columns. + ''' + return k * nrow * ncol + i * ncol + j + + @staticmethod + def kij_from_nn0(n, nlay, nrow, ncol): + ''' + Convert the node number to a zero-based layer, row and column + format. Return (k0, i0, j0). + + Arguments: + + *nodenumber*: The cell nodenumber, ranging from 0 to number of + nodes - 1. + + *nlay*: The number of layers. + + *nrow*: The number of rows. + + *ncol*: The number of columns. + + ''' + if n > nlay * nrow * ncol: + raise Exception('Error in function kij_from_nodenumber...') + k = int(n / nrow / ncol) + i = int((n - k * nrow * ncol) / ncol) + j = n - k * nrow * ncol - i * ncol + return (k, i, j) From 1c8d83d1a6f2a90f4614962193666b3467528ad8 Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Thu, 4 Jul 2019 11:29:47 +0200 Subject: [PATCH 02/10] add gridintersect tests --- autotest/t065_test_gridintersect.py | 1028 +++++++++++++++++++++++++++ 1 file changed, 1028 insertions(+) create mode 100644 autotest/t065_test_gridintersect.py diff --git a/autotest/t065_test_gridintersect.py b/autotest/t065_test_gridintersect.py new file mode 100644 index 0000000000..d9001e5f58 --- /dev/null +++ b/autotest/t065_test_gridintersect.py @@ -0,0 +1,1028 @@ +import flopy.discretization as fgrid +import flopy.plot as fplot +import matplotlib.pyplot as plt +import numpy as np +from descartes import PolygonPatch +from flopy.utils.triangle import Triangle as Triangle +try: + from shapely.geometry import (LineString, MultiLineString, MultiPoint, + MultiPolygon, Point, Polygon) +except Exception as e: + print("Shapely not installed, tests cannot be run.") +from flopy.utils.gridintersect import GridIntersect + +triangle_exe = None + +def get_tri_grid(angrot=0., xyoffset=0., triangle_exe=None): + if not triangle_exe: + return -1 + maximum_area = 50. + x0, x1, y0, y1 = (0.0, 20.0, 0.0, 20.0) + domainpoly = [(x0, y0), (x0, y1), (x1, y1), (x1, y0)] + tri = Triangle(maximum_area=maximum_area, angle=45, model_ws=".", + exe_name=triangle_exe) + tri.add_polygon(domainpoly) + tri.build(verbose=False) + cell2d = tri.get_cell2d() + vertices = tri.get_vertices() + tgr = fgrid.VertexGrid(vertices, cell2d, + botm=np.atleast_2d(np.zeros(len(cell2d))), + top=np.ones(len(cell2d)), xoff=xyoffset, + yoff=xyoffset, angrot=angrot) + return tgr + + +def get_rect_grid(angrot=0., xyoffset=0.): + delc = 10*np.ones(2, dtype=np.float) + delr = 10*np.ones(2, dtype=np.float) + sgr = fgrid.StructuredGrid( + delc, delr, top=None, botm=None, xoff=xyoffset, yoff=xyoffset, angrot=angrot) + return sgr + + +def plot_structured_grid(sgr): + _, ax = plt.subplots(1, 1, figsize=(8, 8)) + sgr.plot(ax=ax) + return ax + + +def plot_vertex_grid(tgr): + _, ax = plt.subplots(1, 1, figsize=(8, 8)) + pmv = fplot.PlotMapView(modelgrid=tgr) + pmv.plot_grid(ax=ax) + return ax + + +def plot_ix_polygon_result(rec, ax): + for i, ishp in enumerate(rec.ixshapes): + ppi = PolygonPatch(ishp, facecolor="C{}".format(i % 10)) + ax.add_patch(ppi) + + +def plot_ix_linestring_result(rec, ax): + for i, ishp in enumerate(rec.ixshapes): + if ishp.type == "MultiLineString": + for part in ishp: + ax.plot(part.xy[0], part.xy[1], ls="-", + c="C{}".format(i % 10)) + else: + ax.plot(ishp.xy[0], ishp.xy[1], ls="-", + c="C{}".format(i % 10)) + + +def plot_ix_point_result(rec, ax): + x = [ip.x for ip in rec.ixshapes] + y = [ip.y for ip in rec.ixshapes] + ax.scatter(x, y) + + +# %% test point structured + + +def test_rect_grid_point_outside(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_point(Point(25., 25.)) + assert len(result) == 0 + return result + + +def test_rect_grid_point_on_outer_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_point(Point(20., 10.)) + assert len(result) == 1 + assert np.all(result.cellids[0] == (0, 1)) + return result + + +def test_rect_grid_point_on_inner_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_point(Point(10., 10.)) + assert len(result) == 1 + assert np.all(result.cellids[0] == (0, 0)) + return result + + +def test_rect_grid_multipoint_in_one_cell(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_point(MultiPoint([Point(1., 1.), Point(2., 2.)])) + assert len(result) == 1 + assert result.cellids[0] == (1, 0) + return result + + +def test_rect_grid_multipoint_in_multiple_cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_point(MultiPoint([Point(1., 1.), Point(12., 12.)])) + assert len(result) == 2 + assert result.cellids[0] == (1, 0) + assert result.cellids[1] == (0, 1) + return result + + +# %% test point shapely + + +def test_rect_grid_point_outside_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_point(Point(25., 25.)) + assert len(result) == 0 + return result + + +def test_rect_grid_point_on_outer_boundary_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_point(Point(20., 10.)) + assert len(result) == 1 + assert np.all(result.cellids[0] == (0, 1)) + return result + + +def test_rect_grid_point_on_inner_boundary_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_point(Point(10., 10.)) + assert len(result) == 1 + assert np.all(result.cellids[0] == (0, 0)) + return result + + +def test_rect_grid_multipoint_in_one_cell_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_point(MultiPoint([Point(1., 1.), Point(2., 2.)])) + assert len(result) == 1 + assert result.cellids[0] == (1, 0) + return result + + +def test_rect_grid_multipoint_in_multiple_cells_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_point(MultiPoint([Point(1., 1.), Point(12., 12.)])) + assert len(result) == 2 + assert result.cellids[0] == (0, 1) + assert result.cellids[1] == (1, 0) + return result + + +def test_tri_grid_point_outside(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_point(Point(25., 25.)) + assert len(result) == 0 + return result + + +def test_tri_grid_point_on_outer_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_point(Point(20., 10.)) + assert len(result) == 1 + assert np.all(result.cellids[0] == 0) + return result + + +def test_tri_grid_point_on_inner_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_point(Point(10., 10.)) + assert len(result) == 1 + assert np.all(result.cellids[0] == 0) + return result + + +def test_tri_grid_multipoint_in_one_cell(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_point(MultiPoint([Point(1., 1.), Point(2., 2.)])) + assert len(result) == 1 + assert result.cellids[0] == 1 + return result + + +def test_tri_grid_multipoint_in_multiple_cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_point(MultiPoint([Point(1., 1.), Point(12., 12.)])) + assert len(result) == 2 + assert result.cellids[0] == 0 + assert result.cellids[1] == 1 + return result + + +# %% test linestring structured + + +def test_rect_grid_linestring_outside(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_linestring(LineString([(25., 25.), (21., 5.)])) + assert len(result) == 0 + return result + + +def test_rect_grid_linestring_in_2cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_linestring(LineString([(5., 5.), (15., 5.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == (1, 0) + assert result.cellids[1] == (1, 1) + return result + + +def test_rect_grid_linestring_on_outer_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_linestring(LineString([(15., 20.), (5., 20.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[1] == (0, 0) + assert result.cellids[0] == (0, 1) + return result + + +def test_rect_grid_linestring_on_inner_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_linestring(LineString([(5., 10.), (15., 10.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == (0, 0) + assert result.cellids[1] == (0, 1) + return result + + +def test_rect_grid_multilinestring_in_one_cell(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_linestring(MultiLineString( + [LineString([(1., 1), (9., 1.)]), LineString([(1., 9.), (9., 9.)])])) + assert len(result) == 1 + assert result.lengths == 16. + assert result.cellids[0] == (1, 0) + return result + + +def test_rect_grid_linestring_in_and_out_of_cell(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_linestring( + LineString([(5., 9), (15., 5.), (5., 1.)])) + assert len(result) == 2 + assert result.cellids[0] == (1, 0) + assert result.cellids[1] == (1, 1) + assert np.allclose(result.lengths.sum(), 21.540659228538015) + return result + + +def test_rect_grid_linestring_in_and_out_of_cell2(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_linestring(LineString( + [(5, 15), (5., 9), (15., 5.), (5., 1.)])) + assert len(result) == 3 + # assert result.cellids[0] == (1, 0) + # assert result.cellids[1] == (1, 1) + # assert np.allclose(result.lengths.sum(), 21.540659228538015) + return result + + +# %% test linestring shapely + + +def test_rect_grid_linestring_outside_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(25., 25.), (21., 5.)])) + assert len(result) == 0 + return result + + +def test_rect_grid_linestring_in_2cells_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(5., 5.), (15., 5.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == (1, 0) + assert result.cellids[1] == (1, 1) + return result + + +def test_rect_grid_linestring_on_outer_boundary_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(15., 20.), (5., 20.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == (0, 0) + assert result.cellids[1] == (0, 1) + return result + + +def test_rect_grid_linestring_on_inner_boundary_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(5., 10.), (15., 10.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == (0, 0) + assert result.cellids[1] == (0, 1) + return result + + +def test_rect_grid_multilinestring_in_one_cell_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_linestring(MultiLineString( + [LineString([(1., 1), (9., 1.)]), LineString([(1., 9.), (9., 9.)])])) + assert len(result) == 1 + assert result.lengths == 16. + assert result.cellids[0] == (1, 0) + return result + + +def test_rect_grid_linestring_in_and_out_of_cell_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_linestring( + LineString([(5., 9), (15., 5.), (5., 1.)])) + assert len(result) == 2 + assert result.cellids[0] == (1, 0) + assert result.cellids[1] == (1, 1) + assert np.allclose(result.lengths.sum(), 21.540659228538015) + return result + + +def test_tri_grid_linestring_outside(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(25., 25.), (21., 5.)])) + assert len(result) == 0 + return result + + +def test_tri_grid_linestring_in_2cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(5., 5.), (5., 15.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == 1 + assert result.cellids[1] == 3 + return result + + +def test_tri_grid_linestring_on_outer_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(15., 20.), (5., 20.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == 2 + assert result.cellids[1] == 7 + return result + + +def test_tri_grid_linestring_on_inner_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_linestring(LineString([(5., 10.), (15., 10.)])) + assert len(result) == 2 + assert result.lengths.sum() == 10. + assert result.cellids[0] == 0 + assert result.cellids[1] == 1 + return result + + +def test_tri_grid_multilinestring_in_one_cell(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_linestring(MultiLineString( + [LineString([(1., 1), (9., 1.)]), LineString([(2., 2.), (9., 2.)])])) + assert len(result) == 1 + assert result.lengths == 15. + assert result.cellids[0] == 4 + return result + + +# %% test polygon structured + + +def test_rect_grid_polygon_outside(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_polygon( + Polygon([(21., 11.), (23., 17.), (25., 11.)])) + assert len(result) == 0 + return result + + +def test_rect_grid_polygon_in_2cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_polygon( + Polygon([(2.5, 5.0), (7.5, 5.0), (7.5, 15.), (2.5, 15.)])) + assert len(result) == 2 + assert result.areas.sum() == 50. + return result + + +def test_rect_grid_polygon_on_outer_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_polygon( + Polygon([(20., 5.0), (25., 5.0), (25., 15.), (20., 15.)])) + assert len(result) == 0 + return result + + +def test_rect_grid_polygon_on_inner_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + result = ix.intersect_polygon( + Polygon([(5., 10.0), (15., 10.0), (15., 5.), (5., 5.)])) + assert len(result) == 2 + assert result.areas.sum() == 50. + return result + + +def test_rect_grid_multipolygon_in_one_cell(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + p1 = Polygon([(1., 1.), (8., 1.), (8., 3.), (1., 3.)]) + p2 = Polygon([(1., 9.), (8., 9.), (8., 7.), (1., 7.)]) + p = MultiPolygon([p1, p2]) + result = ix.intersect_polygon(p) + assert len(result) == 1 + assert result.areas.sum() == 28. + return result + + +def test_rect_grid_multipolygon_in_multiple_cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + p1 = Polygon([(1., 1.), (19., 1.), (19., 3.), (1., 3.)]) + p2 = Polygon([(1., 9.), (19., 9.), (19., 7.), (1., 7.)]) + p = MultiPolygon([p1, p2]) + result = ix.intersect_polygon(p) + assert len(result) == 2 + assert result.areas.sum() == 72. + return result + + +def test_rect_grid_polygon_with_hole(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr, method="structured") + p = Polygon([(5., 5.), (5., 15.), (25., 15.), (25., -5.), + (5., -5.)], holes=[[(9., -1), (9, 11), (21, 11), (21, -1)]]) + result = ix.intersect_polygon(p) + assert len(result) == 3 + assert result.areas.sum() == 104. + return result + + +# %% test polygon shapely + + +def test_rect_grid_polygon_outside_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(21., 11.), (23., 17.), (25., 11.)])) + assert len(result) == 0 + return result + + +def test_rect_grid_polygon_in_2cells_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(2.5, 5.0), (7.5, 5.0), (7.5, 15.), (2.5, 15.)])) + assert len(result) == 2 + assert result.areas.sum() == 50. + return result + + +def test_rect_grid_polygon_on_outer_boundary_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(20., 5.0), (25., 5.0), (25., 15.), (20., 15.)])) + assert len(result) == 0 + return result + + +def test_rect_grid_polygon_on_inner_boundary_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(5., 10.0), (15., 10.0), (15., 5.), (5., 5.)])) + assert len(result) == 2 + assert result.areas.sum() == 50. + return result + + +def test_rect_grid_multipolygon_in_one_cell_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + p1 = Polygon([(1., 1.), (8., 1.), (8., 3.), (1., 3.)]) + p2 = Polygon([(1., 9.), (8., 9.), (8., 7.), (1., 7.)]) + p = MultiPolygon([p1, p2]) + result = ix.intersect_polygon(p) + assert len(result) == 1 + assert result.areas.sum() == 28. + return result + + +def test_rect_grid_multipolygon_in_multiple_cells_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + p1 = Polygon([(1., 1.), (19., 1.), (19., 3.), (1., 3.)]) + p2 = Polygon([(1., 9.), (19., 9.), (19., 7.), (1., 7.)]) + p = MultiPolygon([p1, p2]) + result = ix.intersect_polygon(p) + assert len(result) == 2 + assert result.areas.sum() == 72. + return result + + +def test_rect_grid_polygon_with_hole_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_rect_grid() + ix = GridIntersect(gr) + p = Polygon([(5., 5.), (5., 15.), (25., 15.), (25., -5.), + (5., -5.)], holes=[[(9., -1), (9, 11), (21, 11), (21, -1)]]) + result = ix.intersect_polygon(p) + assert len(result) == 3 + assert result.areas.sum() == 104. + return result + + +def test_tri_grid_polygon_outside(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(21., 11.), (23., 17.), (25., 11.)])) + assert len(result) == 0 + return result + + +def test_tri_grid_polygon_in_2cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(2.5, 5.0), (5.0, 5.0), (5.0, 15.), (2.5, 15.)])) + assert len(result) == 2 + assert result.areas.sum() == 25. + return result + + +def test_tri_grid_polygon_on_outer_boundary(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(20., 5.0), (25., 5.0), (25., 15.), (20., 15.)])) + assert len(result) == 0 + return result + + +def test_tri_grid_polygon_on_inner_boundary(): + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + result = ix.intersect_polygon( + Polygon([(5., 10.0), (15., 10.0), (15., 5.), (5., 5.)])) + assert len(result) == 4 + assert result.areas.sum() == 50. + return result + + +def test_tri_grid_multipolygon_in_one_cell(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + p1 = Polygon([(1., 1.), (8., 1.), (8., 3.), (3., 3.)]) + p2 = Polygon([(5., 5.), (8., 5.), (8., 8.)]) + p = MultiPolygon([p1, p2]) + result = ix.intersect_polygon(p) + assert len(result) == 1 + assert result.areas.sum() == 16.5 + return result + + +def test_tri_grid_multipolygon_in_multiple_cells(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + p1 = Polygon([(1., 1.), (19., 1.), (19., 3.), (1., 3.)]) + p2 = Polygon([(1., 9.), (19., 9.), (19., 7.), (1., 7.)]) + p = MultiPolygon([p1, p2]) + result = ix.intersect_polygon(p) + assert len(result) == 4 + assert result.areas.sum() == 72. + return result + + +def test_tri_grid_polygon_with_hole(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + gr = get_tri_grid(triangle_exe=triangle_exe) + if gr == -1: + return + ix = GridIntersect(gr) + p = Polygon([(5., 5.), (5., 15.), (25., 15.), (25., -5.), + (5., -5.)], holes=[[(9., -1), (9, 11), (21, 11), (21, -1)]]) + result = ix.intersect_polygon(p) + assert len(result) == 6 + assert result.areas.sum() == 104. + return result + + +# %% test rotated offset grids + + +def test_point_offset_rot_structured_grid(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + sgr = get_rect_grid(angrot=45., xyoffset=10.) + p = Point(10., 10 + np.sqrt(200.)) + ix = GridIntersect(sgr, method="structured") + result = ix.intersect_point(p) + # assert len(result) == 1. + return result + + +def test_linestring_offset_rot_structured_grid(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + sgr = get_rect_grid(angrot=45., xyoffset=10.) + ls = LineString([(5, 10. + np.sqrt(200.)), (15, 10. + np.sqrt(200.))]) + ix = GridIntersect(sgr, method="structured") + result = ix.intersect_linestring(ls) + # assert len(result) == 2. + return result + + +def test_polygon_offset_rot_structured_grid(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + sgr = get_rect_grid(angrot=45., xyoffset=10.) + p = Polygon([(5, 10. + np.sqrt(200.)), (15, 10. + np.sqrt(200.)), + (15, 10. + 1.5*np.sqrt(200.)), (5, 10. + 1.5*np.sqrt(200.))]) + ix = GridIntersect(sgr, method="structured") + result = ix.intersect_polygon(p) + # assert len(result) == 3. + return result + + +def test_point_offset_rot_structured_grid_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + sgr = get_rect_grid(angrot=45., xyoffset=10.) + p = Point(10., 10 + np.sqrt(200.)) + ix = GridIntersect(sgr, method="strtree") + result = ix.intersect_point(p) + # assert len(result) == 1. + return result + + +def test_linestring_offset_rot_structured_grid_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + sgr = get_rect_grid(angrot=45., xyoffset=10.) + ls = LineString([(5, 10. + np.sqrt(200.)), (15, 10. + np.sqrt(200.))]) + ix = GridIntersect(sgr, method="strtree") + result = ix.intersect_linestring(ls) + # assert len(result) == 2. + return result + + +def test_polygon_offset_rot_structured_grid_shapely(): + # avoid test fail when shapely not available + try: + import shapely + except: + return + sgr = get_rect_grid(angrot=45., xyoffset=10.) + p = Polygon([(5, 10. + np.sqrt(200.)), (15, 10. + np.sqrt(200.)), + (15, 10. + 1.5*np.sqrt(200.)), (5, 10. + 1.5*np.sqrt(200.))]) + ix = GridIntersect(sgr, method="strtree") + result = ix.intersect_polygon(p) + # assert len(result) == 3. + return result From 5efde89004c6ad6f64d05e21ee7b501f1074dc8d Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Thu, 4 Jul 2019 11:58:47 +0200 Subject: [PATCH 03/10] make codacy happy --- flopy/utils/gridintersect.py | 213 ++++++++++++++++++++--------------- 1 file changed, 124 insertions(+), 89 deletions(-) diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py index 8c74b36cc2..ec29738115 100644 --- a/flopy/utils/gridintersect.py +++ b/flopy/utils/gridintersect.py @@ -2,13 +2,13 @@ import numpy as np from .geometry import transform try: - import shapely from shapely.geometry import (LineString, MultiLineString, MultiPoint, - MultiPolygon, Point, Polygon, box) + Point, Polygon, box) from shapely.strtree import STRtree from shapely.affinity import translate, rotate except ModuleNotFoundError: - print("Shapely is needed for grid intersect operations! Please install shapely.") + print("Shapely is needed for grid intersect operations!" + "Please install shapely.") class GridIntersect: @@ -24,9 +24,9 @@ class GridIntersect: - Building the STRtree can take a while for large grids. Once built the intersect routines (for individual shapes) should be pretty fast. - The optimized routines for structured grids will generally outperform - the shapely routines because of the reduced overhead of building and parsing - the queried STR-tree. For Polygons, shapely is sometimes faster than - the optimized structured routines. + the shapely routines because of the reduced overhead of building and + parsing the queried STR-tree. For Polygons, shapely is sometimes faster + than the optimized structured routines. - The STR-tree query is based on the bounding box of the shape, if the bounding box of the shape covers nearly the entire grid, the query won't be able to limit the search space much resulting in slower @@ -95,7 +95,8 @@ def _rect_grid_to_shape_list(self): return shplist def _usg_grid_to_shape_list(self): - """internal method, convert unstructred grid to list of shapely polygons + """internal method, convert unstructred grid to list of shapely + polygons Returns ------- @@ -122,7 +123,9 @@ def _vtx_grid_to_shape_list(self): if isinstance(self.mfgrid._cell2d, np.recarray): for icell in self.mfgrid._cell2d.icell2d: points = [] - for iv in self.mfgrid._cell2d[["icvert_0", "icvert_1", "icvert_2"]][icell]: + for iv in self.mfgrid._cell2d[["icvert_0", + "icvert_1", + "icvert_2"]][icell]: points.append((self.mfgrid._vertices.xv[iv], self.mfgrid._vertices.yv[iv])) # close the polygon, if necessary @@ -145,7 +148,8 @@ def _vtx_grid_to_shape_list(self): shplist.append(p) return shplist - def _sort_strtree_result(self, shapelist): + @staticmethod + def _sort_strtree_result(shapelist): """internal method, sort strtree query result by node id Parameters @@ -205,7 +209,8 @@ def _intersect_point_shapely(self, shp, sort_by_cellid=True): if intersect.is_empty: continue - elif intersect.geom_type == "Point" or intersect.geom_type == "MultiPoint": + elif (intersect.geom_type == "Point" or + intersect.geom_type == "MultiPoint"): pt = intersect.__geo_interface__["coordinates"] if pt in vertices: continue @@ -213,7 +218,8 @@ def _intersect_point_shapely(self, shp, sort_by_cellid=True): vertices.append(pt) cellids.append(r.name) - rec = np.recarray(len(isectshp), names=["cellids", "vertices", "ixshapes"], + rec = np.recarray(len(isectshp), + names=["cellids", "vertices", "ixshapes"], formats=["O", "O", "O"]) rec.ixshapes = isectshp rec.vertices = vertices @@ -221,7 +227,8 @@ def _intersect_point_shapely(self, shp, sort_by_cellid=True): return rec - def _intersect_linestring_shapely(self, shp, keepzerolengths=False, sort_by_cellid=True): + def _intersect_linestring_shapely(self, shp, keepzerolengths=False, + sort_by_cellid=True): """intersect with LineString or MultiLineString Parameters @@ -259,7 +266,8 @@ def _intersect_linestring_shapely(self, shp, keepzerolengths=False, sort_by_cell # Results in: # - LineString: # - MultiLineString: - # - GeometryCollection (Empty, or collection of LineString and Point) + # - GeometryCollection (Empty, or collection of LineString + # and Point) if "LineString" in intersect.geom_type: # result is a LineString or MultiLineString @@ -306,7 +314,8 @@ def _intersect_linestring_shapely(self, shp, keepzerolengths=False, sort_by_cell # isectshp.append(intersect) # cellids.append(r.name) - rec = np.recarray(len(isectshp), names=["cellids", "vertices", "lengths", "ixshapes"], + rec = np.recarray(len(isectshp), + names=["cellids", "vertices", "lengths", "ixshapes"], formats=["O", "O", "f8", "O"]) rec.ixshapes = isectshp rec.vertices = vertices @@ -350,9 +359,11 @@ def _intersect_polygon_shapely(self, shp, sort_by_cellid=True): intersect = shp.intersection(r) # Results in - # - GeomCollection (Combination of Points, LineStrings and Polygons - # and Emptys) -> store as multiple entries in rec_array (only Polygons or MultiPolygons) - # - MultiPolygon (several Polygons) -> store as single entry in rec array + # - GeomCollection (Combination of Points, LineStrings and + # Polygons and Emptys) -> store as multiple entries in rec_array + # (only Polygons or MultiPolygons) + # - MultiPolygon (several Polygons) -> store as single entry + # in rec array # - Polygon (single Polygon) -> store as single entry in rec array if "Polygon" in intersect.geom_type: @@ -405,7 +416,8 @@ def _intersect_polygon_shapely(self, shp, sort_by_cellid=True): # vertices.append(np.nan) # cellids.append(r.name) - rec = np.recarray(len(isectshp), names=["cellids", "vertices", "areas", "ixshapes"], + rec = np.recarray(len(isectshp), + names=["cellids", "vertices", "areas", "ixshapes"], formats=["O", "O", "f8", "O"]) rec.ixshapes = isectshp rec.vertices = vertices @@ -525,7 +537,8 @@ def _intersect_linestring_structured(self, shp, keepzerolengths=False): lineclip = shp.intersection(pl) if lineclip.length == 0.: # linestring does not intersect modelgrid - return np.recarray(0, names=["cellids", "vertices", "lengths", "ixshapes"], + return np.recarray(0, names=["cellids", "vertices", + "lengths", "ixshapes"], formats=["O", "O", "f8", "O"]) if lineclip.geom_type is 'MultiLineString': # there are multiple lines nodelist, lengths, vertices = [], [], [] @@ -534,12 +547,14 @@ def _intersect_linestring_structured(self, shp, keepzerolengths=False): n, l, v, ix = self._get_nodes_intersecting_linestring(ls) nodelist += n lengths += l - # if necessary, transform coordinates back to real world coordinates + # if necessary, transform coordinates back to real + # world coordinates if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. or self.mfgrid.yoffset != 0.): v_realworld = [] for pt in v: - rx, ry = transform([pt[0]], [pt[1]], self.mfgrid.xoffset, + rx, ry = transform([pt[0]], [pt[1]], + self.mfgrid.xoffset, self.mfgrid.yoffset, self.mfgrid.angrot_radians, inverse=False) @@ -554,9 +569,11 @@ def _intersect_linestring_structured(self, shp, keepzerolengths=False): vertices += v_realworld ixshapes += ix_realworld else: # linestring is fully within grid - nodelist, lengths, vertices, ixshapes = self._get_nodes_intersecting_linestring( - lineclip) - # if necessary, transform coordinates back to real world coordinates + nodelist, lengths, vertices, ixshapes = \ + self._get_nodes_intersecting_linestring( + lineclip) + # if necessary, transform coordinates back to real + # world coordinates if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. or self.mfgrid.yoffset != 0.): v_realworld = [] @@ -602,7 +619,7 @@ def _intersect_linestring_structured(self, shp, keepzerolengths=False): templengths = [] tempverts = [] tempshapes = [] - for i in range(len(nodelist)): + for i, _ in enumerate(nodelist): if lengths[i] > 0: tempnodes.append(nodelist[i]) templengths.append(lengths[i]) @@ -613,7 +630,8 @@ def _intersect_linestring_structured(self, shp, keepzerolengths=False): vertices = tempverts ixshapes = tempshapes - rec = np.recarray(len(nodelist), names=["cellids", "vertices", "lengths", "ixshapes"], + rec = np.recarray(len(nodelist), + names=["cellids", "vertices", "lengths", "ixshapes"], formats=["O", "O", "f8", "O"]) rec.vertices = vertices rec.lengths = lengths @@ -689,8 +707,9 @@ def _get_nodes_intersecting_linestring(self, linestring): n = 0 while True: (i, j) = nodelist[n] - node, length, verts, ixshape = self._check_adjacent_cells_intersecting_line( - linestring, (i, j), nodelist) + node, length, verts, ixshape = \ + self._check_adjacent_cells_intersecting_line( + linestring, (i, j), nodelist) for inode, ilength, ivert, ix in zip(node, length, verts, ixshape): if inode is not None: @@ -706,7 +725,8 @@ def _get_nodes_intersecting_linestring(self, linestring): return nodelist, lengths, vertices, ixshapes - def _check_adjacent_cells_intersecting_line(self, linestring, i_j, nodelist): + def _check_adjacent_cells_intersecting_line(self, linestring, i_j, + nodelist): """helper method that follows a line through a structured grid Parameters @@ -979,17 +999,20 @@ def _intersect_polygon_structured(self, shp): nodelist.append((i, j)) areas.append(intersect.area) - # if necessary, transform coordinates back to real world coordinates + # if necessary, transform coordinates back to real + # world coordinates if (self.mfgrid.angrot != 0. or self.mfgrid.xoffset != 0. or self.mfgrid.yoffset != 0.): v_realworld = [] for pt in intersect.__geo_interface__["coordinates"]: - rx, ry = transform([pt[0]], [pt[1]], self.mfgrid.xoffset, + rx, ry = transform([pt[0]], [pt[1]], + self.mfgrid.xoffset, self.mfgrid.yoffset, self.mfgrid.angrot_radians, inverse=False) v_realworld.append([rx, ry]) - intersect_realworld = rotate(intersect, self.mfgrid.angrot, + intersect_realworld = rotate(intersect, + self.mfgrid.angrot, origin=(0., 0.)) intersect_realworld = translate(intersect_realworld, self.mfgrid.xoffset, @@ -1001,7 +1024,8 @@ def _intersect_polygon_structured(self, shp): ixshapes.append(intersect_realworld) vertices.append(v_realworld) - rec = np.recarray(len(nodelist), names=["cellids", "vertices", "areas", "ixshapes"], + rec = np.recarray(len(nodelist), + names=["cellids", "vertices", "areas", "ixshapes"], formats=["O", "O", "f8", "O"]) rec.vertices = vertices rec.areas = areas @@ -1010,7 +1034,8 @@ def _intersect_polygon_structured(self, shp): return rec - def plot_polygon(self, rec, ax=None, **kwargs): + @staticmethod + def plot_polygon(rec, ax=None, **kwargs): """method to plot the polygon intersection results from the resulting numpy.recarray. @@ -1019,7 +1044,8 @@ def plot_polygon(self, rec, ax=None, **kwargs): Parameters ---------- rec : numpy.recarray - record array containing intersection results (the resulting shapes) + record array containing intersection results + (the resulting shapes) ax : matplotlib.pyplot.axes, optional axes to plot onto, if not provided, creates a new figure **kwargs: @@ -1046,7 +1072,8 @@ def plot_polygon(self, rec, ax=None, **kwargs): return ax - def plot_linestring(self, rec, ax=None, **kwargs): + @staticmethod + def plot_linestring(rec, ax=None, **kwargs): """method to plot the linestring intersection results from the resulting numpy.recarray. @@ -1055,7 +1082,8 @@ def plot_linestring(self, rec, ax=None, **kwargs): Parameters ---------- rec : numpy.recarray - record array containing intersection results (the resulting shapes) + record array containing intersection results + (the resulting shapes) ax : matplotlib.pyplot.axes, optional axes to plot onto, if not provided, creates a new figure **kwargs: @@ -1081,7 +1109,8 @@ def plot_linestring(self, rec, ax=None, **kwargs): return ax - def plot_point(self, rec, ax=None, **kwargs): + @staticmethod + def plot_point(rec, ax=None, **kwargs): """method to plot the point intersection results from the resulting numpy.recarray. @@ -1090,7 +1119,8 @@ def plot_point(self, rec, ax=None, **kwargs): Parameters ---------- rec : numpy.recarray - record array containing intersection results (the resulting shapes) + record array containing intersection results + (the resulting shapes) ax : matplotlib.pyplot.axes, optional axes to plot onto, if not provided, creates a new figure **kwargs: @@ -1122,15 +1152,16 @@ class ModflowGridIndices: @staticmethod def find_position_in_array(arr, x): ''' - If arr has x positions for the left edge of a cell, then return the cell - index containing x. - - Arguments: + If arr has x positions for the left edge of a cell, then + return the cell index containing x. - *arr*: A one dimensional array (such as Xe) that contains coordinates - for the left cell edge. + Parameters + ---------- + arr : A one dimensional array (such as Xe) that contains + coordinates for the left cell edge. - *x*: The x position to find in arr. + x : float + The x position to find in arr. ''' jpos = None @@ -1161,16 +1192,17 @@ def kij_from_nodenumber(nodenumber, nlay, nrow, ncol): Convert the modflow node number to a zero-based layer, row and column format. Return (k0, i0, j0). - Arguments: - - *nodenumber*: The cell nodenumber, ranging from 1 to number of - nodes. - - *nlay*: The number of layers. - - *nrow*: The number of rows. - - *ncol*: The number of columns. + Parameters + ---------- + nodenumber: int + The cell nodenumber, ranging from 1 to number of + nodes. + nlay: int + The number of layers. + nrow: int + The number of rows. + ncol: int + The number of columns. ''' if nodenumber > nlay * nrow * ncol: @@ -1187,17 +1219,18 @@ def nodenumber_from_kij(k, i, j, nrow, ncol): Calculate the nodenumber using the zero-based layer, row, and column values. The first node has a value of 1. - Arguments: - - *k*: The model layer number as a zero-based value. - - *i*: The model row number as a zero-based value. - - *j*: The model column number as a zero-based value. - - *nrow*: The number of model rows. - - *ncol*: The number of model columns. + Parameters + ---------- + k : int + The model layer number as a zero-based value. + i : int + The model row number as a zero-based value. + j : int + The model column number as a zero-based value. + nrow : int + The number of model rows. + ncol : int + The number of model columns. ''' return k * nrow * ncol + i * ncol + j + 1 @@ -1205,19 +1238,20 @@ def nodenumber_from_kij(k, i, j, nrow, ncol): def nn0_from_kij(k, i, j, nrow, ncol): ''' Calculate the zero-based nodenumber using the zero-based layer, row, - and column values. The first node has a value of 0. - - Arguments: - - *k*: The model layer number as a zero-based value. - - *i*: The model row number as a zero-based value. - - *j*: The model column number as a zero-based value. - - *nrow*: The number of model rows. + and column values. The first node has a value of 0. - *ncol*: The number of model columns. + Parameters + ---------- + k : int + The model layer number as a zero-based value. + i : int + The model row number as a zero-based value. + j : int + The model column number as a zero-based value. + nrow : int + The number of model rows. + ncol : int + The number of model columns. ''' return k * nrow * ncol + i * ncol + j @@ -1227,16 +1261,17 @@ def kij_from_nn0(n, nlay, nrow, ncol): Convert the node number to a zero-based layer, row and column format. Return (k0, i0, j0). - Arguments: - - *nodenumber*: The cell nodenumber, ranging from 0 to number of - nodes - 1. - - *nlay*: The number of layers. - - *nrow*: The number of rows. - - *ncol*: The number of columns. + Parameters + ---------- + nodenumber : int + The cell nodenumber, ranging from 0 to number of + nodes - 1. + nlay : int + The number of layers. + nrow : int + The number of rows. + ncol : int + The number of columns. ''' if n > nlay * nrow * ncol: From f522f6c85b34bd38e5e8871c96eeec98c89ea36a Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Thu, 4 Jul 2019 12:20:06 +0200 Subject: [PATCH 04/10] attempt 2 making codacy happy --- autotest/t065_test_gridintersect.py | 5 +++-- flopy/utils/gridintersect.py | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/autotest/t065_test_gridintersect.py b/autotest/t065_test_gridintersect.py index d9001e5f58..bc4c5af371 100644 --- a/autotest/t065_test_gridintersect.py +++ b/autotest/t065_test_gridintersect.py @@ -3,7 +3,7 @@ import matplotlib.pyplot as plt import numpy as np from descartes import PolygonPatch -from flopy.utils.triangle import Triangle as Triangle +from flopy.utils.triangle import Triangle try: from shapely.geometry import (LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon) @@ -36,7 +36,8 @@ def get_rect_grid(angrot=0., xyoffset=0.): delc = 10*np.ones(2, dtype=np.float) delr = 10*np.ones(2, dtype=np.float) sgr = fgrid.StructuredGrid( - delc, delr, top=None, botm=None, xoff=xyoffset, yoff=xyoffset, angrot=angrot) + delc, delr, top=None, botm=None, xoff=xyoffset, yoff=xyoffset, + angrot=angrot) return sgr diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py index ec29738115..55ab55a1e2 100644 --- a/flopy/utils/gridintersect.py +++ b/flopy/utils/gridintersect.py @@ -103,10 +103,6 @@ def _usg_grid_to_shape_list(self): list list of shapely Polygons """ - - # shplist = [] - # TODO: add logic here - # return shplist raise NotImplementedError() def _vtx_grid_to_shape_list(self): From 5f8e55491f620c77c1d673cc5ee2484fc5eb116c Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Thu, 4 Jul 2019 14:36:18 +0200 Subject: [PATCH 05/10] add keepzerolengths option to strtree method --- flopy/utils/gridintersect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py index 55ab55a1e2..2c009c2adf 100644 --- a/flopy/utils/gridintersect.py +++ b/flopy/utils/gridintersect.py @@ -285,10 +285,10 @@ def _intersect_linestring_shapely(self, shp, keepzerolengths=False, if intersect.is_empty: continue - # if not keep_all_ix, continue if length == 0.0 - # if not keep_all_ix: - if intersect.length == 0.0: - continue + # if keep zero lengths + if not keepzerolengths: + if intersect.length == 0.0: + continue # loop over collection for geom in intersect.geoms: From 30f83e7eb2aa247cd01d2ec8ea3af8024d9f2866 Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Fri, 5 Jul 2019 18:50:20 +0200 Subject: [PATCH 06/10] add gridintersection demo notebook --- .../flopy3_grid_intersection_demo.ipynb | 1118 +++++++++++++++++ 1 file changed, 1118 insertions(+) create mode 100644 examples/Notebooks/flopy3_grid_intersection_demo.ipynb diff --git a/examples/Notebooks/flopy3_grid_intersection_demo.ipynb b/examples/Notebooks/flopy3_grid_intersection_demo.ipynb new file mode 100644 index 0000000000..4a5ba3743f --- /dev/null +++ b/examples/Notebooks/flopy3_grid_intersection_demo.ipynb @@ -0,0 +1,1118 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Intersecting grids with shapes\n", + "\n", + "_Note: This feature requires the shapely module (which is not a dependency of flopy) so must be installed by the user._\n", + "\n", + "This notebook shows the grid intersection functionality in flopy. The intersection methods are available through the GridIntersect object. A flopy model grid is passed to instantiate the object. Then the modelgrid can be intersected with Points, LineStrings and Polygons through the different intersect methods. There are two intersection modes: \n", + "- the first (default mode) is accessed by passing `method='strtree'` to `GridIntersect` and converts the modelgrid to a list of shapes that are sorted into an STR-tree to allow fast spatial queries. This works on structured and vertex grids.\n", + "- the second only works on structured grids and is accessed by passing `method='structured'` to `GridIntersect`. These methods use information from the structured grid to limit the search space for intersections and are generally faster.\n", + "\n", + "This notebook showcases the functionality of the GridIntersect class. \n", + "\n", + "\n", + "### Table of Contents\n", + "- [GridIntersect Class](#gridclass)\n", + "- [Rectangular regular grid](#rectgrid)\n", + " - [Polygon with regular grid](#rectgrid.1)\n", + " - [Polyline with regular grid](#rectgrid.2)\n", + " - [MultiPoint with regular grid](#rectgrid.3)\n", + "- [Triangular grid](#trigrid)\n", + " - [Polygon with triangular grid](#trigrid.1)\n", + " - [Polyline with triangular grid](#trigrid.2)\n", + " - [MultiPoint with triangular grid](#trigrid.3)\n", + "- [Tests](#tests)\n", + "- [Timings](#timings)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import some stuff" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "flopy is installed in C:\\GitHub\\flopy_db\\flopy\n", + "3.7.3 (default, Mar 27 2019, 17:13:21) [MSC v.1915 64 bit (AMD64)]\n", + "numpy version: 1.16.2\n", + "matplotlib version: 3.0.3\n", + "flopy version: 3.2.13\n" + ] + } + ], + "source": [ + "import sys\n", + "import os\n", + "import platform\n", + "import numpy as np\n", + "import matplotlib as mpl\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# run installed version of flopy or add local path\n", + "try:\n", + " import flopy\n", + " import flopy.discretization as fgrid\n", + " import flopy.plot as fplot\n", + " from flopy.utils.triangle import Triangle as Triangle\n", + " from flopy.utils.gridintersect import GridIntersect\n", + "except:\n", + " fpth = os.path.abspath(os.path.join('..', '..'))\n", + " sys.path.append(fpth)\n", + " import flopy\n", + " import flopy.discretization as fgrid\n", + " import flopy.plot as fplot\n", + " from flopy.utils.triangle import Triangle as Triangle\n", + " from flopy.utils.gridintersect import GridIntersect\n", + "\n", + "import shapely\n", + "from shapely.geometry import Polygon, Point, LineString, MultiLineString, MultiPoint, MultiPolygon\n", + "from shapely.strtree import STRtree \n", + "\n", + "print(sys.version)\n", + "print('numpy version: {}'.format(np.__version__))\n", + "print('matplotlib version: {}'.format(mpl.__version__))\n", + "print('flopy version: {}'.format(flopy.__version__))" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "triangle_exe = None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## [GridIntersect Class](#top)\n", + "\n", + "This GridIntersect class takes a flopy.mfgrid and by default converts it to a list of Shapely geometries and builds a STRTree which can be used to efficiently query the grid to perform intersections. If the method is set to 'structured', the STR-tree is not built and different intersection methods are applied (written by Chris Langevin). The following methods are available:\n", + "- ` _rect_grid_to_shape_list`: convert rectangular (structured) modflow grid to list of shapely geometries\n", + "- `_sort_strtree_result`: sort STRTree by cellid (to ensure lowest cellid is returned when shapes intersect with multiple grid cells)\n", + "- `_usg_grid_to_shape_list`: not yet implemented, convert unstructured grid to list of shapely geometries\n", + "- `_vtx_grid_to_shape_list`: convert vertex modflow grid to list of shapely geometries\n", + "- `_intersect_point_shapely`: intersect Shapely point with grid\n", + "- `_intersect_polygon_shapely`: intersect Shapely Polygon with grid\n", + "- `_intersect_linestring_shapely`: intersect Shapely LineString with grid\n", + "- `_intersect_point_structured`: intersect Shapely point with grid, using optimized search for structured grids\n", + "- `_intersect_polygon_structured`: intersect Shapely Polygon with grid, using optimized search for structured grids\n", + "- `_intersect_rectangle_structured`: intersect rectangle with grid to get intersecting node ids\n", + "- `_intersect_linestring_structured`: intersect Shapely LineString with structured grid, using optimized search for structured grids\n", + "- `_check_adjacent_cells_intersecting_line`: helper function to check adjacent cells in a structured grid for line intersections\n", + "- `_get_nodes_intersecting_linestring`: helper function to follow linestring through structured grid\n", + "- `intersect_point`: intersect point with grid, method depends on whether 'structured' or 'strtree' is passed at intialization.\n", + "- `intersect_linestring`: intersect linestring with grid, method depends on whether 'structured' or 'strtree' is passed at intialization.\n", + "- `intersect_polygon`: intersect polygon with grid, method depends on whether 'structured' or 'strtree' is passed at intialization.\n", + "- `plot_point`: plot intersect result for point\n", + "- `plot_polygon`: plot intersect result for polygons\n", + "- `plot_polyline`: plot intersect result for linestrings" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## [Rectangular regular grid](#top)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "delc = 10*np.ones(10, dtype=np.float)\n", + "delr = 10*np.ones(10, dtype=np.float)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "xoff = 0.\n", + "yoff = 0.\n", + "angrot = 0.\n", + "sgr = fgrid.StructuredGrid(delc, delr, top=None, botm=None, xoff=xoff, yoff=yoff, angrot=angrot)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "sgr.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### [Polygon with regular grid](#top)\n", + "Polygon to intersect with:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "p = Polygon(shell=[(15, 15), (20, 50), (35, 80.), (80, 50), (80, 40), (40, 5), (15, 12)], \n", + " holes=[[(25, 25), (25, 45), (45, 45), (45, 25)]])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create GridIntersect class" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "ix = GridIntersect(sgr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Do the intersect operation for a polygon" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "result = ix.intersect_polygon(p)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.86 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%timeit ix.intersect_polygon(p)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The results are returned as a numpy.recarray containing several fields based on the intersection performed. An explanation of the data in each of the possible fields is given below:\n", + "- cellids: contains the cell ids of the intersected grid cells\n", + "- vertices: contains the vertices of the intersected shape\n", + "- areas: contains the area of the polygon in that grid cell (only for polygons)\n", + "- lenghts: contains the length of the linestring in that grid cell (only for linestrings)\n", + "- ixshapes: contains the shapely object representing the intersected shape (useful for plotting the result)\n", + "\n", + "Looking at the data for the polygon intersection (convert to pandas.DataFrame for prettier formatting)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((2, 3), (((30.0, 70.0), (35.0, 80.0), (40.0, 76.66666666666667), (40.0, 70.0), (30.0, 70.0)),), 66.66666667, ),\n", + " ((2, 4), (((40.0, 76.66666666666667), (50.0, 70.0), (40.0, 70.0), (40.0, 76.66666666666667)),), 33.33333333, ),\n", + " ((3, 2), (((25.0, 60.0), (30.0, 70.0), (30.0, 60.0), (25.0, 60.0)),), 25. , ),\n", + " ((3, 3), (((30.0, 70.0), (40.0, 70.0), (40.0, 60.0), (30.0, 60.0), (30.0, 70.0)),), 100. , ),\n", + " ((3, 4), (((40.0, 70.0), (50.0, 70.0), (50.0, 60.0), (40.0, 60.0), (40.0, 70.0)),), 100. , ),\n", + " ((3, 5), (((50.0, 70.0), (60.0, 63.333333333333336), (60.0, 60.0), (50.0, 60.0), (50.0, 70.0)),), 66.66666667, ),\n", + " ((3, 6), (((60.0, 63.333333333333336), (65.0, 60.0), (60.0, 60.0), (60.0, 63.333333333333336)),), 8.33333333, ),\n", + " ((4, 2), (((20.0, 50.0), (25.0, 60.0), (30.0, 60.0), (30.0, 50.0), (20.0, 50.0)),), 75. , ),\n", + " ((4, 3), (((30.0, 60.0), (40.0, 60.0), (40.0, 50.0), (30.0, 50.0), (30.0, 60.0)),), 100. , ),\n", + " ((4, 4), (((40.0, 60.0), (50.0, 60.0), (50.0, 50.0), (40.0, 50.0), (40.0, 60.0)),), 100. , ),\n", + " ((4, 5), (((50.0, 60.0), (60.0, 60.0), (60.0, 50.0), (50.0, 50.0), (50.0, 60.0)),), 100. , ),\n", + " ((4, 6), (((65.0, 60.0), (70.0, 56.666666666666664), (70.0, 50.0), (60.0, 50.0), (60.0, 60.0), (65.0, 60.0)),), 91.66666667, ),\n", + " ((4, 7), (((70.0, 56.666666666666664), (80.0, 50.0), (70.0, 50.0), (70.0, 56.666666666666664)),), 33.33333333, ),\n", + " ((5, 1), (((18.571428571428573, 40.0), (20.0, 50.0), (20.0, 40.0), (18.571428571428573, 40.0)),), 7.14285714, ),\n", + " ((5, 2), (((30.0, 45.0), (25.0, 45.0), (25.0, 40.0), (20.0, 40.0), (20.0, 50.0), (30.0, 50.0), (30.0, 45.0)),), 75. , ),\n", + " ((5, 3), (((40.0, 45.0), (30.0, 45.0), (30.0, 50.0), (40.0, 50.0), (40.0, 45.0)),), 50. , ),\n", + " ((5, 4), (((45.0, 40.0), (45.0, 45.0), (40.0, 45.0), (40.0, 50.0), (50.0, 50.0), (50.0, 40.0), (45.0, 40.0)),), 75. , ),\n", + " ((5, 5), (((50.0, 50.0), (60.0, 50.0), (60.0, 40.0), (50.0, 40.0), (50.0, 50.0)),), 100. , ),\n", + " ((5, 6), (((60.0, 50.0), (70.0, 50.0), (70.0, 40.0), (60.0, 40.0), (60.0, 50.0)),), 100. , ),\n", + " ((5, 7), (((80.0, 50.0), (80.0, 40.0), (70.0, 40.0), (70.0, 50.0), (80.0, 50.0)),), 100. , ),\n", + " ((6, 1), (((17.142857142857142, 30.0), (18.571428571428573, 40.0), (20.0, 40.0), (20.0, 30.0), (17.142857142857142, 30.0)),), 21.42857143, ),\n", + " ((6, 2), (((25.0, 40.0), (25.0, 30.0), (20.0, 30.0), (20.0, 40.0), (25.0, 40.0)),), 50. , ),\n", + " ((6, 4), (((45.0, 30.0), (45.0, 40.0), (50.0, 40.0), (50.0, 30.0), (45.0, 30.0)),), 50. , ),\n", + " ((6, 5), (((50.0, 40.0), (60.0, 40.0), (60.0, 30.0), (50.0, 30.0), (50.0, 40.0)),), 100. , ),\n", + " ((6, 6), (((70.0, 31.25), (68.57142857142857, 30.0), (60.0, 30.0), (60.0, 40.0), (70.0, 40.0), (70.0, 31.25)),), 99.10714286, ),\n", + " ((6, 7), (((80.0, 40.0), (70.0, 31.25), (70.0, 40.0), (80.0, 40.0)),), 43.75 , ),\n", + " ((7, 1), (((15.714285714285714, 20.0), (17.142857142857142, 30.0), (20.0, 30.0), (20.0, 20.0), (15.714285714285714, 20.0)),), 35.71428571, ),\n", + " ((7, 2), (((25.0, 30.0), (25.0, 25.0), (30.0, 25.0), (30.0, 20.0), (20.0, 20.0), (20.0, 30.0), (25.0, 30.0)),), 75. , ),\n", + " ((7, 3), (((30.0, 25.0), (40.0, 25.0), (40.0, 20.0), (30.0, 20.0), (30.0, 25.0)),), 50. , ),\n", + " ((7, 4), (((40.0, 25.0), (45.0, 25.0), (45.0, 30.0), (50.0, 30.0), (50.0, 20.0), (40.0, 20.0), (40.0, 25.0)),), 75. , ),\n", + " ((7, 5), (((60.0, 22.5), (57.142857142857146, 20.0), (50.0, 20.0), (50.0, 30.0), (60.0, 30.0), (60.0, 22.5)),), 96.42857143, ),\n", + " ((7, 6), (((68.57142857142857, 30.0), (60.0, 22.5), (60.0, 30.0), (68.57142857142857, 30.0)),), 32.14285714, ),\n", + " ((8, 1), (((15.0, 15.0), (15.714285714285714, 20.0), (20.0, 20.0), (20.0, 10.6), (15.0, 12.0), (15.0, 15.0)),), 41.71428571, ),\n", + " ((8, 2), (((22.142857142857142, 10.0), (20.0, 10.6), (20.0, 20.0), (30.0, 20.0), (30.0, 10.0), (22.142857142857142, 10.0)),), 99.35714286, ),\n", + " ((8, 3), (((30.0, 20.0), (40.0, 20.0), (40.0, 10.0), (30.0, 10.0), (30.0, 20.0)),), 100. , ),\n", + " ((8, 4), (((50.0, 13.75), (45.714285714285715, 10.0), (40.0, 10.0), (40.0, 20.0), (50.0, 20.0), (50.0, 13.75)),), 91.96428571, ),\n", + " ((8, 5), (((57.142857142857146, 20.0), (50.0, 13.75), (50.0, 20.0), (57.142857142857146, 20.0)),), 22.32142857, ),\n", + " ((9, 2), (((30.0, 7.8), (22.142857142857142, 10.0), (30.0, 10.0), (30.0, 7.8)),), 8.64285714, ),\n", + " ((9, 3), (((40.0, 5.0), (30.0, 7.8), (30.0, 10.0), (40.0, 10.0), (40.0, 5.0)),), 36. , ),\n", + " ((9, 4), (((45.714285714285715, 10.0), (40.0, 5.0), (40.0, 10.0), (45.714285714285715, 10.0)),), 14.28571429, )],\n", + " dtype=[('cellids', 'O'), ('vertices', 'O'), ('areas', '" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", + "sgr.plot(ax=ax)\n", + "ix.plot_polygon(result, ax=ax)\n", + "\n", + "# only cells that intersect with shape\n", + "for irow, icol in result.cellids:\n", + " h2, = ax.plot(sgr.xcellcenters[0, icol], sgr.ycellcenters[irow, 0], \"kx\", label=\"centroids of intersected gridcells\")\n", + " \n", + "ax.legend([h2], [i.get_label() for i in [h2]], loc=\"best\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, the intersection can be calculated using special methods optimized for structured grids. Access these methods by instantiating the GridIntersect class with the `method=\"structured\"` keyword argument." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((2, 3), (((30.0, 70.0), (35.0, 80.0), (40.0, 76.66666666666667), (40.0, 70.0), (30.0, 70.0)),), 66.66666667, ),\n", + " ((2, 4), (((40.0, 76.66666666666667), (50.0, 70.0), (40.0, 70.0), (40.0, 76.66666666666667)),), 33.33333333, ),\n", + " ((3, 2), (((25.0, 60.0), (30.0, 70.0), (30.0, 60.0), (25.0, 60.0)),), 25. , ),\n", + " ((3, 3), (((30.0, 70.0), (40.0, 70.0), (40.0, 60.0), (30.0, 60.0), (30.0, 70.0)),), 100. , ),\n", + " ((3, 4), (((40.0, 70.0), (50.0, 70.0), (50.0, 60.0), (40.0, 60.0), (40.0, 70.0)),), 100. , ),\n", + " ((3, 5), (((50.0, 70.0), (60.0, 63.333333333333336), (60.0, 60.0), (50.0, 60.0), (50.0, 70.0)),), 66.66666667, ),\n", + " ((3, 6), (((60.0, 63.333333333333336), (65.0, 60.0), (60.0, 60.0), (60.0, 63.333333333333336)),), 8.33333333, ),\n", + " ((4, 2), (((20.0, 50.0), (25.0, 60.0), (30.0, 60.0), (30.0, 50.0), (20.0, 50.0)),), 75. , ),\n", + " ((4, 3), (((30.0, 60.0), (40.0, 60.0), (40.0, 50.0), (30.0, 50.0), (30.0, 60.0)),), 100. , ),\n", + " ((4, 4), (((40.0, 60.0), (50.0, 60.0), (50.0, 50.0), (40.0, 50.0), (40.0, 60.0)),), 100. , ),\n", + " ((4, 5), (((50.0, 60.0), (60.0, 60.0), (60.0, 50.0), (50.0, 50.0), (50.0, 60.0)),), 100. , ),\n", + " ((4, 6), (((65.0, 60.0), (70.0, 56.666666666666664), (70.0, 50.0), (60.0, 50.0), (60.0, 60.0), (65.0, 60.0)),), 91.66666667, ),\n", + " ((4, 7), (((70.0, 56.666666666666664), (80.0, 50.0), (70.0, 50.0), (70.0, 56.666666666666664)),), 33.33333333, ),\n", + " ((5, 1), (((18.571428571428573, 40.0), (20.0, 50.0), (20.0, 40.0), (18.571428571428573, 40.0)),), 7.14285714, ),\n", + " ((5, 2), (((30.0, 45.0), (25.0, 45.0), (25.0, 40.0), (20.0, 40.0), (20.0, 50.0), (30.0, 50.0), (30.0, 45.0)),), 75. , ),\n", + " ((5, 3), (((40.0, 45.0), (30.0, 45.0), (30.0, 50.0), (40.0, 50.0), (40.0, 45.0)),), 50. , ),\n", + " ((5, 4), (((45.0, 40.0), (45.0, 45.0), (40.0, 45.0), (40.0, 50.0), (50.0, 50.0), (50.0, 40.0), (45.0, 40.0)),), 75. , ),\n", + " ((5, 5), (((50.0, 50.0), (60.0, 50.0), (60.0, 40.0), (50.0, 40.0), (50.0, 50.0)),), 100. , ),\n", + " ((5, 6), (((60.0, 50.0), (70.0, 50.0), (70.0, 40.0), (60.0, 40.0), (60.0, 50.0)),), 100. , ),\n", + " ((5, 7), (((80.0, 50.0), (80.0, 40.0), (70.0, 40.0), (70.0, 50.0), (80.0, 50.0)),), 100. , ),\n", + " ((6, 1), (((17.142857142857142, 30.0), (18.571428571428573, 40.0), (20.0, 40.0), (20.0, 30.0), (17.142857142857142, 30.0)),), 21.42857143, ),\n", + " ((6, 2), (((25.0, 40.0), (25.0, 30.0), (20.0, 30.0), (20.0, 40.0), (25.0, 40.0)),), 50. , ),\n", + " ((6, 4), (((45.0, 30.0), (45.0, 40.0), (50.0, 40.0), (50.0, 30.0), (45.0, 30.0)),), 50. , ),\n", + " ((6, 5), (((50.0, 40.0), (60.0, 40.0), (60.0, 30.0), (50.0, 30.0), (50.0, 40.0)),), 100. , ),\n", + " ((6, 6), (((70.0, 31.25), (68.57142857142857, 30.0), (60.0, 30.0), (60.0, 40.0), (70.0, 40.0), (70.0, 31.25)),), 99.10714286, ),\n", + " ((6, 7), (((80.0, 40.0), (70.0, 31.25), (70.0, 40.0), (80.0, 40.0)),), 43.75 , ),\n", + " ((7, 1), (((15.714285714285714, 20.0), (17.142857142857142, 30.0), (20.0, 30.0), (20.0, 20.0), (15.714285714285714, 20.0)),), 35.71428571, ),\n", + " ((7, 2), (((25.0, 30.0), (25.0, 25.0), (30.0, 25.0), (30.0, 20.0), (20.0, 20.0), (20.0, 30.0), (25.0, 30.0)),), 75. , ),\n", + " ((7, 3), (((30.0, 25.0), (40.0, 25.0), (40.0, 20.0), (30.0, 20.0), (30.0, 25.0)),), 50. , ),\n", + " ((7, 4), (((40.0, 25.0), (45.0, 25.0), (45.0, 30.0), (50.0, 30.0), (50.0, 20.0), (40.0, 20.0), (40.0, 25.0)),), 75. , ),\n", + " ((7, 5), (((60.0, 22.5), (57.142857142857146, 20.0), (50.0, 20.0), (50.0, 30.0), (60.0, 30.0), (60.0, 22.5)),), 96.42857143, ),\n", + " ((7, 6), (((68.57142857142857, 30.0), (60.0, 22.5), (60.0, 30.0), (68.57142857142857, 30.0)),), 32.14285714, ),\n", + " ((8, 1), (((15.0, 15.0), (15.714285714285714, 20.0), (20.0, 20.0), (20.0, 10.6), (15.0, 12.0), (15.0, 15.0)),), 41.71428571, ),\n", + " ((8, 2), (((22.142857142857142, 10.0), (20.0, 10.6), (20.0, 20.0), (30.0, 20.0), (30.0, 10.0), (22.142857142857142, 10.0)),), 99.35714286, ),\n", + " ((8, 3), (((30.0, 20.0), (40.0, 20.0), (40.0, 10.0), (30.0, 10.0), (30.0, 20.0)),), 100. , ),\n", + " ((8, 4), (((50.0, 13.75), (45.714285714285715, 10.0), (40.0, 10.0), (40.0, 20.0), (50.0, 20.0), (50.0, 13.75)),), 91.96428571, ),\n", + " ((8, 5), (((57.142857142857146, 20.0), (50.0, 13.75), (50.0, 20.0), (57.142857142857146, 20.0)),), 22.32142857, ),\n", + " ((9, 2), (((30.0, 7.8), (22.142857142857142, 10.0), (30.0, 10.0), (30.0, 7.8)),), 8.64285714, ),\n", + " ((9, 3), (((40.0, 5.0), (30.0, 7.8), (30.0, 10.0), (40.0, 10.0), (40.0, 5.0)),), 36. , ),\n", + " ((9, 4), (((45.714285714285715, 10.0), (40.0, 5.0), (40.0, 10.0), (45.714285714285715, 10.0)),), 14.28571429, )],\n", + " dtype=[('cellids', 'O'), ('vertices', 'O'), ('areas', '[Polyline with regular grid](#top)\n", + "MultiLineString to intersect with:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "ls1 = LineString([(95, 105), (30, 50)])\n", + "ls2 = LineString([(30, 50), (90, 22)])\n", + "ls3 = LineString([(90, 22), (0, 0)])\n", + "mls = MultiLineString(lines=[ls1, ls2, ls3])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8.01 ms ± 364 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%timeit ix.intersect_linestring(mls)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "result = ix.intersect_linestring(mls)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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WbKksngPd/j3e8UVVzB8+HA++ugjzjrrLF544QWMMVxzzTX86U9/An6Z2/vBBx9UK+vx3vtErz/R8T88e3X2XdV/N6WlpYSFhbF69erjvleF6vxcRRxDy2DWG5+4hlxxzTglJYWHHnqIlJSUI64p18bYsWNJSUkhMzMTgKysLADOPPNMZs6cWfm8E/2SDQ0NrRwpVuWMM85gzpw55Ofnc+DAAd577z1OP/30o57zxhtvUFJSws6dOyu/r7y8PHJycjj33HN58sknq8wyYsQIXn31VaDsZqh27drRqlWrahyB4/v19zVhwgSefvrpyq+PdVw2bdpE9+7due222zj//PNZs2YNY8eO5e2332bPnj1A2bFOS0tjxIgRzJ8/n4pPAqv4GVT3vc8444zK7/2TTz4hOzv7qDzDhg1j/vz5ZGdnU1xczDvvvFOt73/YsGG89957AJXvUZHlxRdfJD8/vzJzq1atiImJ4a233gLKSvvbb7895r6r83MVcYSM78pu3tqzoWwZzNP+oDKuA58o5NTUVFJSUipHxB6Ph5SUFFJTU2u9z759+/LnP/+Z0aNHM3DgQO68804A/va3v7F69WoGDBhAnz59eO655467n/DwcEaOHEm/fv24++67j3p88ODBTJ48mWHDhjF8+HCuv/76I05XA1x00UX07NmT/v37k5CQwOjRowHIzc3lvPPOY8CAAYwePZonnnjiqP1PnTqVFStWMGDAAO69915mz55d20NyhN/85je89957lTdWPfXUU5Xvc7zj8uabb9KvXz9iY2PZsGEDV199NX369GHGjBlccskljB49mvHjx7Nz504iIiJ4/vnnmTRpEgMHDqy8G7y67/3AAw/w9ddfM3jwYD7//HO6det2VJ4uXbpw3333MXz4cMaNG0efPn1o3br1Cb//hx9+mBdffJG4uLgjLgGcffbZnH/++QwdOpTY2NjKKU2vvvoqM2fOZODAgfTt25f333//WLuu1s9VxHY/fAovng2mtGwZTK1JXWdWdU+ZNqSqPg/5+++/p3fv3jYlOjYt79hw7Mqbl5dHy5YtKS4u5qKLLuK6667joosuOu5rnHBsa/L/iJs+W1ZZG0695G2kZTDdeGyvvfbalcaYobXdh0+MkEXq4sEHHyQ2NpZ+/foRExPDhRdeaHckEWfSMpgNSoUsTd5jjz3G6tWr2bBh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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", + "sgr.plot(ax=ax)\n", + "ix.plot_linestring(result, ax=ax)\n", + "\n", + "for irow, icol in result.cellids:\n", + " h2, = ax.plot(sgr.xcellcenters[0, icol], sgr.ycellcenters[irow, 0], \"kx\", label=\"centroids of intersected gridcells\")\n", + " \n", + "ax.legend([h2], [i.get_label() for i in [h2]], loc=\"best\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Same as before, the intersect for structured grids can also be performed with a different method optimized for structured grids" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((9, 1), list([[(20.0, 4.888888888888889), (10.0, 2.4444444444444446)]]), 10.29443095, list([])),\n", + " ((0, 7), list([[(80.0, 92.3076923076923), (77.27272727272728, 90.0)]]), 3.57259854, list([])),\n", + " ((1, 6), list([[(70.0, 83.84615384615384), (65.45454545454545, 80.0)]]), 5.9543309 , list([])),\n", + " ((9, 4), list([[(40.90909090909091, 10.0), (40.0, 9.777777777777779)]]), 0.93585736, list([])),\n", + " ((2, 5), list([[(60.0, 75.38461538461539), (53.63636363636364, 70.0)]]), 8.33606326, list([])),\n", + " ((8, 5), list([[(60.0, 14.666666666666666), (50.0, 12.222222222222221)]]), 10.29443095, list([])),\n", + " ((9, 0), list([[(10.0, 2.4444444444444446), (0.0, 0.0)]]), 10.29443095, list([])),\n", + " ((4, 4), list([[(41.81818181818182, 60.0), (40.0, 58.46153846153846)]]), 2.38173236, list([])),\n", + " ((3, 4), list([[(50.0, 66.92307692307692), (41.81818181818182, 60.0)]]), 10.71779561, list([])),\n", + " ((8, 6), list([[(70.0, 17.11111111111111), (60.0, 14.666666666666666)]]), 10.29443095, list([])),\n", + " ((2, 6), list([[(65.45454545454545, 80.0), (60.0, 75.38461538461539)]]), 7.14519708, list([])),\n", + " ((9, 3), list([[(40.0, 9.777777777777779), (30.0, 7.333333333333334)]]), 10.29443095, list([])),\n", + " ((8, 7), list([[(80.0, 19.555555555555557), (70.0, 17.11111111111111)]]), 10.29443095, list([])),\n", + " ((0, 8), list([[(89.0909090909091, 100.0), (80.0, 92.3076923076923)]]), 11.90866179, list([])),\n", + " ((3, 5), list([[(53.63636363636364, 70.0), (50.0, 66.92307692307692)]]), 4.76346472, list([])),\n", + " ((9, 2), list([[(30.0, 7.333333333333334), (20.0, 4.888888888888889)]]), 10.29443095, list([])),\n", + " ((8, 8), list([[(81.81818181818181, 20.0), (80.0, 19.555555555555557)]]), 1.87171472, list([])),\n", + " ((4, 3), list([[(40.0, 58.46153846153846), (30.0, 50.0)]]), 13.09952797, list([])),\n", + " ((1, 7), list([[(77.27272727272728, 90.0), (70.0, 83.84615384615384)]]), 9.52692944, list([])),\n", + " ((7, 8), list([[(90.0, 22.0), (81.81818181818181, 20.0)]]), 8.42271623, list([])),\n", + " ((8, 4), list([[(50.0, 12.222222222222221), (40.90909090909091, 10.0)]]), 9.35857359, list([]))],\n", + " dtype=[('cellids', 'O'), ('vertices', 'O'), ('lengths', '\n", + "\n", + "MultiPoint to intersect with" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "mp = MultiPoint(points=[Point(50.0, 0.0), Point(45., 45.), \n", + " Point(10., 10.), Point(150., 100.)])" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "result = ix.intersect_point(mp)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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t++rnd+Dzra9du3aWLZsWfu2v/SlL8U3v/nNiIi45557Amj/fu7U2TYaGhriP//zP9v366hRo2Ljxo277bd58+bFF7/4xYiIqKmpiVWrVnX6fYjY85ieN29eAEuijC70ZU+SsvPBD36Q6dOnc9VVVzFz5kzq6urKWt99993H+eef3/4SmqFDhwJw7733snz58vb5/vrXv7JhwwYAJk+ezMCBAxk4cCBHHnkkL7zwQofrHjduXPvLXxYtWsQnPvEJhgwZAsB5553HAw88wLhx49rnv//++7ngggvo168fRx11FKeddhoAhx12GIMGDeKSSy5h8uTJ7dc2d7Vo0SJuu+02AE477TTWrVvHyy+/vF/7Yud63//+93P77bd3OM/e9svZZ5/NwQcfDMDJJ5/Mt771LVatWsV5553HqFGjWLhwIUuXLm0/o/HKK69w5JFH8vvf/56JEye276ud34POtr3zlPjObd9///3teSdPnswRRxyxx7KdbeNXv/oVd999N9/73veAtrv+V65c2ek+OuWUU6ivr2fq1Kmcd955nc7X0yxkSdlZtGgRc+bMYebMmcyZM4e6urqySjkiOrwz9rXXXuN//ud/2gtmVwMHDmz/vF+/fp1eMx08ePBu2+mKjrL079+fhx56iIULF3LzzTdzww03cN999+3xdXRlXXtz0EEHAXv/mva2X3b+sgHwmc98hhNPPJGf/exnfOQjH+Gmm24iIpg2bRrf/va3d1vu7rvv7lLWndvetGkTsPvrkPe1fGff54jgtttu4z3vec9u0zv7JevGG2/kwQcf5Gc/+xljx47l4YcfZtiwYfvMXi6vIUvKyqJFi7jkkktobm5m1qxZNDc3M3Xq1PZryt0xadIkmpubWbduHdB2LRbgzDPP5IYbbmif7+GHH97reg499ND2kWJHJk6cyJ133snmzZvZtGkTd9xxBx/60If2mOfmm29mx44drF69uv3r2rhxIy+//DJnnXUW1157bYdZJk6cyE9+8hOg7Wao4cOHc9hhh3VhD+zdG7+uru6XJ598kmOOOYYvf/nLnH322TzyyCNMmjSJBQsWtF8bX79+Pc888wwnn3wy//3f/83Ov+y383vQ1W3v+rX/4he/4MUXX9wjT2fb+MhHPsL111/f/gvNsmXL9ro/nnjiCU488URmzZrF8OHDefbZZ/c6f0+xkCVl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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", + "sgr.plot(ax=ax)\n", + "ix.plot_point(result, ax=ax, s=50)\n", + " \n", + "for irow, icol in result.cellids:\n", + " h2, = ax.plot(sgr.xcellcenters[0, icol], sgr.ycellcenters[irow, 0], \"kx\", label=\"centroids of intersected cells\")\n", + " \n", + "ax.legend([h2], [i.get_label() for i in [h2]], loc=\"best\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Same as before, the intersect for structured grids can also be performed with a different method optimized for structured grids" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((9, 4), ),\n", + " ((5, 4), ),\n", + " ((8, 0), )],\n", + " dtype=[('cellids', 'O'), ('ixshapes', 'O')])" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ixs = GridIntersect(sgr, method=\"structured\")\n", + "# pd.DataFrame(ixs.intersect_point(mp))\n", + "ixs.intersect_point(mp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## [Triangular Grid](#top)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "maximum_area = 50.\n", + "x0, x1, y0, y1 = sgr.extent\n", + "domainpoly = [(x0, y0), (x0, y1), (x1, y1), (x1, y0)]\n", + "tri = Triangle(maximum_area=maximum_area, angle=30, model_ws=\".\", \n", + " exe_name=triangle_exe)\n", + "tri.add_polygon(domainpoly)\n", + "tri.build(verbose=False)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "cell2d = tri.get_cell2d()\n", + "vertices = tri.get_vertices()\n", + "tgr = fgrid.VertexGrid(vertices, cell2d)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", + "pmv = fplot.PlotMapView(modelgrid=tgr)\n", + "pmv.plot_grid(ax=ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### [Polygon with triangular grid](#top)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "ix2 = GridIntersect(tgr)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "result = ix2.intersect_polygon(p)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", + "pmv = fplot.PlotMapView(ax=ax, modelgrid=tgr)\n", + "pmv.plot_grid()\n", + "ix.plot_polygon(result, ax=ax)\n", + "\n", + "# only cells that intersect with shape\n", + "for cellid in result.cellids:\n", + " h2, = ax.plot(tgr.xcellcenters[cellid], tgr.ycellcenters[cellid], \"kx\", label=\"centroids of intersected gridcells\")\n", + " \n", + "ax.legend([h2], [i.get_label() for i in [h2]], loc=\"best\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### [LineString with triangular grid](#top)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "result = ix2.intersect_linestring(mls)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", + "pmv = fplot.PlotMapView(ax=ax, modelgrid=tgr)\n", + "pmv.plot_grid()\n", + "ix2.plot_linestring(result, ax=ax, lw=3)\n", + "\n", + "for cellid in result.cellids:\n", + " h2, = ax.plot(tgr.xcellcenters[cellid], tgr.ycellcenters[cellid], \"kx\", label=\"centroids of intersected gridcells\")\n", + " \n", + "ax.legend([h2], [i.get_label() for i in [h2]], loc=\"best\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### [MultiPoint with triangular grid](#top)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "result = ix2.intersect_point(mp)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", + "pmv = fplot.PlotMapView(ax=ax, modelgrid=tgr)\n", + "pmv.plot_grid()\n", + "ix2.plot_point(result, ax=ax)\n", + " \n", + "for cellid in result.cellids:\n", + " h2, = ax.plot(tgr.xcellcenters[cellid], tgr.ycellcenters[cellid], \"kx\", label=\"return one intersecting grid cell per point\")\n", + " \n", + "ax.legend([h2], [i.get_label() for i in [h2]], loc=\"best\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## [Tests](#top)\n", + "Tests are written for Points, LineStrings and Polygons for both rectangular (regular) grids, triangular grids, and rotated and offset regular grids." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "============================= test session starts =============================\n", + "platform win32 -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0\n", + "rootdir: C:\\GitHub\\flopy_db, inifile:\n", + "plugins: remotedata-0.3.1, openfiles-0.3.2, doctestplus-0.3.0, cov-2.7.1, arraydiff-0.3\n", + "collected 0 items / 1 errors\n", + "WARNING: Failed to generate report: No data to report.\n", + "\n", + "\n", + "=================================== ERRORS ====================================\n", + "____________ ERROR collecting autotest/t065_test_gridintersect.py _____________\n", + "ImportError while importing test module 'C:\\GitHub\\flopy_db\\autotest\\t065_test_gridintersect.py'.\n", + "Hint: make sure your test modules/packages have valid Python names.\n", + "Traceback:\n", + "..\\..\\autotest\\t065_test_gridintersect.py:12: in \n", + " from flopy.utils.gridintersect import GridIntersect\n", + "E ModuleNotFoundError: No module named 'flopy.utils.gridintersect'\n", + "------------------------------- Captured stdout -------------------------------\n", + "flopy is installed in C:\\Users\\dbrak\\Anaconda3\\lib\\site-packages\\flopy\n", + "\n", + "----------- coverage: platform win32, python 3.7.3-final-0 -----------\n", + "Name Stmts Miss Cover\n", + "---------------------------\n", + "\n", + "!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!\n", + "=========================== 1 error in 2.66 seconds ===========================\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Coverage.py warning: Module gridintersect was never imported. (module-not-imported)\n", + "Coverage.py warning: No data was collected. (no-data-collected)\n", + "C:\\Users\\dbrak\\Anaconda3\\lib\\site-packages\\pytest_cov\\plugin.py:229: PytestWarning: Failed to generate report: No data to report.\n", + "\n", + " self.cov_controller.finish()\n" + ] + } + ], + "source": [ + "!pytest --cov-report term --cov gridintersect ../../autotest/t065_test_gridintersect.py " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## [Timings](#top)\n", + "Comparing performance for the different methods in a large grid. Some helper functions are defined below" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "def ix_shapely_point(nrnc, npoints=100):\n", + " results = []\n", + " delc = np.ones(nrnc, dtype=np.float)\n", + " delr = np.ones(nrnc, dtype=np.float)\n", + " sgr = fgrid.StructuredGrid(delc, delr, top=None, botm=None)\n", + " ix = GridIntersect(sgr)\n", + " points = np.random.random((npoints, 2)) * nrnc\n", + " for p in [Point(x, y) for x, y in points]:\n", + " results.append(ix.intersect_point(p))\n", + " return np.concatenate(results, axis=0)\n", + "\n", + "\n", + "def ix_structured_point(nrnc, npoints=100):\n", + " delc = np.ones(nrnc, dtype=np.float)\n", + " delr = np.ones(nrnc, dtype=np.float)\n", + " sgr = fgrid.StructuredGrid(delc, delr, top=None, botm=None)\n", + " ix = GridIntersect(sgr, method=\"structured\")\n", + " points = np.random.random((npoints, 2)) * nrnc\n", + " mp = MultiPoint(points=[Point(x, y) for x, y in points])\n", + " return ix.intersect_point(mp)\n", + "\n", + "\n", + "def ix_shapely_linestring(nrnc, ls=None):\n", + " if ls is None:\n", + " ls = LineString([(0, 0), (nrnc/3, nrnc)])\n", + " delc = np.ones(nrnc, dtype=np.float)\n", + " delr = np.ones(nrnc, dtype=np.float)\n", + " sgr = fgrid.StructuredGrid(delc, delr, top=None, botm=None)\n", + " ix = GridIntersect(sgr)\n", + " return ix.intersect_linestring(ls)\n", + "\n", + "\n", + "def ix_structured_linestring(nrnc, ls=None):\n", + " if ls is None:\n", + " ls = LineString([(0, 0), (nrnc/3, nrnc)])\n", + " delc = np.ones(nrnc, dtype=np.float)\n", + " delr = np.ones(nrnc, dtype=np.float)\n", + " sgr = fgrid.StructuredGrid(delc, delr, top=None, botm=None)\n", + " ix = GridIntersect(sgr, method=\"structured\")\n", + " return ix.intersect_linestring(ls)\n", + "\n", + "\n", + "def ix_shapely_polygon(nrnc, p=Polygon([(10, 10), (540, 430), (730, 80), (250, 0)])):\n", + " delc = np.ones(nrnc, dtype=np.float)\n", + " delr = np.ones(nrnc, dtype=np.float)\n", + " sgr = fgrid.StructuredGrid(delc, delr, top=None, botm=None)\n", + " ix = GridIntersect(sgr)\n", + " return ix.intersect_polygon(p)\n", + "\n", + "\n", + "def ix_structured_polygon(nrnc, p=Polygon([(10, 10), (540, 430), (730, 80), (250, 0)])):\n", + " delc = np.ones(nrnc, dtype=np.float)\n", + " delr = np.ones(nrnc, dtype=np.float)\n", + " sgr = fgrid.StructuredGrid(delc, delr, top=None, botm=None)\n", + " ix = GridIntersect(sgr, method=\"structured\")\n", + " return ix.intersect_polygon(p)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Below are some results of `%timeit` runs of some intersections on a 1000 x 1000 structured grid. For obvious reasons not having to build the STR-tree saves a significant amount of time for large grids (~ 15 seconds on my laptop)." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "# nrnc = 1000 # no rows and columns\n", + "nrnc = 10 # save time when testing notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For point intersections, most of the time required by the shapely approach is needed to build the STR-tree (~15 s). Obviously, the pure numpy approach used in structured mode is unbeatable." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "20.7 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit -n 1 -r 1 ix_shapely_point(nrnc, npoints=100)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "219 µs ± 10.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + ] + } + ], + "source": [ + "%timeit ix_structured_point(nrnc, npoints=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For linestrings, following the linestring through the grid (in structured mode) reduces the amount of intersection calls by a significant amount. This is where the downside of the STR-tree query is obvious. The bounding box of the linestring covers about one third of the grid. The query only reduces the search-space by 2/3 leaving ~333k cells to try to intersect with. On top of the building of the STR-tree the intersection calls take another ~15 seconds.\n", + "\n", + "(Cutting the linestring into pieces would probably improve performance.)" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6.61 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit -n 1 -r 1 ix_shapely_linestring(nrnc)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.81 ms ± 99.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%timeit ix_structured_linestring(nrnc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For Polygons the difference between structured mode and shapely mode is less obvious. Building the STR-tree (~15s) and doing the intersect (~20s) takes a little bit longer than performing the intersection in structured mode. However, note that intersecting with a second similarly sized polygon in shapely mode will only require ~20s, whereas in structured mode the required time will remain ~30 seconds. \n", + "\n", + "For repeated intersections with Polygons, the shapely method might be preferred over the structured method." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.49 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit -n 1 -r 1 ix_shapely_polygon(nrnc)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.19 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], + "source": [ + "%timeit -n 1 -r 1 ix_structured_polygon(nrnc)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From f4c410b51a44f591dff931ceb560883231f26ae7 Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Fri, 5 Jul 2019 18:51:46 +0200 Subject: [PATCH 07/10] update travis to install shapely minor edit in gridintersect --- .travis.yml | 1 + .../flopy3_grid_intersection_demo.ipynb | 311 +++++++++--------- flopy/utils/gridintersect.py | 1 - 3 files changed, 156 insertions(+), 157 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19b149e8ea..38b5c1aa0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,6 +56,7 @@ install: else pip install -r requirements.travis.txt; fi - pip install https://github.com/modflowpy/pymake/zipball/master - pip install --upgrade jupyter +- pip install shapely[vectorize] - pip install nbconvert - pip install nose-timer - pip install coveralls diff --git a/examples/Notebooks/flopy3_grid_intersection_demo.ipynb b/examples/Notebooks/flopy3_grid_intersection_demo.ipynb index 4a5ba3743f..3a0e82189b 100644 --- a/examples/Notebooks/flopy3_grid_intersection_demo.ipynb +++ b/examples/Notebooks/flopy3_grid_intersection_demo.ipynb @@ -38,14 +38,13 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "flopy is installed in C:\\GitHub\\flopy_db\\flopy\n", "3.7.3 (default, Mar 27 2019, 17:13:21) [MSC v.1915 64 bit (AMD64)]\n", "numpy version: 1.16.2\n", "matplotlib version: 3.0.3\n", @@ -89,7 +88,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 54, "metadata": {}, "outputs": [], "source": [ @@ -133,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 55, "metadata": {}, "outputs": [], "source": [ @@ -143,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 56, "metadata": {}, "outputs": [], "source": [ @@ -155,16 +154,16 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 5, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" }, @@ -195,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 58, "metadata": {}, "outputs": [], "source": [ @@ -212,7 +211,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 59, "metadata": {}, "outputs": [], "source": [ @@ -228,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 60, "metadata": {}, "outputs": [], "source": [ @@ -237,14 +236,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "7.86 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "8.05 ms ± 435 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], @@ -268,56 +267,56 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 62, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "rec.array([((2, 3), (((30.0, 70.0), (35.0, 80.0), (40.0, 76.66666666666667), (40.0, 70.0), (30.0, 70.0)),), 66.66666667, ),\n", - " ((2, 4), (((40.0, 76.66666666666667), (50.0, 70.0), (40.0, 70.0), (40.0, 76.66666666666667)),), 33.33333333, ),\n", - " ((3, 2), (((25.0, 60.0), (30.0, 70.0), (30.0, 60.0), (25.0, 60.0)),), 25. , ),\n", - " ((3, 3), (((30.0, 70.0), (40.0, 70.0), (40.0, 60.0), (30.0, 60.0), (30.0, 70.0)),), 100. , ),\n", - " ((3, 4), (((40.0, 70.0), (50.0, 70.0), (50.0, 60.0), (40.0, 60.0), (40.0, 70.0)),), 100. , ),\n", - " ((3, 5), (((50.0, 70.0), (60.0, 63.333333333333336), (60.0, 60.0), (50.0, 60.0), (50.0, 70.0)),), 66.66666667, ),\n", - " ((3, 6), (((60.0, 63.333333333333336), (65.0, 60.0), (60.0, 60.0), (60.0, 63.333333333333336)),), 8.33333333, ),\n", - " ((4, 2), (((20.0, 50.0), (25.0, 60.0), (30.0, 60.0), (30.0, 50.0), (20.0, 50.0)),), 75. , ),\n", - " ((4, 3), (((30.0, 60.0), (40.0, 60.0), (40.0, 50.0), (30.0, 50.0), (30.0, 60.0)),), 100. , ),\n", - " ((4, 4), (((40.0, 60.0), (50.0, 60.0), (50.0, 50.0), (40.0, 50.0), (40.0, 60.0)),), 100. , ),\n", - " ((4, 5), (((50.0, 60.0), (60.0, 60.0), (60.0, 50.0), (50.0, 50.0), (50.0, 60.0)),), 100. , ),\n", - " ((4, 6), (((65.0, 60.0), (70.0, 56.666666666666664), (70.0, 50.0), (60.0, 50.0), (60.0, 60.0), (65.0, 60.0)),), 91.66666667, ),\n", - " ((4, 7), (((70.0, 56.666666666666664), (80.0, 50.0), (70.0, 50.0), (70.0, 56.666666666666664)),), 33.33333333, ),\n", - " ((5, 1), (((18.571428571428573, 40.0), (20.0, 50.0), (20.0, 40.0), (18.571428571428573, 40.0)),), 7.14285714, ),\n", - " ((5, 2), (((30.0, 45.0), (25.0, 45.0), (25.0, 40.0), (20.0, 40.0), (20.0, 50.0), (30.0, 50.0), (30.0, 45.0)),), 75. , ),\n", - " ((5, 3), (((40.0, 45.0), (30.0, 45.0), (30.0, 50.0), (40.0, 50.0), (40.0, 45.0)),), 50. , ),\n", - " ((5, 4), (((45.0, 40.0), (45.0, 45.0), (40.0, 45.0), (40.0, 50.0), (50.0, 50.0), (50.0, 40.0), (45.0, 40.0)),), 75. , ),\n", - " ((5, 5), (((50.0, 50.0), (60.0, 50.0), (60.0, 40.0), (50.0, 40.0), (50.0, 50.0)),), 100. , ),\n", - " ((5, 6), (((60.0, 50.0), (70.0, 50.0), (70.0, 40.0), (60.0, 40.0), (60.0, 50.0)),), 100. , ),\n", - " ((5, 7), (((80.0, 50.0), (80.0, 40.0), (70.0, 40.0), (70.0, 50.0), (80.0, 50.0)),), 100. , ),\n", - " ((6, 1), (((17.142857142857142, 30.0), (18.571428571428573, 40.0), (20.0, 40.0), (20.0, 30.0), (17.142857142857142, 30.0)),), 21.42857143, ),\n", - " ((6, 2), (((25.0, 40.0), (25.0, 30.0), (20.0, 30.0), (20.0, 40.0), (25.0, 40.0)),), 50. , ),\n", - " ((6, 4), (((45.0, 30.0), (45.0, 40.0), (50.0, 40.0), (50.0, 30.0), (45.0, 30.0)),), 50. , ),\n", - " ((6, 5), (((50.0, 40.0), (60.0, 40.0), (60.0, 30.0), (50.0, 30.0), (50.0, 40.0)),), 100. , ),\n", - " ((6, 6), (((70.0, 31.25), (68.57142857142857, 30.0), (60.0, 30.0), (60.0, 40.0), (70.0, 40.0), (70.0, 31.25)),), 99.10714286, ),\n", - " ((6, 7), (((80.0, 40.0), (70.0, 31.25), (70.0, 40.0), (80.0, 40.0)),), 43.75 , ),\n", - " ((7, 1), (((15.714285714285714, 20.0), (17.142857142857142, 30.0), (20.0, 30.0), (20.0, 20.0), (15.714285714285714, 20.0)),), 35.71428571, ),\n", - " ((7, 2), (((25.0, 30.0), (25.0, 25.0), (30.0, 25.0), (30.0, 20.0), (20.0, 20.0), (20.0, 30.0), (25.0, 30.0)),), 75. , ),\n", - " ((7, 3), (((30.0, 25.0), (40.0, 25.0), (40.0, 20.0), (30.0, 20.0), (30.0, 25.0)),), 50. , ),\n", - " ((7, 4), (((40.0, 25.0), (45.0, 25.0), (45.0, 30.0), (50.0, 30.0), (50.0, 20.0), (40.0, 20.0), (40.0, 25.0)),), 75. , ),\n", - " ((7, 5), (((60.0, 22.5), (57.142857142857146, 20.0), (50.0, 20.0), (50.0, 30.0), (60.0, 30.0), (60.0, 22.5)),), 96.42857143, ),\n", - " ((7, 6), (((68.57142857142857, 30.0), (60.0, 22.5), (60.0, 30.0), (68.57142857142857, 30.0)),), 32.14285714, ),\n", - " ((8, 1), (((15.0, 15.0), (15.714285714285714, 20.0), (20.0, 20.0), (20.0, 10.6), (15.0, 12.0), (15.0, 15.0)),), 41.71428571, ),\n", - " ((8, 2), (((22.142857142857142, 10.0), (20.0, 10.6), (20.0, 20.0), (30.0, 20.0), (30.0, 10.0), (22.142857142857142, 10.0)),), 99.35714286, ),\n", - " ((8, 3), (((30.0, 20.0), (40.0, 20.0), (40.0, 10.0), (30.0, 10.0), (30.0, 20.0)),), 100. , ),\n", - " ((8, 4), (((50.0, 13.75), (45.714285714285715, 10.0), (40.0, 10.0), (40.0, 20.0), (50.0, 20.0), (50.0, 13.75)),), 91.96428571, ),\n", - " ((8, 5), (((57.142857142857146, 20.0), (50.0, 13.75), (50.0, 20.0), (57.142857142857146, 20.0)),), 22.32142857, ),\n", - " ((9, 2), (((30.0, 7.8), (22.142857142857142, 10.0), (30.0, 10.0), (30.0, 7.8)),), 8.64285714, ),\n", - " ((9, 3), (((40.0, 5.0), (30.0, 7.8), (30.0, 10.0), (40.0, 10.0), (40.0, 5.0)),), 36. , ),\n", - " ((9, 4), (((45.714285714285715, 10.0), (40.0, 5.0), (40.0, 10.0), (45.714285714285715, 10.0)),), 14.28571429, )],\n", + "rec.array([((2, 3), (((30.0, 70.0), (35.0, 80.0), (40.0, 76.66666666666667), (40.0, 70.0), (30.0, 70.0)),), 66.66666667, ),\n", + " ((2, 4), (((40.0, 76.66666666666667), (50.0, 70.0), (40.0, 70.0), (40.0, 76.66666666666667)),), 33.33333333, ),\n", + " ((3, 2), (((25.0, 60.0), (30.0, 70.0), (30.0, 60.0), (25.0, 60.0)),), 25. , ),\n", + " ((3, 3), (((30.0, 70.0), (40.0, 70.0), (40.0, 60.0), (30.0, 60.0), (30.0, 70.0)),), 100. , ),\n", + " ((3, 4), (((40.0, 70.0), (50.0, 70.0), (50.0, 60.0), (40.0, 60.0), (40.0, 70.0)),), 100. , ),\n", + " ((3, 5), (((50.0, 70.0), (60.0, 63.333333333333336), (60.0, 60.0), (50.0, 60.0), (50.0, 70.0)),), 66.66666667, ),\n", + " ((3, 6), (((60.0, 63.333333333333336), (65.0, 60.0), (60.0, 60.0), (60.0, 63.333333333333336)),), 8.33333333, ),\n", + " ((4, 2), (((20.0, 50.0), (25.0, 60.0), (30.0, 60.0), (30.0, 50.0), (20.0, 50.0)),), 75. , ),\n", + " ((4, 3), (((30.0, 60.0), (40.0, 60.0), (40.0, 50.0), (30.0, 50.0), (30.0, 60.0)),), 100. , ),\n", + " ((4, 4), (((40.0, 60.0), (50.0, 60.0), (50.0, 50.0), (40.0, 50.0), (40.0, 60.0)),), 100. , ),\n", + " ((4, 5), (((50.0, 60.0), (60.0, 60.0), (60.0, 50.0), (50.0, 50.0), (50.0, 60.0)),), 100. , ),\n", + " ((4, 6), (((65.0, 60.0), (70.0, 56.666666666666664), (70.0, 50.0), (60.0, 50.0), (60.0, 60.0), (65.0, 60.0)),), 91.66666667, ),\n", + " ((4, 7), (((70.0, 56.666666666666664), (80.0, 50.0), (70.0, 50.0), (70.0, 56.666666666666664)),), 33.33333333, ),\n", + " ((5, 1), (((18.571428571428573, 40.0), (20.0, 50.0), (20.0, 40.0), (18.571428571428573, 40.0)),), 7.14285714, ),\n", + " ((5, 2), (((30.0, 45.0), (25.0, 45.0), (25.0, 40.0), (20.0, 40.0), (20.0, 50.0), (30.0, 50.0), (30.0, 45.0)),), 75. , ),\n", + " ((5, 3), (((40.0, 45.0), (30.0, 45.0), (30.0, 50.0), (40.0, 50.0), (40.0, 45.0)),), 50. , ),\n", + " ((5, 4), (((45.0, 40.0), (45.0, 45.0), (40.0, 45.0), (40.0, 50.0), (50.0, 50.0), (50.0, 40.0), (45.0, 40.0)),), 75. , ),\n", + " ((5, 5), (((50.0, 50.0), (60.0, 50.0), (60.0, 40.0), (50.0, 40.0), (50.0, 50.0)),), 100. , ),\n", + " ((5, 6), (((60.0, 50.0), (70.0, 50.0), (70.0, 40.0), (60.0, 40.0), (60.0, 50.0)),), 100. , ),\n", + " ((5, 7), (((80.0, 50.0), (80.0, 40.0), (70.0, 40.0), (70.0, 50.0), (80.0, 50.0)),), 100. , ),\n", + " ((6, 1), (((17.142857142857142, 30.0), (18.571428571428573, 40.0), (20.0, 40.0), (20.0, 30.0), (17.142857142857142, 30.0)),), 21.42857143, ),\n", + " ((6, 2), (((25.0, 40.0), (25.0, 30.0), (20.0, 30.0), (20.0, 40.0), (25.0, 40.0)),), 50. , ),\n", + " ((6, 4), (((45.0, 30.0), (45.0, 40.0), (50.0, 40.0), (50.0, 30.0), (45.0, 30.0)),), 50. , ),\n", + " ((6, 5), (((50.0, 40.0), (60.0, 40.0), (60.0, 30.0), (50.0, 30.0), (50.0, 40.0)),), 100. , ),\n", + " ((6, 6), (((70.0, 31.25), (68.57142857142857, 30.0), (60.0, 30.0), (60.0, 40.0), (70.0, 40.0), (70.0, 31.25)),), 99.10714286, ),\n", + " ((6, 7), (((80.0, 40.0), (70.0, 31.25), (70.0, 40.0), (80.0, 40.0)),), 43.75 , ),\n", + " ((7, 1), (((15.714285714285714, 20.0), (17.142857142857142, 30.0), (20.0, 30.0), (20.0, 20.0), (15.714285714285714, 20.0)),), 35.71428571, ),\n", + " ((7, 2), (((25.0, 30.0), (25.0, 25.0), (30.0, 25.0), (30.0, 20.0), (20.0, 20.0), (20.0, 30.0), (25.0, 30.0)),), 75. , ),\n", + " ((7, 3), (((30.0, 25.0), (40.0, 25.0), (40.0, 20.0), (30.0, 20.0), (30.0, 25.0)),), 50. , ),\n", + " ((7, 4), (((40.0, 25.0), (45.0, 25.0), (45.0, 30.0), (50.0, 30.0), (50.0, 20.0), (40.0, 20.0), (40.0, 25.0)),), 75. , ),\n", + " ((7, 5), (((60.0, 22.5), (57.142857142857146, 20.0), (50.0, 20.0), (50.0, 30.0), (60.0, 30.0), (60.0, 22.5)),), 96.42857143, ),\n", + " ((7, 6), (((68.57142857142857, 30.0), (60.0, 22.5), (60.0, 30.0), (68.57142857142857, 30.0)),), 32.14285714, ),\n", + " ((8, 1), (((15.0, 15.0), (15.714285714285714, 20.0), (20.0, 20.0), (20.0, 10.6), (15.0, 12.0), (15.0, 15.0)),), 41.71428571, ),\n", + " ((8, 2), (((22.142857142857142, 10.0), (20.0, 10.6), (20.0, 20.0), (30.0, 20.0), (30.0, 10.0), (22.142857142857142, 10.0)),), 99.35714286, ),\n", + " ((8, 3), (((30.0, 20.0), (40.0, 20.0), (40.0, 10.0), (30.0, 10.0), (30.0, 20.0)),), 100. , ),\n", + " ((8, 4), (((50.0, 13.75), (45.714285714285715, 10.0), (40.0, 10.0), (40.0, 20.0), (50.0, 20.0), (50.0, 13.75)),), 91.96428571, ),\n", + " ((8, 5), (((57.142857142857146, 20.0), (50.0, 13.75), (50.0, 20.0), (57.142857142857146, 20.0)),), 22.32142857, ),\n", + " ((9, 2), (((30.0, 7.8), (22.142857142857142, 10.0), (30.0, 10.0), (30.0, 7.8)),), 8.64285714, ),\n", + " ((9, 3), (((40.0, 5.0), (30.0, 7.8), (30.0, 10.0), (40.0, 10.0), (40.0, 5.0)),), 36. , ),\n", + " ((9, 4), (((45.714285714285715, 10.0), (40.0, 5.0), (40.0, 10.0), (45.714285714285715, 10.0)),), 14.28571429, )],\n", " dtype=[('cellids', 'O'), ('vertices', 'O'), ('areas', '" + "" ] }, - "execution_count": 11, + "execution_count": 63, "metadata": {}, "output_type": "execute_result" }, @@ -383,56 +382,56 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "rec.array([((2, 3), (((30.0, 70.0), (35.0, 80.0), (40.0, 76.66666666666667), (40.0, 70.0), (30.0, 70.0)),), 66.66666667, ),\n", - " ((2, 4), (((40.0, 76.66666666666667), (50.0, 70.0), (40.0, 70.0), (40.0, 76.66666666666667)),), 33.33333333, ),\n", - " ((3, 2), (((25.0, 60.0), (30.0, 70.0), (30.0, 60.0), (25.0, 60.0)),), 25. , ),\n", - " ((3, 3), (((30.0, 70.0), (40.0, 70.0), (40.0, 60.0), (30.0, 60.0), (30.0, 70.0)),), 100. , ),\n", - " ((3, 4), (((40.0, 70.0), (50.0, 70.0), (50.0, 60.0), (40.0, 60.0), (40.0, 70.0)),), 100. , ),\n", - " ((3, 5), (((50.0, 70.0), (60.0, 63.333333333333336), (60.0, 60.0), (50.0, 60.0), (50.0, 70.0)),), 66.66666667, ),\n", - " ((3, 6), (((60.0, 63.333333333333336), (65.0, 60.0), (60.0, 60.0), (60.0, 63.333333333333336)),), 8.33333333, ),\n", - " ((4, 2), (((20.0, 50.0), (25.0, 60.0), (30.0, 60.0), (30.0, 50.0), (20.0, 50.0)),), 75. , ),\n", - " ((4, 3), (((30.0, 60.0), (40.0, 60.0), (40.0, 50.0), (30.0, 50.0), (30.0, 60.0)),), 100. , ),\n", - " ((4, 4), (((40.0, 60.0), (50.0, 60.0), (50.0, 50.0), (40.0, 50.0), (40.0, 60.0)),), 100. , ),\n", - " ((4, 5), (((50.0, 60.0), (60.0, 60.0), (60.0, 50.0), (50.0, 50.0), (50.0, 60.0)),), 100. , ),\n", - " ((4, 6), (((65.0, 60.0), (70.0, 56.666666666666664), (70.0, 50.0), (60.0, 50.0), (60.0, 60.0), (65.0, 60.0)),), 91.66666667, ),\n", - " ((4, 7), (((70.0, 56.666666666666664), (80.0, 50.0), (70.0, 50.0), (70.0, 56.666666666666664)),), 33.33333333, ),\n", - " ((5, 1), (((18.571428571428573, 40.0), (20.0, 50.0), (20.0, 40.0), (18.571428571428573, 40.0)),), 7.14285714, ),\n", - " ((5, 2), (((30.0, 45.0), (25.0, 45.0), (25.0, 40.0), (20.0, 40.0), (20.0, 50.0), (30.0, 50.0), (30.0, 45.0)),), 75. , ),\n", - " ((5, 3), (((40.0, 45.0), (30.0, 45.0), (30.0, 50.0), (40.0, 50.0), (40.0, 45.0)),), 50. , ),\n", - " ((5, 4), (((45.0, 40.0), (45.0, 45.0), (40.0, 45.0), (40.0, 50.0), (50.0, 50.0), (50.0, 40.0), (45.0, 40.0)),), 75. , ),\n", - " ((5, 5), (((50.0, 50.0), (60.0, 50.0), (60.0, 40.0), (50.0, 40.0), (50.0, 50.0)),), 100. , ),\n", - " ((5, 6), (((60.0, 50.0), (70.0, 50.0), (70.0, 40.0), (60.0, 40.0), (60.0, 50.0)),), 100. , ),\n", - " ((5, 7), (((80.0, 50.0), (80.0, 40.0), (70.0, 40.0), (70.0, 50.0), (80.0, 50.0)),), 100. , ),\n", - " ((6, 1), (((17.142857142857142, 30.0), (18.571428571428573, 40.0), (20.0, 40.0), (20.0, 30.0), (17.142857142857142, 30.0)),), 21.42857143, ),\n", - " ((6, 2), (((25.0, 40.0), (25.0, 30.0), (20.0, 30.0), (20.0, 40.0), (25.0, 40.0)),), 50. , ),\n", - " ((6, 4), (((45.0, 30.0), (45.0, 40.0), (50.0, 40.0), (50.0, 30.0), (45.0, 30.0)),), 50. , ),\n", - " ((6, 5), (((50.0, 40.0), (60.0, 40.0), (60.0, 30.0), (50.0, 30.0), (50.0, 40.0)),), 100. , ),\n", - " ((6, 6), (((70.0, 31.25), (68.57142857142857, 30.0), (60.0, 30.0), (60.0, 40.0), (70.0, 40.0), (70.0, 31.25)),), 99.10714286, ),\n", - " ((6, 7), (((80.0, 40.0), (70.0, 31.25), (70.0, 40.0), (80.0, 40.0)),), 43.75 , ),\n", - " ((7, 1), (((15.714285714285714, 20.0), (17.142857142857142, 30.0), (20.0, 30.0), (20.0, 20.0), (15.714285714285714, 20.0)),), 35.71428571, ),\n", - " ((7, 2), (((25.0, 30.0), (25.0, 25.0), (30.0, 25.0), (30.0, 20.0), (20.0, 20.0), (20.0, 30.0), (25.0, 30.0)),), 75. , ),\n", - " ((7, 3), (((30.0, 25.0), (40.0, 25.0), (40.0, 20.0), (30.0, 20.0), (30.0, 25.0)),), 50. , ),\n", - " ((7, 4), (((40.0, 25.0), (45.0, 25.0), (45.0, 30.0), (50.0, 30.0), (50.0, 20.0), (40.0, 20.0), (40.0, 25.0)),), 75. , ),\n", - " ((7, 5), (((60.0, 22.5), (57.142857142857146, 20.0), (50.0, 20.0), (50.0, 30.0), (60.0, 30.0), (60.0, 22.5)),), 96.42857143, ),\n", - " ((7, 6), (((68.57142857142857, 30.0), (60.0, 22.5), (60.0, 30.0), (68.57142857142857, 30.0)),), 32.14285714, ),\n", - " ((8, 1), (((15.0, 15.0), (15.714285714285714, 20.0), (20.0, 20.0), (20.0, 10.6), (15.0, 12.0), (15.0, 15.0)),), 41.71428571, ),\n", - " ((8, 2), (((22.142857142857142, 10.0), (20.0, 10.6), (20.0, 20.0), (30.0, 20.0), (30.0, 10.0), (22.142857142857142, 10.0)),), 99.35714286, ),\n", - " ((8, 3), (((30.0, 20.0), (40.0, 20.0), (40.0, 10.0), (30.0, 10.0), (30.0, 20.0)),), 100. , ),\n", - " ((8, 4), (((50.0, 13.75), (45.714285714285715, 10.0), (40.0, 10.0), (40.0, 20.0), (50.0, 20.0), (50.0, 13.75)),), 91.96428571, ),\n", - " ((8, 5), (((57.142857142857146, 20.0), (50.0, 13.75), (50.0, 20.0), (57.142857142857146, 20.0)),), 22.32142857, ),\n", - " ((9, 2), (((30.0, 7.8), (22.142857142857142, 10.0), (30.0, 10.0), (30.0, 7.8)),), 8.64285714, ),\n", - " ((9, 3), (((40.0, 5.0), (30.0, 7.8), (30.0, 10.0), (40.0, 10.0), (40.0, 5.0)),), 36. , ),\n", - " ((9, 4), (((45.714285714285715, 10.0), (40.0, 5.0), (40.0, 10.0), (45.714285714285715, 10.0)),), 14.28571429, )],\n", + "rec.array([((2, 3), (((30.0, 70.0), (35.0, 80.0), (40.0, 76.66666666666667), (40.0, 70.0), (30.0, 70.0)),), 66.66666667, ),\n", + " ((2, 4), (((40.0, 76.66666666666667), (50.0, 70.0), (40.0, 70.0), (40.0, 76.66666666666667)),), 33.33333333, ),\n", + " ((3, 2), (((25.0, 60.0), (30.0, 70.0), (30.0, 60.0), (25.0, 60.0)),), 25. , ),\n", + " ((3, 3), (((30.0, 70.0), (40.0, 70.0), (40.0, 60.0), (30.0, 60.0), (30.0, 70.0)),), 100. , ),\n", + " ((3, 4), (((40.0, 70.0), (50.0, 70.0), (50.0, 60.0), (40.0, 60.0), (40.0, 70.0)),), 100. , ),\n", + " ((3, 5), (((50.0, 70.0), (60.0, 63.333333333333336), (60.0, 60.0), (50.0, 60.0), (50.0, 70.0)),), 66.66666667, ),\n", + " ((3, 6), (((60.0, 63.333333333333336), (65.0, 60.0), (60.0, 60.0), (60.0, 63.333333333333336)),), 8.33333333, ),\n", + " ((4, 2), (((20.0, 50.0), (25.0, 60.0), (30.0, 60.0), (30.0, 50.0), (20.0, 50.0)),), 75. , ),\n", + " ((4, 3), (((30.0, 60.0), (40.0, 60.0), (40.0, 50.0), (30.0, 50.0), (30.0, 60.0)),), 100. , ),\n", + " ((4, 4), (((40.0, 60.0), (50.0, 60.0), (50.0, 50.0), (40.0, 50.0), (40.0, 60.0)),), 100. , ),\n", + " ((4, 5), (((50.0, 60.0), (60.0, 60.0), (60.0, 50.0), (50.0, 50.0), (50.0, 60.0)),), 100. , ),\n", + " ((4, 6), (((65.0, 60.0), (70.0, 56.666666666666664), (70.0, 50.0), (60.0, 50.0), (60.0, 60.0), (65.0, 60.0)),), 91.66666667, ),\n", + " ((4, 7), (((70.0, 56.666666666666664), (80.0, 50.0), (70.0, 50.0), (70.0, 56.666666666666664)),), 33.33333333, ),\n", + " ((5, 1), (((18.571428571428573, 40.0), (20.0, 50.0), (20.0, 40.0), (18.571428571428573, 40.0)),), 7.14285714, ),\n", + " ((5, 2), (((30.0, 45.0), (25.0, 45.0), (25.0, 40.0), (20.0, 40.0), (20.0, 50.0), (30.0, 50.0), (30.0, 45.0)),), 75. , ),\n", + " ((5, 3), (((40.0, 45.0), (30.0, 45.0), (30.0, 50.0), (40.0, 50.0), (40.0, 45.0)),), 50. , ),\n", + " ((5, 4), (((45.0, 40.0), (45.0, 45.0), (40.0, 45.0), (40.0, 50.0), (50.0, 50.0), (50.0, 40.0), (45.0, 40.0)),), 75. , ),\n", + " ((5, 5), (((50.0, 50.0), (60.0, 50.0), (60.0, 40.0), (50.0, 40.0), (50.0, 50.0)),), 100. , ),\n", + " ((5, 6), (((60.0, 50.0), (70.0, 50.0), (70.0, 40.0), (60.0, 40.0), (60.0, 50.0)),), 100. , ),\n", + " ((5, 7), (((80.0, 50.0), (80.0, 40.0), (70.0, 40.0), (70.0, 50.0), (80.0, 50.0)),), 100. , ),\n", + " ((6, 1), (((17.142857142857142, 30.0), (18.571428571428573, 40.0), (20.0, 40.0), (20.0, 30.0), (17.142857142857142, 30.0)),), 21.42857143, ),\n", + " ((6, 2), (((25.0, 40.0), (25.0, 30.0), (20.0, 30.0), (20.0, 40.0), (25.0, 40.0)),), 50. , ),\n", + " ((6, 4), (((45.0, 30.0), (45.0, 40.0), (50.0, 40.0), (50.0, 30.0), (45.0, 30.0)),), 50. , ),\n", + " ((6, 5), (((50.0, 40.0), (60.0, 40.0), (60.0, 30.0), (50.0, 30.0), (50.0, 40.0)),), 100. , ),\n", + " ((6, 6), (((70.0, 31.25), (68.57142857142857, 30.0), (60.0, 30.0), (60.0, 40.0), (70.0, 40.0), (70.0, 31.25)),), 99.10714286, ),\n", + " ((6, 7), (((80.0, 40.0), (70.0, 31.25), (70.0, 40.0), (80.0, 40.0)),), 43.75 , ),\n", + " ((7, 1), (((15.714285714285714, 20.0), (17.142857142857142, 30.0), (20.0, 30.0), (20.0, 20.0), (15.714285714285714, 20.0)),), 35.71428571, ),\n", + " ((7, 2), (((25.0, 30.0), (25.0, 25.0), (30.0, 25.0), (30.0, 20.0), (20.0, 20.0), (20.0, 30.0), (25.0, 30.0)),), 75. , ),\n", + " ((7, 3), (((30.0, 25.0), (40.0, 25.0), (40.0, 20.0), (30.0, 20.0), (30.0, 25.0)),), 50. , ),\n", + " ((7, 4), (((40.0, 25.0), (45.0, 25.0), (45.0, 30.0), (50.0, 30.0), (50.0, 20.0), (40.0, 20.0), (40.0, 25.0)),), 75. , ),\n", + " ((7, 5), (((60.0, 22.5), (57.142857142857146, 20.0), (50.0, 20.0), (50.0, 30.0), (60.0, 30.0), (60.0, 22.5)),), 96.42857143, ),\n", + " ((7, 6), (((68.57142857142857, 30.0), (60.0, 22.5), (60.0, 30.0), (68.57142857142857, 30.0)),), 32.14285714, ),\n", + " ((8, 1), (((15.0, 15.0), (15.714285714285714, 20.0), (20.0, 20.0), (20.0, 10.6), (15.0, 12.0), (15.0, 15.0)),), 41.71428571, ),\n", + " ((8, 2), (((22.142857142857142, 10.0), (20.0, 10.6), (20.0, 20.0), (30.0, 20.0), (30.0, 10.0), (22.142857142857142, 10.0)),), 99.35714286, ),\n", + " ((8, 3), (((30.0, 20.0), (40.0, 20.0), (40.0, 10.0), (30.0, 10.0), (30.0, 20.0)),), 100. , ),\n", + " ((8, 4), (((50.0, 13.75), (45.714285714285715, 10.0), (40.0, 10.0), (40.0, 20.0), (50.0, 20.0), (50.0, 13.75)),), 91.96428571, ),\n", + " ((8, 5), (((57.142857142857146, 20.0), (50.0, 13.75), (50.0, 20.0), (57.142857142857146, 20.0)),), 22.32142857, ),\n", + " ((9, 2), (((30.0, 7.8), (22.142857142857142, 10.0), (30.0, 10.0), (30.0, 7.8)),), 8.64285714, ),\n", + " ((9, 3), (((40.0, 5.0), (30.0, 7.8), (30.0, 10.0), (40.0, 10.0), (40.0, 5.0)),), 36. , ),\n", + " ((9, 4), (((45.714285714285715, 10.0), (40.0, 5.0), (40.0, 10.0), (45.714285714285715, 10.0)),), 14.28571429, )],\n", " dtype=[('cellids', 'O'), ('vertices', 'O'), ('areas', '" + "" ] }, - "execution_count": 16, + "execution_count": 68, "metadata": {}, "output_type": "execute_result" }, @@ -537,37 +536,37 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 69, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "rec.array([((9, 1), list([[(20.0, 4.888888888888889), (10.0, 2.4444444444444446)]]), 10.29443095, list([])),\n", - " ((0, 7), list([[(80.0, 92.3076923076923), (77.27272727272728, 90.0)]]), 3.57259854, list([])),\n", - " ((1, 6), list([[(70.0, 83.84615384615384), (65.45454545454545, 80.0)]]), 5.9543309 , list([])),\n", - " ((9, 4), list([[(40.90909090909091, 10.0), (40.0, 9.777777777777779)]]), 0.93585736, list([])),\n", - " ((2, 5), list([[(60.0, 75.38461538461539), (53.63636363636364, 70.0)]]), 8.33606326, list([])),\n", - " ((8, 5), list([[(60.0, 14.666666666666666), (50.0, 12.222222222222221)]]), 10.29443095, list([])),\n", - " ((9, 0), list([[(10.0, 2.4444444444444446), (0.0, 0.0)]]), 10.29443095, list([])),\n", - " ((4, 4), list([[(41.81818181818182, 60.0), (40.0, 58.46153846153846)]]), 2.38173236, list([])),\n", - " ((3, 4), list([[(50.0, 66.92307692307692), (41.81818181818182, 60.0)]]), 10.71779561, list([])),\n", - " ((8, 6), list([[(70.0, 17.11111111111111), (60.0, 14.666666666666666)]]), 10.29443095, list([])),\n", - " ((2, 6), list([[(65.45454545454545, 80.0), (60.0, 75.38461538461539)]]), 7.14519708, list([])),\n", - " ((9, 3), list([[(40.0, 9.777777777777779), (30.0, 7.333333333333334)]]), 10.29443095, list([])),\n", - " ((8, 7), list([[(80.0, 19.555555555555557), (70.0, 17.11111111111111)]]), 10.29443095, list([])),\n", - " ((0, 8), list([[(89.0909090909091, 100.0), (80.0, 92.3076923076923)]]), 11.90866179, list([])),\n", - " ((3, 5), list([[(53.63636363636364, 70.0), (50.0, 66.92307692307692)]]), 4.76346472, list([])),\n", - " ((9, 2), list([[(30.0, 7.333333333333334), (20.0, 4.888888888888889)]]), 10.29443095, list([])),\n", - " ((8, 8), list([[(81.81818181818181, 20.0), (80.0, 19.555555555555557)]]), 1.87171472, list([])),\n", - " ((4, 3), list([[(40.0, 58.46153846153846), (30.0, 50.0)]]), 13.09952797, list([])),\n", - " ((1, 7), list([[(77.27272727272728, 90.0), (70.0, 83.84615384615384)]]), 9.52692944, list([])),\n", - " ((7, 8), list([[(90.0, 22.0), (81.81818181818181, 20.0)]]), 8.42271623, list([])),\n", - " ((8, 4), list([[(50.0, 12.222222222222221), (40.90909090909091, 10.0)]]), 9.35857359, list([]))],\n", + "rec.array([((9, 1), list([[(20.0, 4.888888888888889), (10.0, 2.4444444444444446)]]), 10.29443095, list([])),\n", + " ((0, 7), list([[(80.0, 92.3076923076923), (77.27272727272728, 90.0)]]), 3.57259854, list([])),\n", + " ((1, 6), list([[(70.0, 83.84615384615384), (65.45454545454545, 80.0)]]), 5.9543309 , list([])),\n", + " ((9, 4), list([[(40.90909090909091, 10.0), (40.0, 9.777777777777779)]]), 0.93585736, list([])),\n", + " ((2, 5), list([[(60.0, 75.38461538461539), (53.63636363636364, 70.0)]]), 8.33606326, list([])),\n", + " ((8, 5), list([[(60.0, 14.666666666666666), (50.0, 12.222222222222221)]]), 10.29443095, list([])),\n", + " ((9, 0), list([[(10.0, 2.4444444444444446), (0.0, 0.0)]]), 10.29443095, list([])),\n", + " ((4, 4), list([[(41.81818181818182, 60.0), (40.0, 58.46153846153846)]]), 2.38173236, list([])),\n", + " ((3, 4), list([[(50.0, 66.92307692307692), (41.81818181818182, 60.0)]]), 10.71779561, list([])),\n", + " ((8, 6), list([[(70.0, 17.11111111111111), (60.0, 14.666666666666666)]]), 10.29443095, list([])),\n", + " ((2, 6), list([[(65.45454545454545, 80.0), (60.0, 75.38461538461539)]]), 7.14519708, list([])),\n", + " ((9, 3), list([[(40.0, 9.777777777777779), (30.0, 7.333333333333334)]]), 10.29443095, list([])),\n", + " ((8, 7), list([[(80.0, 19.555555555555557), (70.0, 17.11111111111111)]]), 10.29443095, list([])),\n", + " ((0, 8), list([[(89.0909090909091, 100.0), (80.0, 92.3076923076923)]]), 11.90866179, list([])),\n", + " ((3, 5), list([[(53.63636363636364, 70.0), (50.0, 66.92307692307692)]]), 4.76346472, list([])),\n", + " ((9, 2), list([[(30.0, 7.333333333333334), (20.0, 4.888888888888889)]]), 10.29443095, list([])),\n", + " ((8, 8), list([[(81.81818181818181, 20.0), (80.0, 19.555555555555557)]]), 1.87171472, list([])),\n", + " ((4, 3), list([[(40.0, 58.46153846153846), (30.0, 50.0)]]), 13.09952797, list([])),\n", + " ((1, 7), list([[(77.27272727272728, 90.0), (70.0, 83.84615384615384)]]), 9.52692944, list([])),\n", + " ((7, 8), list([[(90.0, 22.0), (81.81818181818181, 20.0)]]), 8.42271623, list([])),\n", + " ((8, 4), list([[(50.0, 12.222222222222221), (40.90909090909091, 10.0)]]), 9.35857359, list([]))],\n", " dtype=[('cellids', 'O'), ('vertices', 'O'), ('lengths', '" + "" ] }, - "execution_count": 20, + "execution_count": 72, "metadata": {}, "output_type": "execute_result" }, @@ -654,19 +653,19 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 73, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "rec.array([((9, 4), ),\n", - " ((5, 4), ),\n", - " ((8, 0), )],\n", + "rec.array([((9, 4), ),\n", + " ((5, 4), ),\n", + " ((8, 0), )],\n", " dtype=[('cellids', 'O'), ('ixshapes', 'O')])" ] }, - "execution_count": 21, + "execution_count": 73, "metadata": {}, "output_type": "execute_result" } @@ -820,7 +819,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 74, "metadata": {}, "outputs": [ { @@ -851,7 +850,7 @@ "---------------------------\n", "\n", "!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!\n", - "=========================== 1 error in 2.66 seconds ===========================\n" + "=========================== 1 error in 3.19 seconds ===========================\n" ] }, { @@ -880,7 +879,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 75, "metadata": {}, "outputs": [], "source": [ @@ -951,7 +950,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 76, "metadata": {}, "outputs": [], "source": [ @@ -968,14 +967,14 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 77, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "20.7 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "16.5 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], @@ -985,14 +984,14 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 78, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "219 µs ± 10.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + "234 µs ± 15.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" ] } ], @@ -1011,14 +1010,14 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 79, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "6.61 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "5.63 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], @@ -1028,14 +1027,14 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 80, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2.81 ms ± 99.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "2.93 ms ± 209 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], @@ -1054,14 +1053,14 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 81, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "4.49 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "4.62 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], @@ -1071,14 +1070,14 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 82, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "3.19 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + "3.94 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" ] } ], diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py index 2c009c2adf..e3ab6d5999 100644 --- a/flopy/utils/gridintersect.py +++ b/flopy/utils/gridintersect.py @@ -293,7 +293,6 @@ def _intersect_linestring_shapely(self, shp, keepzerolengths=False, # loop over collection for geom in intersect.geoms: verts = geom.__geo_interface__["coordinates"] - # if not keep_all_ix: if verts in vertices: continue vertices.append(verts) From 7f493088ecc4dfc726b283599dbadb1910e336e0 Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Sat, 6 Jul 2019 13:28:07 +0200 Subject: [PATCH 08/10] fix py27 error --- flopy/utils/gridintersect.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py index e3ab6d5999..55661ef75d 100644 --- a/flopy/utils/gridintersect.py +++ b/flopy/utils/gridintersect.py @@ -2,8 +2,7 @@ import numpy as np from .geometry import transform try: - from shapely.geometry import (LineString, MultiLineString, MultiPoint, - Point, Polygon, box) + from shapely.geometry import (LineString, MultiLineString, MultiPoint, Point, Polygon, box) from shapely.strtree import STRtree from shapely.affinity import translate, rotate except ModuleNotFoundError: @@ -481,6 +480,7 @@ def _intersect_point_structured(self, shp): tempnodes.append(node) tempshapes.append(ixs) else: + #TODO: not sure if this is correct tempshapes[-1] = MultiPoint([tempshapes[-1], ixs]) ixshapes = tempshapes @@ -695,7 +695,7 @@ def _get_nodes_intersecting_linestring(self, linestring): else: x = intersect.xy[0] y = intersect.xy[1] - verts = [(i[0], i[1]) for i in zip(x, y)] + verts = [(ixy[0], ixy[1]) for ixy in zip(x, y)] vertices.append(verts) nodelist.append((i, j)) @@ -775,7 +775,7 @@ def _check_adjacent_cells_intersecting_line(self, linestring, i_j, else: x = intersect.xy[0] y = intersect.xy[1] - verts.append([(i[0], i[1]) for i in zip(*intersect.xy)]) + verts.append([(ixy[0], ixy[1]) for ixy in zip(*intersect.xy)]) node.append((ii, jj)) # check to right @@ -802,7 +802,7 @@ def _check_adjacent_cells_intersecting_line(self, linestring, i_j, else: x = intersect.xy[0] y = intersect.xy[1] - verts.append([(i[0], i[1]) for i in zip(*intersect.xy)]) + verts.append([(ixy[0], ixy[1]) for ixy in zip(*intersect.xy)]) node.append((ii, jj)) # check to back @@ -829,7 +829,7 @@ def _check_adjacent_cells_intersecting_line(self, linestring, i_j, else: x = intersect.xy[0] y = intersect.xy[1] - verts.append([(i[0], i[1]) for i in zip(*intersect.xy)]) + verts.append([(ixy[0], ixy[1]) for ixy in zip(*intersect.xy)]) node.append((ii, jj)) # check to front @@ -856,7 +856,7 @@ def _check_adjacent_cells_intersecting_line(self, linestring, i_j, else: x = intersect.xy[0] y = intersect.xy[1] - verts.append([(i[0], i[1]) for i in zip(x, y)]) + verts.append([(ixy[0], ixy[1]) for ixy in zip(x, y)]) node.append((ii, jj)) return node, length, verts, ixshape From 6ab35ceb3b21e86d8f251523c30727508f021a28 Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Sat, 6 Jul 2019 13:36:04 +0200 Subject: [PATCH 09/10] make codacy happy again --- flopy/utils/gridintersect.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py index 55661ef75d..dbe034e409 100644 --- a/flopy/utils/gridintersect.py +++ b/flopy/utils/gridintersect.py @@ -2,7 +2,7 @@ import numpy as np from .geometry import transform try: - from shapely.geometry import (LineString, MultiLineString, MultiPoint, Point, Polygon, box) + from shapely.geometry import (LineString, MultiPoint, Point, Polygon, box) from shapely.strtree import STRtree from shapely.affinity import translate, rotate except ModuleNotFoundError: @@ -480,7 +480,7 @@ def _intersect_point_structured(self, shp): tempnodes.append(node) tempshapes.append(ixs) else: - #TODO: not sure if this is correct + # TODO: not sure if this is correct tempshapes[-1] = MultiPoint([tempshapes[-1], ixs]) ixshapes = tempshapes @@ -775,7 +775,8 @@ def _check_adjacent_cells_intersecting_line(self, linestring, i_j, else: x = intersect.xy[0] y = intersect.xy[1] - verts.append([(ixy[0], ixy[1]) for ixy in zip(*intersect.xy)]) + verts.append([(ixy[0], ixy[1]) + for ixy in zip(*intersect.xy)]) node.append((ii, jj)) # check to right @@ -802,7 +803,8 @@ def _check_adjacent_cells_intersecting_line(self, linestring, i_j, else: x = intersect.xy[0] y = intersect.xy[1] - verts.append([(ixy[0], ixy[1]) for ixy in zip(*intersect.xy)]) + verts.append([(ixy[0], ixy[1]) + for ixy in zip(*intersect.xy)]) node.append((ii, jj)) # check to back @@ -829,7 +831,8 @@ def _check_adjacent_cells_intersecting_line(self, linestring, i_j, else: x = intersect.xy[0] y = intersect.xy[1] - verts.append([(ixy[0], ixy[1]) for ixy in zip(*intersect.xy)]) + verts.append([(ixy[0], ixy[1]) for ixy in + zip(*intersect.xy)]) node.append((ii, jj)) # check to front From 4cd356e36009f226234ec68a16461cec5cf31fc7 Mon Sep 17 00:00:00 2001 From: dbrakenhoff Date: Sat, 6 Jul 2019 13:41:25 +0200 Subject: [PATCH 10/10] last codacy fix --- flopy/utils/gridintersect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py index dbe034e409..75c65205fc 100644 --- a/flopy/utils/gridintersect.py +++ b/flopy/utils/gridintersect.py @@ -2,7 +2,7 @@ import numpy as np from .geometry import transform try: - from shapely.geometry import (LineString, MultiPoint, Point, Polygon, box) + from shapely.geometry import MultiPoint, Point, Polygon, box from shapely.strtree import STRtree from shapely.affinity import translate, rotate except ModuleNotFoundError: