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/autotest/t065_test_gridintersect.py b/autotest/t065_test_gridintersect.py new file mode 100644 index 0000000000..bc4c5af371 --- /dev/null +++ b/autotest/t065_test_gridintersect.py @@ -0,0 +1,1029 @@ +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 +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 diff --git a/examples/Notebooks/flopy3_grid_intersection_demo.ipynb b/examples/Notebooks/flopy3_grid_intersection_demo.ipynb new file mode 100644 index 0000000000..3a0e82189b --- /dev/null +++ b/examples/Notebooks/flopy3_grid_intersection_demo.ipynb @@ -0,0 +1,1117 @@ +{ + "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": 53, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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": 54, + "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": 55, + "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": 56, + "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": 57, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 57, + "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": 58, + "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": 59, + "metadata": {}, + "outputs": [], + "source": [ + "ix = GridIntersect(sgr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Do the intersect operation for a polygon" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [], + "source": [ + "result = ix.intersect_polygon(p)" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8.05 ms ± 435 µ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": 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", + " dtype=[('cellids', 'O'), ('vertices', 'O'), ('areas', '" + ] + }, + "execution_count": 63, + "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": 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", + " dtype=[('cellids', 'O'), ('vertices', 'O'), ('areas', '[Polyline with regular grid](#top)\n", + "MultiLineString to intersect with:" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "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": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.94 ms ± 350 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%timeit ix.intersect_linestring(mls)" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [], + "source": [ + "result = ix.intersect_linestring(mls)" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 68, + "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": 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", + " dtype=[('cellids', 'O'), ('vertices', 'O'), ('lengths', '\n", + "\n", + "MultiPoint to intersect with" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "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": 71, + "metadata": {}, + "outputs": [], + "source": [ + "result = ix.intersect_point(mp)" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 72, + "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": 73, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([((9, 4), ),\n", + " ((5, 4), ),\n", + " ((8, 0), )],\n", + " dtype=[('cellids', 'O'), ('ixshapes', 'O')])" + ] + }, + "execution_count": 73, + "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": 74, + "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 3.19 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": 75, + "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": 76, + "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": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "16.5 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": 78, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "234 µs ± 15.2 µ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": 79, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.63 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": 80, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.93 ms ± 209 µ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": 81, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.62 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": 82, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.94 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 +} diff --git a/flopy/utils/gridintersect.py b/flopy/utils/gridintersect.py new file mode 100644 index 0000000000..75c65205fc --- /dev/null +++ b/flopy/utils/gridintersect.py @@ -0,0 +1,1280 @@ +import matplotlib.pyplot as plt +import numpy as np +from .geometry import transform +try: + from shapely.geometry import MultiPoint, 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 + """ + 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 + + @staticmethod + def _sort_strtree_result(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 keep zero lengths + if not keepzerolengths: + if intersect.length == 0.0: + continue + + # loop over collection + for geom in intersect.geoms: + verts = geom.__geo_interface__["coordinates"] + 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: + # TODO: not sure if this is correct + 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 enumerate(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 = [(ixy[0], ixy[1]) for ixy 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([(ixy[0], ixy[1]) + for ixy 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([(ixy[0], ixy[1]) + for ixy 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([(ixy[0], ixy[1]) for ixy 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([(ixy[0], ixy[1]) for ixy 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 + + @staticmethod + def plot_polygon(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 + + @staticmethod + def plot_linestring(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 + + @staticmethod + def plot_point(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. + + Parameters + ---------- + arr : A one dimensional array (such as Xe) that contains + coordinates for the left cell edge. + + x : float + 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). + + 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: + 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. + + 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 + + @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. + + 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 + + @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). + + 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: + 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)