from a set of points I built the Voronoi tessellation using scipy:
from scipy.spatial import Voronoi
vor = Voronoi(points)
Now I would like to build a Polygon in Shapely from the regions the Voronoi algorithm created. The problem is that the Polygon class requires a list of counter-clockwise vertices. Although I know how to order these vertices, I can't solve the problem because often this is my result:
(overlapping polygon). This is the code (ONE RANDOM EXAMPLE):
def order_vertices(l):
mlat = sum(x[0] for x in l) / len(l)
mlng = sum(x[1] for x in l) / len(l)
# https://stackoverflow.com/questions/1709283/how-can-i-sort-a-coordinate-list-for-a-rectangle-counterclockwise
def algo(x):
return (math.atan2(x[0] - mlat, x[1] - mlng) + 2 * math.pi) % 2*math.pi
l.sort(key=algo)
return l
a = np.asarray(order_vertices([(9.258054711746084, 45.486245994138976),
(9.239284166975443, 45.46805963143515),
(9.271640747003861, 45.48987234571072),
(9.25828782103321, 45.44377372506324),
(9.253993275176263, 45.44484395950612),
(9.250114174032936, 45.48417979682819)]))
plt.plot(a[:,0], a[:,1])
How can I solve this problem?
If you're just after a collection of polygons you don't need to pre-order the point to build them.
The scipy.spatial.Voronoi object has a ridge_vertices attribute containing indices of vertices forming the lines of the Voronoi ridge. If the index is -1 then the ridge goes to infinity.
First start with some random points to build the Voronoi object.
import numpy as np
from scipy.spatial import Voronoi, voronoi_plot_2d
import shapely.geometry
import shapely.ops
points = np.random.random((10, 2))
vor = Voronoi(points)
voronoi_plot_2d(vor)
You can use this to build a collection of Shapely LineString objects.
lines = [
shapely.geometry.LineString(vor.vertices[line])
for line in vor.ridge_vertices
if -1 not in line
]
The shapely.ops module has a polygonize that returns a generator for Shapely Polygon objects.
for poly in shapely.ops.polygonize(lines):
#do something with each polygon
Or if you wanted a single polygon formed from the region enclosed by the Voronoi tesselation you can use the Shapely unary_union method:
shapely.ops.unary_union(list(shapely.ops.polygonize(lines)))
As others have said, it is because you have to rebuild the polygons from the resulting points correctly based on indexes. Although you have the solution, I thought I should mention there is also another pypi supported tesselation package called Pytess (Disclaimer: I am the package maintainer) where the voronoi function returns the voronoi polygons fully built for you.
The library is able to generate ordered list of coordinates, you just need to make use of the index lists provided:
import numpy as np
from scipy.spatial import Voronoi
...
ids = np.array(my_points_list)
vor = Voronoi(points)
polygons = {}
for id, region_index in enumerate(vor.point_region):
points = []
for vertex_index in vor.regions[region_index]:
if vertex_index != -1: # the library uses this for infinity
points.append(list(vor.vertices[vertex_index]))
points.append(points[0])
polygons[id]=points
each polygon in the polygons dictionary can be exported to geojson or brought into shapely and I was able to render them properly in QGIS
The function you implemented (order_vertices() ), cannot work in your case, because it simply takes a already ordered sequence of coordinates, which builds a rectangle, and inverts the direction of the polygon (and maybe works only with rectangles...).
But you have a not ordered sequence of coordinates
Generally speaking, you can not build a polygon from a arbitrary sequence of not ordered vertices because there is no a unique solution for concave polygons, as showed in this example: https://stackoverflow.com/a/7408711/4313133
However, if you are sure that your polygons are always convex, you can build a convex hull with this code: https://stackoverflow.com/a/15945375/4313133 (tested right now, it worked for me)
Probably you can build the convex hull with scipy as well, but I dint test it: scipy.spatial.ConvexHull
Related
from a set of points I built the Voronoi tessellation using scipy:
from scipy.spatial import Voronoi
vor = Voronoi(points)
Now I would like to build a Polygon in Shapely from the regions the Voronoi algorithm created. The problem is that the Polygon class requires a list of counter-clockwise vertices. Although I know how to order these vertices, I can't solve the problem because often this is my result:
(overlapping polygon). This is the code (ONE RANDOM EXAMPLE):
def order_vertices(l):
mlat = sum(x[0] for x in l) / len(l)
mlng = sum(x[1] for x in l) / len(l)
# https://stackoverflow.com/questions/1709283/how-can-i-sort-a-coordinate-list-for-a-rectangle-counterclockwise
def algo(x):
return (math.atan2(x[0] - mlat, x[1] - mlng) + 2 * math.pi) % 2*math.pi
l.sort(key=algo)
return l
a = np.asarray(order_vertices([(9.258054711746084, 45.486245994138976),
(9.239284166975443, 45.46805963143515),
(9.271640747003861, 45.48987234571072),
(9.25828782103321, 45.44377372506324),
(9.253993275176263, 45.44484395950612),
(9.250114174032936, 45.48417979682819)]))
plt.plot(a[:,0], a[:,1])
How can I solve this problem?
If you're just after a collection of polygons you don't need to pre-order the point to build them.
The scipy.spatial.Voronoi object has a ridge_vertices attribute containing indices of vertices forming the lines of the Voronoi ridge. If the index is -1 then the ridge goes to infinity.
First start with some random points to build the Voronoi object.
import numpy as np
from scipy.spatial import Voronoi, voronoi_plot_2d
import shapely.geometry
import shapely.ops
points = np.random.random((10, 2))
vor = Voronoi(points)
voronoi_plot_2d(vor)
You can use this to build a collection of Shapely LineString objects.
lines = [
shapely.geometry.LineString(vor.vertices[line])
for line in vor.ridge_vertices
if -1 not in line
]
The shapely.ops module has a polygonize that returns a generator for Shapely Polygon objects.
for poly in shapely.ops.polygonize(lines):
#do something with each polygon
Or if you wanted a single polygon formed from the region enclosed by the Voronoi tesselation you can use the Shapely unary_union method:
shapely.ops.unary_union(list(shapely.ops.polygonize(lines)))
As others have said, it is because you have to rebuild the polygons from the resulting points correctly based on indexes. Although you have the solution, I thought I should mention there is also another pypi supported tesselation package called Pytess (Disclaimer: I am the package maintainer) where the voronoi function returns the voronoi polygons fully built for you.
The library is able to generate ordered list of coordinates, you just need to make use of the index lists provided:
import numpy as np
from scipy.spatial import Voronoi
...
ids = np.array(my_points_list)
vor = Voronoi(points)
polygons = {}
for id, region_index in enumerate(vor.point_region):
points = []
for vertex_index in vor.regions[region_index]:
if vertex_index != -1: # the library uses this for infinity
points.append(list(vor.vertices[vertex_index]))
points.append(points[0])
polygons[id]=points
each polygon in the polygons dictionary can be exported to geojson or brought into shapely and I was able to render them properly in QGIS
The function you implemented (order_vertices() ), cannot work in your case, because it simply takes a already ordered sequence of coordinates, which builds a rectangle, and inverts the direction of the polygon (and maybe works only with rectangles...).
But you have a not ordered sequence of coordinates
Generally speaking, you can not build a polygon from a arbitrary sequence of not ordered vertices because there is no a unique solution for concave polygons, as showed in this example: https://stackoverflow.com/a/7408711/4313133
However, if you are sure that your polygons are always convex, you can build a convex hull with this code: https://stackoverflow.com/a/15945375/4313133 (tested right now, it worked for me)
Probably you can build the convex hull with scipy as well, but I dint test it: scipy.spatial.ConvexHull
I was trying to create river cross-section profiles based on the point terrestical measurements. When trying to create a Shapely LineString from a Series of points with the common id, I realized that the order of given points really matters as the LineString would just connect given points 'indexwise' (connect points in the list-given order). The below code illustrates the default behaviour:
from shapely.geometry import Point, LineString
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
# Generate random points
x=np.random.randint(0,100,10)
y=np.random.randint(0,50,10)
data = zip(x,y)
# Create Point and default LineString GeoSeries
gdf_point = gpd.GeoSeries([Point(j,k) for j,k in data])
gdf_line = gpd.GeoSeries(LineString(zip(x,y)))
# plot the points and "default" LineString
ax = gdf_line.plot(color='red')
gdf_point.plot(marker='*', color='green', markersize=5,ax=ax)
That would produce the image:
Question: Is there any built-in method within Shapely that would automatically create the most logical (a.k.a.: the shortest, the least complicated, the least criss-cross,...) line through the given list of random 2D points?
Below can you find the desired line (green) compared to the default (red).
Here is what solved my cross-section LineString simplification problem. However, my solution doesn't correctly address computationally more complex task of finding the ultimately shortest path through the given points. As the commenters suggested, there are many libraries and scripts available to solve that particulal problem, but in case anyone want to keep it simple, you can use what did the trick for me. Feel free to use and comment!
def simplify_LineString(linestring):
'''
Function reorders LineString vertices in a way that they each vertix is followed by the nearest remaining vertix.
Caution: This doesn't calculate the shortest possible path (travelling postman problem!) This function performs badly
on very random points since it doesn't see the bigger picture.
It is tested only with the positive cartesic coordinates. Feel free to upgrade and share a better function!
Input must be Shapely LineString and function returns Shapely Linestring.
'''
from shapely.geometry import Point, LineString
import math
if not isinstance(linestring,LineString):
raise IOError("Argument must be a LineString object!")
#create a point lit
points_list = list(linestring.coords)
####
# DECIDE WHICH POINT TO START WITH - THE WESTMOST OR SOUTHMOST? (IT DEPENDS ON GENERAL DIRECTION OF ALL POINTS)
####
points_we = sorted(points_list, key=lambda x: x[0])
points_sn = sorted(points_list, key=lambda x: x[1])
# calculate the the azimuth of general diretction
westmost_point = points_we[0]
eastmost_point = points_we[-1]
deltay = eastmost_point[1] - westmost_point[1]
deltax = eastmost_point[0] - westmost_point[0]
alfa = math.degrees(math.atan2(deltay, deltax))
azimut = (90 - alfa) % 360
if (azimut > 45 and azimut < 135):
#General direction is west-east
points_list = points_we
else:
#general direction is south-north
points_list = points_sn
####
# ITERATIVELY FIND THE NEAREST VERTIX FOR THE EACH REMAINING VERTEX
####
# Create a new, ordered points list, starting with the east or southmost point.
ordered_points_list = points_list[:1]
for iteration in range(0, len(points_list[1:])):
current_point = ordered_points_list[-1] # current point that we are looking the nearest neighour to
possible_candidates = [i for i in points_list if i not in ordered_points_list] # remaining (not yet sortet) points
distance = 10000000000000000000000
best_candidate = None
for candidate in possible_candidates:
current_distance = Point(current_point).distance(Point(candidate))
if current_distance < distance:
best_candidate = candidate
distance = current_distance
ordered_points_list.append(best_candidate)
return LineString(ordered_points_list)
There is no built in function, but shapely has a distance function.
You could easily iterate over the points and calculate the shortest distance between them and construct the 'shortest' path.
There are some examples in the offical github repo.
Google's OR-Tools offer a nice and efficient way for solving the Travelling Salesman Problem: https://developers.google.com/optimization/routing/tsp.
Following the tutorial on their website would give you a solution from this (based on your example code):
to this:
I have two shapely MultiPolygon instances (made of lon,lat points) that intersect at various parts. I'm trying to loop through, determine if there's an intersection between two polygons, and then create a new polygon that excludes that intersection. From the attached image, I basically don't want the red circle to overlap with the yellow contour, I want the edge to be exactly where the yellow contour starts.
I've tried following the instructions here but it doesn't change my output at all, plus I don't want to merge them into one cascading union. I'm not getting any error messages, but when I add these MultiPolygons to a KML file (just raw text manipulation in python, no fancy program) they're still showing up as circles without any modifications.
# multipol1 and multipol2 are my shapely MultiPolygons
from shapely.ops import cascaded_union
from itertools import combinations
from shapely.geometry import Polygon,MultiPolygon
outmulti = []
for pol in multipoly1:
for pol2 in multipoly2:
if pol.intersects(pol2)==True:
# If they intersect, create a new polygon that is
# essentially pol minus the intersection
intersection = pol.intersection(pol2)
nonoverlap = pol.difference(intersection)
outmulti.append(nonoverlap)
else:
# Otherwise, just keep the initial polygon as it is.
outmulti.append(pol)
finalpol = MultiPolygon(outmulti)
I guess you can use the symmetric_difference between theses two polygons, combined by the difference with the second polygon to achieve what you want to do (the symmetric difference will brings you the non-overlapping parts from the two polygons, on which are removed parts of the polygon 2 by the difference). I haven't tested but it might look like :
# multipol1 and multipol2 are my shapely MultiPolygons
from shapely.ops import cascaded_union
from itertools import combinations
from shapely.geometry import Polygon,MultiPolygon
outmulti = []
for pol in multipoly1:
for pol2 in multipoly2:
if pol.intersects(pol2)==True:
# If they intersect, create a new polygon that is
# essentially pol minus the intersection
nonoverlap = (pol.symmetric_difference(pol2)).difference(pol2)
outmulti.append(nonoverlap)
else:
# Otherwise, just keep the initial polygon as it is.
outmulti.append(pol)
finalpol = MultiPolygon(outmulti)
I am using the python scipy to compute the voronoi diagram from a set of points in 2-dimensions as follows:
import numpy as np
from scipy.spatial import Voronoi
x = np.random.uniform(-1, 1, (1000, 2))
v = Voronoi(x)
I got quite confused with the different attributes of the voronoi object. What I basically want to do now is filter out all the vertices which are beyond -0.5 and 0.5 in both the dimensions.
You'll have to explain what you mean by "filter" (filter out?). In any case you can obtain the vertices and several types of ridges of the voronoi shapes:
verts = v.vertices
, this will give an array with two columns (x and y coordinates for the vertices). You can mask them the same way you do with numpy arrays (like verts[:,0] > -0.5) and use them for whatever you wish.
I'm not entirely sure if this answers your question but you can find a pretty good tutorial here, including plotting.
Would anyone with Python experience be able to take a look at this for me?
I'm using this code:
https://bitbucket.org/mozman/geoalg/src/5bbd46fa2270/geoalg/voronoi.py
to perform voronoi tesselation on a group of points.
It works, but the problem is that the code only provides a list of all the vertices used to create the polygons, and which pairs must be joined together. It doesn't provide any information as to what points are used to make up each polygon, which I need.
Thanks.
context.triangles says in which Delaunay triangles input point participate. Each triangle is related to a Voronoi vertex. Triangles and vertices are stored parallel in triangles and vertices arrays.
To find Voronoi cell for a given point p, it is needed to find all vertices (triangles) in which input point is used. And than find order how these vertices are connected by edges array.
Here is a simple (not quite tested) code to do that:
from voronoi import voronoi
import random
from collections import defaultdict
num_points = 50
points = [(random.uniform(0,10), random.uniform(0,10)) for i in xrange(num_points)]
c = voronoi(points)
# For each point find triangles (vertices) of a cell
point_in_triangles = defaultdict(set)
for t_ind, ps in enumerate(c.triangles):
for p in ps:
point_in_triangles[p].add(t_ind)
# Vertex connectivity graph
vertex_graph = defaultdict(set)
for e_ind, (_, r, l) in enumerate(c.edges):
vertex_graph[r].add(l)
vertex_graph[l].add(r)
def cell(point):
if point not in point_in_triangles:
return None
vertices = set(point_in_triangles[point]) # copy
v_cell = [vertices.pop()]
vertices.add(-1) # Simulate infinity :-)
while vertices:
neighbours = vertex_graph[v_cell[-1]] & vertices
if not neighbours:
break
v_cell.append(neighbours.pop())
vertices.discard(v_cell[-1])
return v_cell
for p in xrange(num_points):
print p, cell(p)