I get this error while using cascaded_union (I have also tried unary_union which produces the same error):
ValueError: No Shapely geometry can be created from null value
I have validated that my polygons are valid. Initially polyB isn't valid, but it is converted to a valid polygon using buffer(0).
Any idea on what I am doing wrong? Here's my code:
from shapely.geometry import Polygon
from shapely.ops import cascaded_union
def combineBorders(a, b):
polyA = Polygon(a)
polyB = Polygon(b)
pols = [polyA, polyB]
for p in pols:
if p.is_valid == False:
p = p.buffer(0)
print(p.is_valid)
True
True
newShape = cascaded_union(pols) # THIS IS WHERE THE ERROR KEEPS SHOWING UP
return newShape
Here is a link to the values for polyA, polyB and pols (after they are confirmed to be valid). I have the following versions installed on my Ubuntu 14.04 server:
python-shapely 1.3.0
libgeos 3.4.2
python 2.7
The problem in the question is that the buffered polygon was not put back in the list pols, so the invalid geometry was passed to cascaded_union
You could make this much simpler and versatile with the following, which can take any number of polygon geometries (not just two).
def combineBorders(*geoms):
return cascaded_union([
geom if geom.is_valid else geom.buffer(0) for geom in geoms
])
polyC = combineBorders(polyA, polyB)
Found out the problem. Not sure why this matters (I've seen examples showing it both ways), but it works after putting the polygons directly into cascaded_union like so: newShape = cascaded_union([polyA, polyB]). Here is the fully revised code that works:
from shapely.geometry import Polygon
from shapely.ops import cascaded_union
def combineBorders(a, b):
polyA = Polygon(a)
polyB = Polygon(b)
polyBufA = polyA.buffer(0)
polyBufB = polyB.buffer(0)
newShape = cascaded_union([polyBufA, polyBufB])
return newShape
This also works with unary_union
Related
I want to clip one raster based on the extent of another (smaller) raster. First I determine the coordinates of the corners of the smaller raster using
import rasterio as rio
import gdal
from shapely.geometry import Polygon
src = gdal.Open(smaller_file.tif)
ulx, xres, xskew, uly, yskew, yres = src.GetGeoTransform()
lrx = ulx + (src.RasterXSize * xres)
lry = uly + (src.RasterYSize * yres)
geometry = [[ulx,lry], [ulx,uly], [lrx,uly], [lrx,lry]]
This gives me the following output geometry = [[-174740.0, 592900.0], [-174740.0, 2112760.0], [900180.0, 2112760.0], [900180.0, 592900.0]]. (Note that the crs is EPSG: 32651).
Now I would like to clip the larger file using rio.mask.mask(). According to the documentation, the shape variable should be GeoJSON-like dict or an object that implements the Python geo interface protocol (such as a Shapely Polygon). Therefore I create a Shapely Polygon out of the variable geometry, using
roi = Polygon(geometry)
Now everything is ready to use the rio.mask() function.
output = rio.mask.mask(larger_file.tif, roi, crop = True)
But this gives me the following error
TypeError: 'Polygon' object is not iterable
What do I do wrong? Or if someone knows a more elegant way to do it, please let me know.
(Unfortunately I cannot upload the two files since they're too large)
I found your question when I needed to figure out this kind of clipping myself. I got the same error and fixed it the following way:
rasterio.mask expects a list of features, not a single geometry. So the algorithm wants to run masking over several features bundled in an iterable (e.g. list or tuple) so we need to pass it our polygon within a list (or tuple) object.
The code you posted works after following change:
roi = [Polygon(geometry)]
All we have to do is to enclose the geometry in a list/tuple and then rasterio.mask works as expected.
Is there an equivalent to the very good st_make_grid method of the sf package from r-spatial in python? The method create rectangular grid geometry over the bounding box of a polygon.
I would like to do exactly the same as the solution proposed in this question, e.g. divide a polygon into several squares of the same area that I choose. Thanks for your help.
Alternatively, I could use rpy2 to run a script in r that executes the st_make_grid method which takes a shapely polygon as input and outputs the square polygons, to be read with shapely. Would this be effective on many polygons to process?
Would this be effective on many polygons to process?
Certainly not. There's no built-in Python version but the function below does the trick. If you need performance, make sure that you have pygeos installed in your environment.
def make_grid(polygon, edge_size):
"""
polygon : shapely.geometry
edge_size : length of the grid cell
"""
from itertools import product
import numpy as np
import geopandas as gpd
bounds = polygon.bounds
x_coords = np.arange(bounds[0] + edge_size/2, bounds[2], edge_size)
y_coords = np.arange(bounds[1] + edge_size/2, bounds[3], edge_size)
combinations = np.array(list(product(x_coords, y_coords)))
squares = gpd.points_from_xy(combinations[:, 0], combinations[:, 1]).buffer(edge_size / 2, cap_style=3)
return gpd.GeoSeries(squares[squares.intersects(polygon)])
I'm trying to calculate the size of a polygon of geographic coordinates using shapely, which seems to require a transformation into a suitable projection to yield a results in square meter. I found a couple of examples online, but I couldn't get it working for my example polygon.
I therefore tried to use the same example polygons that came with the code snippets I found, and I noticed that it works for some whole not for others. To reproduce the results, here's the minimal example code:
import json
import pyproj
from shapely.ops import transform
from shapely.geometry import Polygon, mapping
from functools import partial
coords1 = [(-97.59238135821987, 43.47456565304017),
(-97.59244690469288, 43.47962399877412),
(-97.59191951546768, 43.47962728271748),
(-97.59185396090983, 43.47456565304017),
(-97.59238135821987, 43.47456565304017)]
coords1 = reversed(coords1) # Not sure if important, but https://geojsonlint.com says it's wrong handedness
# Doesn't seem to affect the error message though
coords2 = [(13.65374516425911, 52.38533382814119),
(13.65239769133293, 52.38675829106993),
(13.64970274383571, 52.38675829106993),
(13.64835527090953, 52.38533382814119),
(13.64970274383571, 52.38390931824483),
(13.65239769133293, 52.38390931824483),
(13.65374516425911, 52.38533382814119)]
coords = coords1 # DOES NOT WORK
#coords = coords2 # WORKS
polygon = Polygon(coords)
# Print GeoJON to check on https://geojsonlint.com
print(json.dumps(mapping(polygon)))
projection = partial(pyproj.transform,
pyproj.Proj('epsg:4326'),
pyproj.Proj('esri:54009'))
transform(projection, polygon)
Both coords1 and coords2 are just copied from code snippets that supposedly work. However, only coords2 works for me. I've used https://geojsonlint.com to see if there's a difference between the two polygons, and it seems that the handedness/orientation of the polygon is not valid GeoJSON. I don't know if shapely even cares, but reversing the order -- and https://geojsonlint.com says it's valid GeoJSON then, and it shows the polygon on the map -- does not change the error.
So, it works with coords2, but when I use coords1 I get the following error:
~/env/anaconda3/envs/py36/lib/python3.6/site-packages/shapely/geometry/base.py in _repr_svg_(self)
398 if xmin == xmax and ymin == ymax:
399 # This is a point; buffer using an arbitrary size
--> 400 xmin, ymin, xmax, ymax = self.buffer(1).bounds
401 else:
402 # Expand bounds by a fraction of the data ranges
ValueError: not enough values to unpack (expected 4, got 0)
I assume there's something different about coords1 (and the example polygon from my own data) that causes the problem, but I cannot tell what could be different compared to coords2.
In short, what's the difference between coords1 and coords2, with one working and the other not?
UPDATE: I got it working by adding always_xy=True to the definition of the projections. Together with the newer syntax provided by shapely, avoiding partial, the working snippet looks like this:
project = pyproj.Transformer.from_proj(
pyproj.Proj('epsg:4326'), # source coordinate system
pyproj.Proj('epsg:3857'),
always_xy=True
) # destination coordinate system
transform(project.transform, polygon)
To be honest, even after reading the docs, I don't really know what always_xy is doing. Hence I don't want to provide is an answer.
i think you did good, only that the reversed does not create new dataset.
try to use this function to create reversed order list:
def rev_slice(mylist):
'''
return a revered list
mylist: is a list
'''
a = mylist[::-1]
return a
execute the function like so:
coords = rev_slice(coords1)
I'm having a lot of trouble with getting shapely points to actually snap on linestrings. This is really important to be able to construct a network graph from geospatial data for example. I would like to avoid using shapely.ops.snap as according to the docs this seems only to snap to line vertices. Instead I would like to use the linear referencing technique proposed in this question. For a simple point-in-linestring situation (see TEST2 below) this seems to work fine, and the within assertion evaluates to true. However, I can't seem to get it to work for a more complex linestring, even though matplotlib clearly shows the point to lie somewhere along the linestring.
from shapely.wkt import loads
from shapely.geometry import LineString, Point
import matplotlib.pyplot as plt
import geopandas
class Test:
def __init__(self, point, line):
self.point = point
self.line = line
def snap(self, point, line):
snapped_point = line.interpolate(line.project(point))
return snapped_point
def test(self):
snapped_point = self.snap(self.point, self.line)
plt.close()
fig = plt.figure()
ax = plt.gca()
geopandas.GeoDataFrame(geometry=[snapped_point]).plot(ax=ax)
geopandas.GeoDataFrame(geometry=[ls]).plot(ax=ax)
return snapped_point.within(self.line)
def show(self):
plt.show()
### TEST 1
point = loads('POINT (141508.3132070995 445104.7256057188)')
ls = loads('LINESTRING (141408.7699052179 445535.6376462562, 141420.9609999987 445491.5920000025, 141444.1930000001 445394.7640000023, 141465.3480000009 445299.004, 141486.3999999985 445202.0350000015, 141508.3209999985 445104.6910000004, 141529.2399999978 445006.3560000034, 141550.8399999999 444909.6550000002, 141572.6389999985 444811.9950000045, 141594.0459999999 444713.4210000001, 141614.9339999997 444616.2859999968)')
test1 = Test(point, ls)
print(test1.test())
test1.show()
### TEST 2
point = Point((0.5, 0.46))
ls = LineString([(0,0), (1,1), (2,2)])
test2 = Test(point, ls)
print(test2.test())
test2.show()
Why does the second linestring check evaluate to False? Shouldn't the interpolate and project methods be geometry independent?
I have a list of points describing the boundaries of Spain. I want to be able to tell whether a pair of lat,lon is within these boundaries. I have tried the following:
import shapefile
import matplotlib.pyplot as plt
from shapely.geometry import MultiPoint, Point, Polygon
from shapely.geometry.polygon import Polygon
sf = shapefile.Reader(r"\ESP_adm0.shp")
shapes = sf.shapes()
lat = []; lon = []
for i in range(len(shapes[0].points)):
lon.append(shapes[0].points[i][0]);lat.append(shapes[0].points[i][1])
I know I am retrieving the points, because I'm able to plot and get the desired results:
plt.plot(lon,lat,'.', ms=0.1)
(plot in the link below)
plot result
I do the following to get the poitns into a polygon:
coords = list(zip(lat,lon))
spain_pol = Polygon(coords)
And then I use the contains function, always getting false.
spain_pol.contains(Point(0,42))
spain_pol.contains(Point(42,0))
These both return false. In fact I haven't been able to get a single point I've tried to return a True.
I have tried all sorts of things, and I think I must be missing something fundamental. Perhaps the facts Spain has islands and there's more than one polygon is the problem? I'm lost. Any help welcome.
Just in case someone else has the same issue. The following code worked perfectly:
import shapefile
import fiona
from shapely.geometry import MultiPoint, Point, Polygon,shape
from shapely.geometry.polygon import Polygon
multipol = fiona.open(r"C:\Users\Jordi\Downloads\ESP_adm_shp\ESP_adm0.shp")
multi = next(iter(multipol))
point = Point(0,42)
point.within(shape(multi['geometry']))
This returns a very welcome "True" :)