How to convert convex hull vertices into a geopandas polygon - geopandas

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>]

Related

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

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()

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.

Obtaining a triangle pair that have a shared edge from triangle set obtained from Delaunay Triangulation

I want to obtain triangles which are from triangle set obtained from Delaunay Triangulation. I wrote the following code. How can I obtain triangles which have a shred edge with each other (please see the image)? According to this image, I want to obtain triangle1 and 2 from triangle set obtained from Delaunay Triangulation.
rng default;
P = rand([32 2]);
DT = delaunayTriangulation(P);
triplot(DT)
Short answer: neighbors(DT).
Example:
rng default
P = rand([12 2]);
DT = delaunayTriangulation(P);
IC = incenter(DT);
% visualize incl. ID in the center
figure
triplot(DT)
hold on
text(IC(:,1), IC(:,2), num2str([1:size(IC,1)]'))
% find all neighboring triangles
neighbors(DT)
% for the first triangle
neighbors(DT, 1)

How determine optimal epsilon value in meters for DBSCAN by plotting KNN elbow

Before doing DBSCAN I need to find optimal epsilon value, all the points are geographical coordinates, I need the epsilon value in meters before convert it to radians to apply DBSCAN using haversine metrics
from sklearn.neighbors import NearestNeighbors
neigh = NearestNeighbors(n_neighbors=4)
nbrs = neigh.fit(firms[['y', 'x']])
distances, indices = nbrs.kneighbors(firms[['y', 'x']])
AND THEN
# Plotting K-distance Graph
distances = np.sort(distances, axis=0)
distances = distances[:,1]
plt.figure(figsize=(20,10))
plt.plot(distances)
plt.title('K-distance Graph',fontsize=20)
plt.xlabel('Data Points sorted by distance',fontsize=14)
plt.ylabel('Epsilon',fontsize=14)
plt.show()
and the graph output is this, but I need the epsilon value in meters.
I hope this helps to clarify, just a few observations:
a) You are already finding the optimal epsilon value, using that method and from your figure eps = 0.005.
b) If your points are geographic coordinates, you don't need the epsilon value in meters before converting only to then convert to radians so you can apply DBSCAN using haversine metrics, because from the geographic coordinates you can convert straight away to radians, and then you multiply by 6371000/1000 to get the result in kilometers, like this:
from sklearn.metrics.pairwise import haversine_distances
from math import radians
bsas = [-34.83333, -58.5166646]
paris = [49.0083899664, 2.53844117956]
bsas_in_radians = [radians(_) for _ in bsas]
paris_in_radians = [radians(_) for _ in paris]
result = haversine_distances([bsas_in_radians, paris_in_radians])
result * 6371000/1000 # multiply by Earth radius to get kilometers
Code snippet from:
https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.haversine_distances.html

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