Boundary points from a set of coordinates - python

I have a set of lat,long points, and from this points I'd like to extract the points that form the boundaries, I've used convexhull, but for my purpouse is not enough as convehull just returns the most distant points that form the polygon where all the points fit, I need ALL the points that form the peremiter, something like the image I've attached. What could I do? Is there some kind of package ready to use instead of implement any spatial algorithm?
Thanks

You must use a package for convex polygons. Here is an example:
import alphashape
import matplotlib.pyplot as plt
points = put your points here (can be array)!
alpha = 0.95 * alphashape.optimizealpha(points)
hull = alphashape.alphashape(points, alpha)
hull_pts = hull.exterior.coords.xy
fig, ax = plt.subplots()
ax.scatter(hull_pts[0], hull_pts[1], color='red')

Use Concave hull (Alpha shape) instead.

Assuming that you have all points (latitudes and longitudes) in two lists LATS, LONGS respectively, this python snippet cand do the trick. hullPoint will have the set of points that can draw the convex hull.
import numpy as np
from scipy.spatial import ConvexHull
allPoints=np.column_stack((LATS,LONGS))
hullPoints = ConvexHull(allPoints)

Related

Build a polygon from a set of longitude/latitude coordinates in python

I am trying to build a polygon from a set of long/lat tuples in python. By polygon I mean I need to define an area containing the points, something like a concave hull. The python code I use:
from shapely.geometry import Polygon
import geopandas as gpd
import geoplot
crs = {'init': 'epsg:4326'}
z=[(-88.09614, 42.21828), (-88.09605, 42.21903), (-88.09558, 42.21758), (-88.09466, 42.21857), (-88.09448, 42.2176), (-88.09425999999999, 42.2191), (-88.09406, 42.2186), (-88.09352, 42.21763), (-88.09329, 42.21859), (-88.09317, 42.21907), (-88.09226, 42.218779999999995), (-88.09185, 42.217659999999995), (-88.09176, 42.218779999999995), (-88.09138, 42.217659999999995), (-88.09127, 42.218779999999995), (-88.09094, 42.217620000000004), (-88.0907, 42.2188), (-88.09052, 42.21753), (-88.09005, 42.218709999999994), (-88.08998000000001, 42.2174), (-88.08957, 42.218309999999995), (-88.08889, 42.217290000000006), (-88.08830999999999, 42.21763)]
poly = Polygon(z)
pg=gpd.GeoDataFrame(index=[0], crs=crs, geometry=[poly])
geoplot.polyplot(pg)
and the result: view plot
The points are ordered by longitude and the function considers this ordering but it's irrelevant as long as the plotted result is clearly not a polygon.
A polygon can be, but isn't necessarily, a convex hull. In your case, you have a self-intersecting polygon, but a polygon none the less. If your goal is to compute a convex hull, you can use scipy.spatial.ConvexHull, which uses qhull to compute convex hulls
From the documentation:
from scipy.spatial import ConvexHull, convex_hull_plot_2d
points = np.random.rand(30, 2) # 30 random points in 2-D
hull = ConvexHull(points)
and
import matplotlib.pyplot as plt
plt.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
plt.plot(points[simplex, 0], points[simplex, 1], 'k-')
to plot.
The concept of a concave hull is less well defined. One possible definition are alpha shapes. These can be generated with the alphashape package. In fact, the documentation includes examples using geopandas:
import os
import geopandas
data = os.path.join(os.getcwd(), 'data', 'Public_Airports_March2018.shp')
gdf = geopandas.read_file(data)
import cartopy.crs as ccrs
gdf_proj = gdf.to_crs(ccrs.AlbersEqualArea().proj4_init)
gdf_proj.plot()
import alphashape
alpha_shape = alphashape.alphashape(gdf_proj)
alpha_shape.plot()
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.PlateCarree())
ax.scatter([p.x for p in gdf_proj['geometry']],
[p.y for p in gdf_proj['geometry']],
transform=ccrs.AlbersEqualArea())
ax.add_geometries(
alpha_shape['geometry'],
crs=ccrs.AlbersEqualArea(), alpha=.2)
plt.show()

Projection of 3D convex hull onto xy plane with a colour map

What I'd like at the end is the smoothed colour map with contours plotted on top of it. The idea is to preserve as much as possible information from the 3D convex hull.
The problem is that the code I developed so far doesn't work for all the inputs.
Example
If I set tricontourf() integer parameter let say to 8 and provide 10 input files I will get 8 plots which are OK but 2 will be solid colour.
Next if I change parameter to 9 I'll get 7 good and 3 odd. Some of the good ones from the first step are now wrong!
Ideally I'd like to have this parameter fixed at ~25 so the colour map is smoothed.
Have look at the pictures:
This is wrong, int parameter = 9
this is what I want but smoother, int parameter 8
What is important to me is to have triangulation based on the convex hull.
import matplotlib.pyplot as plt
import numpy as np
import sys, os, time, math
from scipy.spatial import ConvexHull
from matplotlib.tri import Triangulation
import matplotlib.cm as cm
# get covex hull data and save them to an array
cvx = []
dataX = []
for filename in sys.argv[1:]:
X = np.genfromtxt(filename,delimiter="", skip_header=2)
dataX.append(X)
hull = ConvexHull(X)
cvx.append(hull)
for idx,filename in enumerate(sys.argv[1:]):
# start plotting data
x, y, z = dataX[idx].T
# triangulation based on a convex hull
simpl = cvx[idx].simplices
tri = Triangulation(x, y, triangles=simpl)
# plot lines (triangles)
plt.triplot(tri, color='k')
# plot contour lines based on convex hull facets
plt.tricontour(x, y, z, 5, linewidths=0.5, colors='k', triangles=simpl)
# plot colour map
plt.tricontourf(x, y, z, 8, cmap=plt.cm.rainbow, triangles=simpl)
plt.show()

Delaunay triangularization of Polyhedron (Python)

I'm trying to get the Delaunay Triangulation of a polyhedron in python so that I can calculate the centroid. I see that there is a Delaunay function in scipy.spatial and that it works in n-dimensions. The trouble is that the documentation shows 2D use and gives me no indication of what to do with higher dimensions. Being able to decompose this object into an array would probably solve this issue for me, but I don't know how to do that.
The problem I'm running into is that I do not know how to verify that this is working correctly as it is outputting an object. I can find nothing on Google about how to graph a polyhedron or how to use this object that scipy is spitting back.
If I do
import numpy as np
from scipy.spatial import Delaunay
points = np.array([[0,0,0],[1,0,0],[1,1,0],[1,0,1],[1,1,1],[0,1,0],[0,1,1],[0,0,1]])
Delaunay(points)
I really would just like to be able to get back the coordinates of these tetrahedrons so that I can calculate the centroids of the polyhedrons. It would also be really nice if I were able to graph the tesselated polyhedron too. I saw in MATLAB that I can do this with a fuction called trimesn, and I found one from matplotlib but it seems to be really different and its documentation is not great.
from matplotlib.collections import TriMesh TriMesh.__doc__
u'\n Class for the efficient drawing of a triangular mesh using\n
Gouraud shading.\n\n A triangular mesh is a
:class:`~matplotlib.tri.Triangulation`\n object.\n '
What tess = Delaunay(pts) returns is an object of the Delanauy class. You can check the tetrahedrons as tess.simplices. It has different attributes and methods. In 2D, for example, it can plot you triangulation, convex hull and Voronoi tesselation.
Regarding the visualization of the final collection of tetrahedrons I didn't find a straightforward way of doing it, but I managed to get a working script. Check the code below.
from __future__ import division
import numpy as np
from scipy.spatial import Delaunay
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
from itertools import combinations
def plot_tetra(tetra, pts, color="green", alpha=0.1, lc="k", lw=1):
combs = combinations(tetra, 3)
for comb in combs:
X = pts[comb, 0]
Y = pts[comb, 1]
Z = pts[comb, 2]
verts = [zip(X, Y, Z)]
triangle = Poly3DCollection(verts, facecolors=color, alpha=0.1)
lines = Line3DCollection(verts, colors=lc, linewidths=lw)
ax.add_collection3d(triangle)
ax.add_collection3d(lines)
pts = np.array([
[0,0,0],
[1,0,0],
[1,1,0],
[1,0,1],
[1,1,1],
[0,1,0],
[0,1,1],
[0,0,1]])
tess = Delaunay(pts)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for k, tetra in enumerate(tess.simplices):
color = plt.cm.Accent(k/(tess.nsimplex - 1))
plot_tetra(tetra, pts, color=color, alpha=0.1, lw=0.5, lc="k")
ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], c='k')
plt.savefig("Delaunay.png", dpi=600)
plt.show()
The resulting image is
You do not need the Delaunay triangulation to compute the centroid of a polyhedron.
The centroid is a weighted sum of tetrahedra centroids, where the weight is the volume of each tetrahedron.
You do not need to partition the polyhedron into tetrahedra.
First, triangulate the faces of the polyhedron, i.e., quads get partitioned
into two coplanar triangles, etc.
Next, pick an arbitrary point p in space, say, the origin.
Now, for each triangle face (a,b,c), compute the signed volume of the tetrahedron
(p,a,b,c). This works if all triangles are oriented counterclockwise.
The signed volume takes care of everything via cancellation.
Use the signed volume as the weight
to multiply the tetrahedra centroids.
Tetrahedra signed volume is explained in Chapter 1 of my book,
"Computational Geometry in C."

ConvexHull not accurate enough - alternatives?

I have a cluster consistent of about 25k points and I want to find the borders. It works with ConvexHull, but the result is that I only get about 19 coordinates as output. This is definitely too few.
Here is the sample code from the SciPy documentation. If you run it you can see that the number of points is very limited.
from scipy.spatial import ConvexHull
import numpy as np
import matplotlib.pyplot as plt
points = np.random.rand(50, 2) # 30 random points in 2-D
hull = ConvexHull(points, incremental=False)
plt.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
plt.plot(points[simplex,0], points[simplex,1], 'r-')
plt.show()
Is it possible to get more points to increase the accuracy of the boarder? Or do I need a different code?
Well then your hull wouldn't be convex!
Try http://www.geosensor.net/papers/duckham08.PR.pdf for an algorithm that will attempt to get what you probably want, which is something that morally follows the "border" of the set of points.
You could also try alpha-shapes.

Plotting at boundaries using matplotlib-basemap

I have difficulties in plotting e.g. polygons across the boundaries of a map generated using matplotlib-basemap. In the example below, the map boundary is specified by the dateline. I try to plot a triangle across the dateline by specifying the coordinates of vertices of a triangle. This works fine when all coordinates are within the map, but if they go across the map boundary, basemap performs strange extrapolation, as it seems not to know how to draw the rectangles in the right way.
Right way would mean in my sense that the triangle is drawn until the map boundary and would then continue at the other side of the map.
Below is a minimum code example and a figure illustrating the general problem.
Any ideas how to solve this problem in a general way are highly welcome.
from mpl_toolkits.basemap import Basemap
import matplotlib.pylab as plt
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib as mpl
from matplotlib.collections import PatchCollection
![plt.close('all')
Path = mpath.Path
fig=plt.figure(); ax=fig.add_subplot(121); ax1=fig.add_subplot(122)
def do_plot(ax,lons,lats,title):
patches=\[\]
m = Basemap(projection='robin', resolution='c',lon_0=0.,ax=ax) #todo: how to make it properly work for other projections ???
m.drawmapboundary(fill_color='grey')
m.drawcoastlines()
#--- generate first sample with no problem
x,y=m(lons,lats)
verts = np.asarray(\[x,y\]).T
codes = \[Path.MOVETO,Path.LINETO,Path.LINETO\]
patches.append(mpatches.PathPatch(mpath.Path(verts, codes,closed=True)))
#--- generate collection
cmap = plt.cm.get_cmap('jet', 50); norm = mpl.colors.Normalize(vmin=None, vmax=None) #colorbar mapping
collection = PatchCollection(patches, cmap=cmap,norm=norm, alpha=1.,match_original=False) #construct library of all objects
colors = np.asarray(np.random.random(len(patches)))
collection.set_array(np.array(colors)) #assign data values here
#--- do actual plotting
im=m.ax.add_collection(collection)
ax.set_title(title)
do_plot(ax,\[-10.,0.,20.\],\[30.,50.,20.\],'This works')
do_plot(ax1,\[170,180,-175\],\[30.,50.,20.\],'... and here is the boundary problem')
plt.show()][1]
You cannot get around this problem with Basemap in a simple way. In your line x,y=m(lons,lats) you have transformed the points to map coordinates, and drawing the polygon just draws between those projected points.
You might try using Cartopy, which can do this. This example may help.

Categories

Resources