shapely nearest_points between two polygons returns expected result for simple polygons, unexpected result for complex polygons - nearest-neighbor

I am using nearest_points from shapely to retrieve the nearest points between two polygons.
I get the expected result for two simple polygons:
However for more complex polygons, the points are not the expected nearest points between the polygons.
Note: I added ax.set_aspect('equal') so that the nearest points line woild have to be at a right angle (right?)
What is wrong with my code or my polygons (or me)?
from shapely.geometry import Point, Polygon, LineString, MultiPolygon
from shapely.ops import nearest_points
import matplotlib.pyplot as plt
import shapely.wkt as wkt
#Set of 2 Polyogons A where the nearest points don's seem right
poly1=wkt.loads('POLYGON((-0.136319755454978 51.460464712623626, -0.1363352511419218 51.46042713866513, -0.1363348393705439 51.460425, -0.1365967582352347 51.460425, -0.1363077932125138 51.4605825392028, -0.136237298707157 51.46052697162038, -0.136319755454978 51.460464712623626))')
poly2=wkt.loads('POLYGON ((-0.1371553266140889 51.46046700960882, -0.1371516327997412 51.46046599134276, -0.1371478585043985 51.46046533117243, -0.1371440383598866 51.46046503515535, -0.1371402074187299 51.460465106007696, -0.1371364008325196 51.460465543079344, -0.137132653529373 51.46046634235985, -0.1371289998934435 51.46046749651525, -0.1371254734494216 51.46046899495536, -0.1371221065549237 51.46047082393093, -0.1366012836405492 51.460786954965236, -0.1365402944168757 51.46074798846902, -0.1370125055334012 51.46045400071198, -0.1371553266140889 51.46046700960882))')
#Set of 2 polygons B where the nearest points seem right
#poly1 = Polygon([(0, 0), (2, 8), (14, 10), (6, 1)])
#poly2 = Polygon([(10, 0),(13,5),(14,2)])
p1, p2 = nearest_points(poly1, poly2)
fig,ax= plt.subplots()
ax.set_aspect('equal')
x1,y1=poly1.exterior.xy
x2,y2=poly2.exterior.xy
#Plot Polgygons
plt.plot(x1,y1)
plt.plot(x2,y2)
#Plot LineString connecting the nearest points
plt.plot([p1.x, p2.x],[p1.y,p2.y], color='green')
fig.show()

Related

Geopandas: How to relate the length of a linestring to the linestring point used to find distance to polygon

I’m trying to find the length of the linestring between the starting point of the linestring and the point which are used to find the nearest distance to a polygon.
So I used the following code to get the minimum distance between the linestring and some polygons.
gdf['MinDistToTrack'] = gdf.geometry.apply(lambda l: min(rail_or.distance(l)))
and I would also like to get the distance from the start of the linestring to the point used by the above code.
Now I get dataframe containing the polygons with a value 'MinDistToTrack' (which I have now) but also with a value ‘Length_Of_Linestring_Up_To_Location_Of_Polygon’.
So, let’s say that from the start of the linestring to the polygon there are 22 meters following the path of the linestring, then this is the value I would like to save together with the 'MinDistToTrack'
Polygon ID : 1
'MinDistToTrack' : 1m
'LengthOfLinestringUpToLocationOfPolygon' : 22m
Is this possible or do I need to split the linestring up into small elements and then look at all elements and the length of all the preceding elements in relation to the linestring elements which is nearest to the polygon?
Picture showing the problem
You may use the following concepts from shapely:
The nearest_points() function in shapely.ops calculates the nearest points in a pair of geometries.
shapely.ops.nearest_points(geom1, geom2)
Returns a tuple of the nearest points in the input geometries. The points are returned in the same order as the input geometries.
https://shapely.readthedocs.io/en/stable/manual.html#shapely.ops.nearest_points
from shapely.ops import nearest_points
P = Polygon([(0, 0), (1, 0), (0.5, 1), (0, 0)])
Lin = Linestring([(0, 2), (1, 2), (1, 3), (0, 3)])
nps = [o.wkt for o in nearest_points(P, Lin)]
##nps = ['POINT (0.5 1)', 'POINT (0.5 2)']
np_lin = = nps[1]
You can then use the point np_lin and Project it on the Lin to get the distance using
d = Lin.project(np_lin)
d will be the distance along Lin to the point np_lin i.e. nearest to the corresponding Point of P.

Access Z coordinate in a LINESTRING Z in geopandas?

I have a GeoDataFrame with a LINESTRING Z geometry where Z is my altitude for the lat/long. (There are other columns in the dataframe that I deleted for ease of sharing but are relevant when displaying the resulting track)
TimeUTC Latitude Longitude AGL geometry
0 2021-06-16 00:34:04+00:00 42.835413 -70.919610 82.2 LINESTRING Z (-70.91961 42.83541 82.20000, -70...
I would like to find the maximum Z value in that linestring but I am unable to find a way to access it or extract the x,y,z values in a way that I can determine the maximum value outside of the linestring.
line.geometry.bounds only returns the x,y min/max.
The best solution I could come up with was to turn all the points into a list of tuples:
points = line.apply(lambda x: [y for y in x['geometry'].coords], axis=1)
And then find the maximum value of the third element:
from operator import itemgetter
max(ft2,key=itemgetter(2))[2]
I hope there is a better solution available.
Thank you.
You can take your lambda function approach and just take it one step further:
import numpy as np
line['geometry'].apply(lambda geom: np.max([coord[2] for coord in geom.coords]))
Here's a fully reproducible example from start to finish:
import shapely
import numpy as np
import geopandas as gpd
linestring = shapely.geometry.LineString([[0,0,0],
[1,1,1],
[2,2,2]])
gdf = gpd.GeoDataFrame({'id':[1,2,3],
'geometry':[linestring,
linestring,
linestring]})
gdf['max_z'] = (gdf['geometry']
.apply(lambda geom:
np.max([coord[2] for coord in geom.coords])))
In the example above, I create a new column called "max_z" that stores the maximum Z value for each row.
Important note
This solution will only work if you exclusively have LineStrings in your geometries. If, for example, you have MultiLineStrings, you'll have to adapt the function I wrote to take care of that.

How to solve the lack of precision of the coordinates of the centroid points after a buffer?

When generating polygons by buffer (here squares), the geometric points used for generation have different coordinates than those taken by the .centroid method on the polygon after their generation.
Here is an example with just one point.
from shapely.ops import transform
import geopandas as gpd
import shapely.wkt
import pyproj
from math import sqrt
def edge_size(area): return sqrt(area)*1e3
point = "POINT (4379065.583907348 2872272.254645019)"
point = shapely.wkt.loads(point)
center = gpd.GeoSeries(point)
project = pyproj.Transformer.from_proj(
pyproj.Proj('epsg:3395'),
pyproj.Proj('epsg:4326'),
always_xy=True)
center = center.apply(lambda p: transform(project.transform, p))
print(center.iloc[0])
square = point.buffer(
edge_size(3), cap_style=3) #distance of 3km2
square = gpd.GeoSeries(square)
square = square.apply(lambda p: transform(project.transform, p))
square = square.apply(lambda p: p.centroid)
print(square.iloc[0])
#POINT (39.33781544185747 25.11929860805248)
#POINT (39.33781544185747 25.11929777802279)
This leads to processing errors afterwards.
First of all, is this normal? And how to solve this problem?
I also reported my problem here. Thank you for your attention.
Copying my answer from GitHub for posterity.
This is not a bug but a misunderstanding of coordinate transformation. You have to keep in mind that what is square in one projection is not square in another.
If you stick to the same CRS, the output of the centroid of a buffer equals the initial point. But the centroid of a reprojected polygon is slightly off, specifically because you did reprojection that skewed the geometry in one direction.
How to overcome this problem?
Do all your operations in one CRS and reproject once you are done.

How to convert convex hull vertices into a geopandas polygon

Iam using DBSCAN to cluster coordinates together and then using convexhull to draw 'polygons' around each cluster. I then want to construct geopandas polygons out of my convex hull shapes to be used for spatial joining.
import pandas as pd, numpy as np, matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from scipy.spatial import ConvexHull
Lat=[10,10,20,23,27,28,29,34,11,34,66,22]
Lon=[39,40,23,21,11,29,66,33,55,22,11,55]
D=list(zip(Lat, Lon))
df = pd.DataFrame(D,columns=['LAT','LON'])
X=np.array(df[['LAT', 'LON']])
kms_per_radian = 6371.0088
epsilon = 1500 / kms_per_radian
db = DBSCAN(eps=epsilon, min_samples=3)
model=db.fit(np.radians(X))
cluster_labels = db.labels_
num_clusters = len(set(cluster_labels))
cluster_labels = cluster_labels.astype(float)
cluster_labels[cluster_labels == -1] = np.nan
labels = pd.DataFrame(db.labels_,columns=['CLUSTER_LABEL'])
dfnew=pd.concat([df,labels],axis=1,sort=False)
z=[] #HULL simplices coordinates will be appended here
for i in range (0,num_clusters-1):
dfq=dfnew[dfnew['CLUSTER_LABEL']==i]
Y = np.array(dfq[['LAT', 'LON']])
hull = ConvexHull(Y)
plt.plot(Y[:, 1],Y[:, 0], 'o')
z.append(Y[hull.vertices,:].tolist())
for simplex in hull.simplices:
ploted=plt.plot( Y[simplex, 1], Y[simplex, 0],'k-',c='m')
plt.show()
print(z)
the vertices appended in list[z] represent coordinates of the convex hull however they are not constructed in sequence and closed loop object hence constructing polygon using polygon = Polygon(poin1,point2,point3) will not produce a polygon object. is there a way to construct geopandas polygon object using convex hull vertices in order to use for spatial joining. THanks for your advise.
Instead of generating polygon directly, I would make a MultiPoint out of your coordinates and then generate convex hull around that MultiPoint. That should result in the same geometry, but in properly ordered manner.
Having z as list of lists as you do:
from shapely.geometry import MultiPoint
chulls = []
for hull in z:
chulls.append(MultiPoint(hull).convex_hull)
chulls
[<shapely.geometry.polygon.Polygon at 0x117d50dc0>,
<shapely.geometry.polygon.Polygon at 0x11869aa30>]

Split a triangle into smaller triangles

I have a triangulated mesh. I want to limit the maximum edge length. Therefore I take the all triangles with long edges (longer than the limit), and split them into smaller triangles.
My idea is the following:
I split the longest edge in half and get two triangles. If these are also too large I do it recursively. This works nice, because I also split the correspondent adjacent triangle and the vertexes collapse again.
The problem: When there is a acute-angled triangles. The result look a bit weird. Small angles get even smaller, ...
Is there a better way of splitting such triangles.
Another idea is, to split a edge into k equidistant edges, (with k the smallest value, such that edgelength/k < limit).
I can do this on all 3 edges of the triangle. But how should I connect these vertexes?
As you are bothered with small angles and small triangles, I would advise you to use Delaunay triangulation, because one of its properties is that it maximizes the minimal angle and it avoids small triangles.
Delaunay triangulation requires the points as input. Since you don't have this, you could perform the algorithm recursively, splitting lines when they are too long.
The following Python code does exactly what you would like to achieve.
It uses the Delaunay class included in scipy.
def splitViaDelaunay(points, maxLength):
from scipy.spatial import Delaunay
from math import sqrt, ceil
print "Perform Delaunay triangulation with "+str(len(points))+" points"
tri = Delaunay(points)
# get set of edges from the simpleces
edges = set()
for simplex in tri.simplices:
# simplex is one triangle: [ 4 5 17]
edges.add((simplex[0], simplex[1]))
edges.add((simplex[1], simplex[2]))
edges.add((simplex[0], simplex[2]))
# check if all edges are small enough
# and add new points if not
isFinished = True
for edge in edges:
p1, p2 = edge
[x1, y1] = points[p1]
[x2, y2] = points[p2]
length = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))
if length > maxLength:
isFinished = False
# split in how many pieces?
nPieces = ceil(length/maxLength)
for piece in range(1, int(nPieces)):
points.append([x1+piece/float(nPieces)*(x2-x1), y1+piece/float(nPieces)*(y2-y1)])
if not isFinished:
splitViaDelaunay(points, maxLength)
Let's try it out.
points = [[0,0], [10,3], [9.5,4]]
splitViaDelaunay(points, 0.5)
It outputs
Perform Delaunay triangulation with 3 points
Perform Delaunay triangulation with 45 points
Perform Delaunay triangulation with 97 points
Perform Delaunay triangulation with 105 points
Let's see the results now in a figure, created via the matplotlib library from python.
def plotPointsViaDelaunayTriangulation(pnts):
from scipy.spatial import Delaunay
import numpy as np
points = np.array(pnts)
tri = Delaunay(points)
import matplotlib.pyplot as plt
plt.triplot(points[:,0], points[:,1], tri.simplices.copy())
plt.plot(points[:,0], points[:,1], 'o')
plt.show()
plotPointsViaDelaunayTriangulation(points)
This is the result:

Resources