-
Notifications
You must be signed in to change notification settings - Fork 318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add point, polyline, and polygon intersection capabilities #536
Comments
A notebook provided by @dbrakenhoff with some intersection ideas (@rubencalje) Intersecting Polygons and Polylines with model grids using Shapelyimport numpy as np
import shapely.geometry as geom
from shapely.geometry import Polygon, LineString, MultiLineString, MultiPolygon
from shapely.strtree import STRtree
from descartes import PolygonPatch
import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection General intersect function based on list of Shapely geometries that represent a grid, a Shapely Polygon or Polyline to intersect with and a declaration of shptype (could probably also be inferred from shp). def intersect(mglist, shp, shptype="POLYGON"):
intersect_dict = {}
s = STRtree(mglist)
result = s.query(shp)
isectshp = []
cellids = []
vertices = []
areas = []
lengths = []
for i, r in enumerate(result):
intersect = shp.intersection(r)
if shptype == "POLYGON":
if intersect.area > 0.0:
cellids.append(mglist.index(r))
isectshp.append(intersect)
areas.append(intersect.area)
vertices.append(intersect.__geo_interface__["coordinates"])
elif shptype == "POLYLINE":
try:
isect_iter = iter(intersect)
except TypeError:
isect_iter = [intersect]
for isect in isect_iter:
if isect.length > 0.0:
cellids.append(mglist.index(r))
isectshp.append(isect)
lengths.append(isect.length)
vertices.append(isect.__geo_interface__["coordinates"])
else:
raise NotImplementedError("shptype '{}' is not supported!".format(shptype))
intersect_dict["intersect"] = isectshp
intersect_dict["cellids"] = cellids
intersect_dict["vertices"] = vertices
if shptype == "POLYGON":
intersect_dict["areas"] = areas
elif shptype == "POLYLINE":
intersect_dict["lengths"] = lengths
return intersect_dict Rectangular regular gridgrid_cells = []
nx = 50
ny = 50
x0, y0 = 0, 0
dx, dy = 1000./nx, 1000./ny
for i in range(ny):
for j in range(nx):
x = x0+j*dx
y = y0+i*dy
xy = [[x, y], [x+dx, y], [x+dx, y+dy], [x, y+dy], [x, y]]
grid_cells.append(Polygon(xy)) Shapes to intersect with: poly_intersect = Polygon(shell=[(150, 150), (200, 500), (350, 750), (810, 440), (400, 50), (150, 120)],
holes=[[(250, 250), (250, 450), (450, 450), (450, 250)]])
ls1 = LineString([(50, 50), (350, 475)])
ls2 = LineString([(350, 475), (875, 225)])
mls = MultiLineString(lines=[ls1, ls2]) poly_intersect mls fig, ax = plt.subplots(1, 1, figsize=(10, 10))
ax.set_aspect("equal")
polys = []
for g in grid_cells:
pp = PolygonPatch(g, facecolor="C0", alpha=0.5)
ax.add_patch(pp)
polys.append(g)
pp2 = PolygonPatch(poly_intersect, alpha=0.5, facecolor="red")
ax.add_patch(pp2)
for ig in mls.geoms:
ax.plot(ig.xy[0], ig.xy[1])
ax.set_xlim(-dx, nx*1000./nx+dx)
ax.set_ylim(-dy, ny*1000./ny+dy)
Polygon with regular grid%%time
result = intersect(polys, poly_intersect, shptype="POLYGON")
print(result.keys())
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
ax.set_aspect("equal")
for g in grid_cells:
pp = PolygonPatch(g, edgecolor="k", alpha=1.0, facecolor="none")
ax.add_patch(pp)
for i, ishp in enumerate(result["intersect"]):
ppi = PolygonPatch(ishp, facecolor="C{}".format(i%10), alpha=0.5)
ax.add_patch(ppi)
for cid in result["cellids"]:
c = polys[cid].centroid
ax.plot(c.x, c.y, "r.")
ax.set_xlim(-dx, (nx)*1000/nx+dx)
ax.set_ylim(-dy, (ny)*1000/ny+dy)
plt.show() Polyline with regular grid%%time
result = intersect(polys, mls, shptype="POLYLINE")
print(result.keys())
fig, ax = plt.subplots(1, 1, figsize=(10, 10))
ax.set_aspect("equal")
for g in grid_cells:
pp = PolygonPatch(g, edgecolor="k", alpha=1.0, facecolor="none")
ax.add_patch(pp)
for i, ishp in enumerate(result["intersect"]):
ax.plot(ishp.xy[0], ishp.xy[1], ls="-", c="C{}".format(i%10))
for cid in result["cellids"]:
c = polys[cid].centroid
ax.plot(c.x, c.y, "r.")
ax.set_xlim(-dx, (nx)*1000./nx+dx)
ax.set_ylim(-dy, (ny)*1000./ny+dy)
plt.show() Triangular gridfrom flopy.utils.triangle import Triangle as Triangle
import config
maximum_area = 500.
domainpoly = [(x0, y0), (x0, y0+ny*dy), (x0+nx*dx, y0+ny*dy), (x0+nx*dx, y0)]
tri = Triangle(maximum_area=maximum_area, angle=30, model_ws=".", exe_name=config.triangleexe)
tri.add_polygon(domainpoly)
tri.build(verbose=False)
fig = plt.figure(figsize=(10, 10))
ax = plt.subplot(1, 1, 1, aspect='equal')
pc = tri.plot(ax=ax) tri.ncpl
iverts, verts = tri.iverts, tri.verts %%time
ptchs = []
tridict = {}
for icell, ivertlist in enumerate(iverts):
points = []
for iv in ivertlist:
points.append((verts[iv, 0], verts[iv, 1]))
# close the polygon, if necessary
if ivertlist[0] != ivertlist[-1]:
iv = ivertlist[0]
points.append((verts[iv, 0], verts[iv, 1]))
ptchs.append(Polygon(points))
Polygon with triangular grid%%time
result = intersect(ptchs, poly_intersect, shptype="POLYGON")
print(result.keys())
fig = plt.figure(figsize=(10, 10))
ax = plt.subplot(1, 1, 1, aspect='equal')
pc = tri.plot(ax=ax)
for i, ishp in enumerate(result["intersect"]):
ppi = PolygonPatch(ishp, facecolor="C{}".format(i%10), alpha=0.5)
ax.add_patch(ppi)
for cid in result["cellids"]:
c = ptchs[cid].centroid
ax.plot(c.x, c.y, "r.")
ax.set_xlim(-dx, (nx)*1000./nx+dx)
ax.set_ylim(-dy, (ny)*1000./ny+dy)
plt.show() Polyline with triangular grid%%time
result = intersect(ptchs, mls, shptype="POLYLINE")
print(result.keys())
fig = plt.figure(figsize=(10, 10))
ax = plt.subplot(1, 1, 1, aspect='equal')
pc = tri.plot(ax=ax)
for i, ishp in enumerate(result["intersect"]):
ax.plot(ishp.xy[0], ishp.xy[1], ls="-", c="C{}".format(i%10))
for cid in result["cellids"]:
c = ptchs[cid].centroid
ax.plot(c.x, c.y, "r.")
ax.set_xlim(-dx, (nx)*1000./ny+dx)
ax.set_ylim(-dy, (ny)*1000./nx+dy)
plt.show() |
As @langevin-usgs mentioned in an email, the timings on intersecting with the unstructured grid are quite slow. The times shown in the notebook are what i get on my laptop. When I was writing the notebook I hadn't really noticed initially, but I think i was working with a much smaller grid then. I'll look into why this is the case... could be that STRtree isn't very efficient for unstructured grids, or shapely just isn't as fast as I hoped it would be... |
This is something that we wrote a while back that has some accelerated intersection capabilities, at least for structured grids. It still uses shapely, but it limits the number of cells that it has to check by following the line through the grid.
|
I figured it out. The time for intersecting the polygon with the unstructured grid is now 150 ms instead of 35 seconds. The lookup in the list of modelgrid shapes to get the cellid from the modelgrid is extremely slow. If you comment out those lookups in the intersect function it's a lot faster. def intersect(mglist, shp, shptype="POLYGON"):
intersect_dict = {}
s = STRtree(mglist)
result = s.query(shp)
isectshp = []
cellids = []
vertices = []
areas = []
lengths = []
for i, r in enumerate(result):
intersect = shp.intersection(r)
if shptype == "POLYGON":
if intersect.area > 0.0:
# cellids.append(mglist.index(r)) # this is extremely slow
isectshp.append(intersect)
areas.append(intersect.area)
vertices.append(intersect.__geo_interface__["coordinates"])
elif shptype == "POLYLINE":
try:
isect_iter = iter(intersect)
except TypeError:
isect_iter = [intersect]
for isect in isect_iter:
if isect.length > 0.0:
# cellids.append(mglist.index(r)) # this is extremely slow
isectshp.append(isect)
lengths.append(isect.length)
vertices.append(isect.__geo_interface__["coordinates"])
else:
raise NotImplementedError("shptype '{}' is not supported!".format(shptype))
intersect_dict["intersect"] = isectshp
intersect_dict["cellids"] = cellids
intersect_dict["vertices"] = vertices
if shptype == "POLYGON":
intersect_dict["areas"] = areas
elif shptype == "POLYLINE":
intersect_dict["lengths"] = lengths
return intersect_dict |
@dbrakenhoff and @rubencalje, @jdhughes-usgs and @langevin-usgs discussed using a Thoughts? |
I am working on combining the code @langevin-usgs posted and the notebook I made. I'm fine with the |
On the speed issue related to the line
A possible fix is to replace it with (see: shapely/shapely#618)
where
This worked for me so I thought I'd share it here. |
Thanks for the tip @vincentpost ! It's taken a while, but I've attached a notebook that contains a GridIntersect class which structures the intersect functionality from the first notebook. The way it works is you create an GridIntersect object from your flopy model grid. This converts the gridcells to Shapely geometries and builds an STRTree. Intersects can then be performed using I've added some tests and some timing at the bottom of the notebook. Curious to hear what you think! If this is something worth including, do you think adding it to the existing Grid objects like the example below would be a good idea? Or would you prefer adding as something separate? class StructuredGrid:
def intersect_polylines(self, list_of_lines):
ix = GridIntersect(self)
result = []
for line in list_of_lines:
result.append(ix.intersect_polyline(line))
return result This example does mean the conversion to Shapely and building the STRTree are performed for each intersect call (but you can do multiple intersects in one go). It does mean that changing the grid after creation won't result in unexpected differences. ps. @langevin-usgs I wanted to compare with your gridops.py file, but I couldn't find |
There is a version of the mfgridutil here: https://github.com/usgs/gw-general-models/tree/master/general-models/MFGrid/mfgrid. Excited to take a look at your intersection capabilities. Will try to do so over the next few days. Thanks for putting it together! |
@dbrakenhoff, I'm starting to play with your nice intersect class. I ran into one quick thing early on, that is just related to plotting. Trying passing in a rotation angle like:
In order for things to show up right, you need to replace [irow, 0] and [0, icol] with [irow, icol], but this is trivial of course. We are at the MODFLOW conference next week, so maybe we can talk it over with some people and see what they think. Will keep you posted! |
One thing to think about: what is a reasonable amount of time for some common operations? For example, the following search for 100 random points in a million cell model takes a bit of time (a couple minutes I think). Do we need to have an optimized search for structured grids in this case?
|
I agree we should make use of the optimized search if at all possible. I've added the methods you sent me earlier to the GridIntersect class. The methods used now depend on the method keyword argument passed when you instantiate the class. By setting I've added more tests, and included some timing comparisons in the notebook in the attached zipfile. Curious to hear what you think! ps. just remembered your comment about the plotting of rotated grids, but haven't yet fixed my code to take that into account. |
Just submitted a PR #610 . I also have a notebook containing some of the functionality, but not sure if I can add that to flopy notebooks, because it cannot be run unless shapely is available. Here's a zipfile containing the gridintersect code, the tests and the example notebook which can be run seperately. |
Can you try to update the install block in the - pip install shapely[vectorized]
- pip install nbconvert
- pip install nose-timer
- pip install coveralls
- pip install pylint You should use the same initial code block from the other notebooks so that flopy loads correctly. According to PyPi the |
That seems to work. There are some failures on Python 2.7 for grid intersection and there is some unrelated Notebook failing. |
Does the failing notebook run for you? Also is your fork current with develop on upstream? |
Added by #610 . Issue can be closed. |
Functionality has been added with #610. Closing... |
Here are some thoughts related to March 2019 developer meeting at Delft.
Possible Dependencies
geopandas, shapely, others
Projections, sampling, etc
Initial thoughts for syntax of calls
Point shape to grid for boundary conditions
or
Polyline shape to grid for boundary conditions
Polygon shape to grid for boundary conditions
The text was updated successfully, but these errors were encountered: