Find shortest linestring between two points while avoiding n-sided polygon - python

I am trying to find the shortest linestring between two points. There is a constraint that there is an n-sided polygon possibly directly between the 2 points. I am not allowed to cross through the polygon but only pass through its edges.
eg.
start = (2,0)
end = (0,1)
poly = [(1,0),(1,1),(1,2),(2,1)]
passing it through the function would output 2.41
so far I have
from shapely.geometry import LineString, Polygon, Point
def shortest_linestring(start, end, poly):
poly = Polygon(poly)
p1 = Point(start)
p2 = Point(end)
but I am completely stumped as to what to do next. Any hint would be appreciated.

Related

Shortest path between many 2D points (travelling salesman within Shapely LineString?)

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:

Create new shapely polygon by subtracting the intersection with another polygon

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)

Finding coordinate points of intersection with two numpy arrays

This sort of question is a tad bit different the normal 'how to find the intersection of two lines' via numpy. Here is the situation, I am creating a program that looks at slope stability and I need to find where a circle intersects a line.
I have two numpy arrays:
One array gives me a normal (x, y) values of an elevation profile in 2D
The other array is calculated values of coordinates (x, y) that spans the circumference of a circle from a defined centre.
I need to somehow compare the two at what approximate point does the coordinates of the circle intersect the profile line?
Here some data to work with:
circ_coords = np.array([
[.71,.71],
[0.,1.]
])
linear_profile = np.array([
[0.,0.],
[1.,1.]
])
I need a function that would spit out say a single or multiple coordinate values saying that based on these circular coordinates and your linear profile.. the two would intersect here.
def intersect(array1, array2):
# stuff
return computed_array
You can solve it algebraically. The parametric representation of points (x,y) on the line segment between (x1,y1) and (x2,y2) is:
x=tx1+(1−t)x2 and y=ty1+(1−t)y2,
where 0≤t≤1.
If you substitute it in the equation of the circle and solve the resulting quadratic equation for t, you can test if 0≤t01≤1, i.e line segment intersets with circle. The t01 values could be than used to calculate intersection points.
Shapely has some cool functions. According to this post, this code should work:
from shapely.geometry import LineString
from shapely.geometry import Point
p = Point(0,0)//center
c = p.buffer(0.71).boundary//radius
l = LineString([(0.,0.), (1., 1.)])//line point
i = c.intersection(l)
Apparently here i is the array you are looking for, also, check this post too. Hope this helps.

Find coordinate of the closest point on polygon in Shapely

Say I have the following Polygon and Point:
>>> poly = Polygon([(0, 0), (2, 8), (14, 10), (6, 1)])
>>> point = Point(12, 4)
I can calculate the point's distance to the polygon...
>>> dist = point.distance(poly)
>>> print(dist)
2.49136439561
...but I would like to know the coordinate of the point on the polygon border where that shortest distance measures to.
My initial approach is to buffer the point by its distance to the polygon, and find the point at which that circle is tangent to the polygon:
>>> buff = point.buffer(dist)
However, I'm not sure how to calculate that point. The two polygon's don't intersect so list(poly.intersection(buff)) will not give me that point.
Am I on the right track with this? Is there a more straightforward method?
While the answer of eguaio does the job, there is a more natural way to get the closest point using shapely.ops.nearest_points function:
from shapely.geometry import Point, Polygon
from shapely.ops import nearest_points
poly = Polygon([(0, 0), (2, 8), (14, 10), (6, 1)])
point = Point(12, 4)
# The points are returned in the same order as the input geometries:
p1, p2 = nearest_points(poly, point)
print(p1.wkt)
# POINT (10.13793103448276 5.655172413793103)
The result is the same as in the other answer:
from shapely.geometry import LinearRing
pol_ext = LinearRing(poly.exterior.coords)
d = pol_ext.project(point)
p = pol_ext.interpolate(d)
print(p.wkt)
# POINT (10.13793103448276 5.655172413793103)
print(p.equals(p1))
# True
Please, do not up-vote this answer, the correct answer is #Georgy 's answer below.
My answer for reference:
There is an easy way to do this relying on Shapely functions.
First, you need to get the exterior ring of the polygon and project the point to the ring. It is mandatory to get the exterior as a LinearRing since polygons
do not have the projection function. Opposed to intuition, this gives a distance, the distance from the first point of the ring to the point in the ring closest to the given point. Then, you just use that distance to get the point with the interpolate function. See the code below.
from shapely.geometry import Polygon, Point, LinearRing
poly = Polygon([(0, 0), (2,8), (14, 10), (6, 1)])
point = Point(12, 4)
pol_ext = LinearRing(poly.exterior.coords)
d = pol_ext.project(point)
p = pol_ext.interpolate(d)
closest_point_coords = list(p.coords)[0]
It is important to mention that this method only works if you know the point is outside the exterior of the polygon. If the point is inside one of its interior rings, you need to adapt the code for that situation.
If the polygon doesn't have interior rings, the code will work even for points inside the polygon. That is because we are in fact working with the exterior ring as a line string, and ignoring whether the line string comes from a polygon or not.
It is easy to extend this code to the general case of computing the distance of any point (inside or outside of the polygon) to the closest point in the polygon boundary. You only need to compute the closest point (and distance) from the point to all line rings: the exterior ring, and each interior ring of the polygon. Then, you just keep the minimum of those.
There are two cases two consider: (1) the closest point lies on an edge and (2) the closest point is a vertex. Case (2) is easy to check - just take the distance to each vertex and find the minimum. Case (1) involves a little more math but still isn't too bad. You need to do two things for case (1): (a) find where the normal from the point to the edge intersects the edge, and (b) verify that it lies within the line segment (as opposed to extending past one of the ends). If it's not on the line segment, ignore it (one of the vertices will be the closest point on that edge).
I liked the idea of intersecting the polygon poly with a circle buff centered at the point point, just as you wrote in your question. I would suggest:
poly.boundary.intersection(buff.boundary)

Editing voronoi class to return polygon points in python

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)

Categories

Resources