I am working in Shapely and have come across a simple issue that I have spent too long trying to figure out.
The code below starts with 6 points, buffers them into circular polygons, unions and polygonizes them to find overlap. Each overlapping region becomes its own polygon and can be colored independently.
What I'm trying to do next is plot the centroids of all of the polygons in the result object. I calculate the centroids with
centroidCoords = poly.centroid.wkt
which gives the points in this format:
POINT (7.210123936676805 -0.0014481952154823)
POINT (5.817327756517152 -1.0513260084561042)
POINT (5.603133733696165 -2.7765635631249412)
...
They say they are points, but they're actually strings and I can't figure out how to convert them to points, like: (7.210123936676805, -0.0014481952154823). I've tried converting them to tuples but ran into errors. I have found lots of suggestions using geopandas, but I am having trouble getting geopandas to install on my machine. I don't need the coordinates changed to lat/long or anything, can't I just plot them ?! They're so close!
Here's the rest of the code. Your help is greatly appreciated.
import matplotlib.pyplot as plt
from shapely.geometry import Point, LineString
from shapely.ops import unary_union, polygonize
from matplotlib.pyplot import cm
import numpy as np
def plot_coords(coords, color):
pts = list(coords)
x, y = zip(*pts)
# print(color)
plt.plot(x,y, color=color)
plt.fill_between(x, y, facecolor=color)
def plot_polys(polys, colors):
for poly, color in zip(polys, colors):
plot_coords(poly.exterior.coords, color)
points = [Point(6, 0),
Point(5, 1.72),
Point(3, 1.73),
Point(2, 0),
Point(3, -1.73),
Point(5, -1.73)]
# buffer points to create circle polygons
circles = []
for point in points:
circles.append(point.buffer(2))
# unary_union and polygonize to find overlaps
rings = [LineString(list(pol.exterior.coords)) for pol in circles]
union = unary_union(rings)
result = [geom for geom in polygonize(union)]
# plot resulting polygons
colors = cm.rainbow(np.linspace(0, 1, len(result)))
centroidCoords = []
for poly in result:
centroidCoords = poly.centroid.wkt
# print(centroidCoords)
plot_polys(result, colors)
plt.show()
Related
I've tried to recreate the image attached using cmaps as well as with if/else statements.
My current attempt is based upon the advice given in this thread
I tried using 1.8<=x<=2.2 but I get an error.
Here is my current code below:
import numpy as np
import matplotlib.pyplot as plt
N = 500
# center, variation, number of points
x = np.random.normal(2,0.2,N)
y = np.random.normal(2,0.2,N)
colors = np.where(x<=2.2,'r',np.where(y<=2.2,'b','b'))
plt.scatter(x , y, c=colors)
plt.colorbar()
plt.show()
To make that plot, you need to pass an array with the color of each point. In this case the color is the distance to the point (2, 2), since the distributions are centered on that point.
import numpy as np
import matplotlib.pyplot as plt
N = 500
# center, variation, number of points
x = np.random.normal(2,0.2,N)
y = np.random.normal(2,0.2,N)
# we calculate the distance to (2, 2).
# This we are going to use to give it the color.
color = np.sqrt((x-2)**2 + (y-2)**2)
plt.scatter(x , y, c=color, cmap='plasma', alpha=0.7)
# we set a alpha
# it is what gives the transparency to the points.
# if they suppose themselves, the colors are added.
plt.show()
I'm testing geopandas to make something quite simple : use the difference method to delete some points of a GeoDataFrame that are inside a circle.
Here's the begining of my script :
%matplotlib inline
# previous line is because I used ipynb
import pandas as pd
import geopandas as gp
from shapely.geometry import Point
[...]
points_df = gp.GeoDataFrame(csv_file, crs=None, geometry=geometry)
Here's the first rows of points_df :
Name Adress geometry
0 place1 street1 POINT (6.182674 48.694416)
1 place2 street2 POINT (6.177306 48.689889)
2 place3 street3 POINT (6.18 48.69600000000001)
3 place4 street4 POINT (6.1819 48.6938)
4 place5 street5 POINT (6.175694 48.690833)
Then, I add a point that will contain several points of the first GeoDF :
base = points_df.plot(marker='o', color='red', markersize=5)
center_coord = [Point(6.18, 48.689900)]
center = gp.GeoDataFrame(crs=None, geometry=center_coord)
center.plot(ax=base, color = 'blue',markersize=5)
circle = center.buffer(0.015)
circle.plot(ax=base, color = 'green')
Here's the result displayed by the iPython notebook :
Now, the goal is to delete red points inside the green circle. To do that, I thought that difference method will be enough. But when I write :
selection = points_df['geometry'].difference(circle)
selection.plot(color = 'green', markersize=5)
The result is that... nothing changed with points_df :
I guess that the difference() method works only with polygons GeoDataFrames and the mix between points and polygons is not posible. But maybe I missed something !
Will a function to test the presence of a point in the circle be better than the difference method in this case ?
I guess that the difference() method works only with polygons
GeoDataFrames and the mix between points and polygons is not posible.
That seems to be the issue, you cant use the overlay with points.
And also for that kind of spatial operation a simple spatial join seems to be the easiest solution.
Starting with the last example ;):
%matplotlib inline
import pandas as pd
import geopandas as gp
import numpy as np
import matplotlib.pyplot as plt
from shapely.geometry import Point
# Create Fake Data
df = pd.DataFrame(np.random.randint(10,20,size=(35, 3)), columns=['Longitude','Latitude','data'])
# create Geometry series with lat / longitude
geometry = [Point(xy) for xy in zip(df.Longitude, df.Latitude)]
df = df.drop(['Longitude', 'Latitude'], axis = 1)
# Create GeoDataFrame
points = gp.GeoDataFrame(df, crs=None, geometry=geometry)
# Create Matplotlib figure
fig, ax = plt.subplots()
# Set Axes to equal (otherwise plot looks weird)
ax.set_aspect('equal')
# Plot GeoDataFrame on Axis ax
points.plot(ax=ax,marker='o', color='red', markersize=5)
# Create new point
center_coord = [Point(15, 13)]
center = gp.GeoDataFrame(crs=None, geometry=center_coord)
# Plot new point
center.plot(ax=ax,color = 'blue',markersize=5)
# Buffer point and plot it
circle = gp.GeoDataFrame(crs=None, geometry=center.buffer(2.5))
circle.plot(color = 'white',ax=ax)
Leaves us with the problem on how to determine if a point is inside or outside of the polygon... one way of achieving that is to Join all points inside the polygon, and create a DataFrame with the difference between all points and points within the circle:
# Calculate the points inside the circle
pointsinside = gp.sjoin(points,circle,how="inner")
# Now the points outside the circle is just the difference
# between points and points inside (see the ~)
pointsoutside = points[~points.index.isin(pointsinside.index)]
# Create a nice plot
fig, ax = plt.subplots()
ax.set_aspect('equal')
circle.plot(color = 'white',ax=ax)
center.plot(ax=ax,color = 'blue',markersize=5)
pointsinside.plot(ax=ax,marker='o', color='green', markersize=5)
pointsoutside.plot(ax=ax,marker='o', color='yellow', markersize=5)
print('Total points:' ,len(points))
print('Points inside circle:' ,len(pointsinside))
print('Points outside circle:' ,len(pointsoutside))
Total points: 35
Points inside circle: 10
Points outside circle: 25
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()
I have an OBJ File Generated by Meshlab with Vertices and Faces data.
In MATLAB i used the function ''patch'' with Vertices data in 1 array (5937x3) and Faces (11870x3) data in another and the result is this:
Simplified version of the code
[V,F] = read_vertices_and_faces_from_obj_file(filename);
patch('Vertices',V,'Faces',F,'FaceColor','r','LineStyle','-')
axis equal
Result
The question is,how can I do that in Python ? There's a simple way like in Matlab??
I'll really appreciate any help.
Your best bet would be to make use of the mplot3d toolkit from the matplotlib library.
A similar question was asked here. Perhaps this slightly edited code excerpt from that question will help you.
The Code:
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.pyplot as plt
fig = plt.figure()
ax = Axes3D(fig)
# Specify 4 vertices
x = [0,1,1,0] # Specify x-coordinates of vertices
y = [0,0,1,1] # Specify y-coordinates of vertices
z = [0,1,0,1] # Specify z-coordinates of vertices
verts = [zip(x, y, z)] # [(0,0,0), (1,0,1), (1,1,0), (0,1,1)]
tri = Poly3DCollection(verts) # Create polygons by connecting all of the vertices you have specified
tri.set_color(colors.rgb2hex(sp.rand(3))) # Give the faces random colors
tri.set_edgecolor('k') # Color the edges of every polygon black
ax.add_collection3d(tri) # Connect polygon collection to the 3D axis
plt.show()
I want to use imshow (for example) to display some data inside the boundaries of a country (for the purposes of example I chose the USA) The simple example below illustrates what I want:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
data = np.arange(100).reshape(10, 10)
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(data)
poly = RegularPolygon([ 0.5, 0.5], 6, 0.4, fc='none',
ec='k', transform=ax.transAxes)
im.set_clip_path(poly)
ax.add_patch(poly)
ax.axis('off')
plt.show()
The result is:
Now I want to do this but instead of a simple polygon, I want to use the complex shape of the USA. I have created some example data contained in the array of "Z" as can be seen in the code below. It is this data that I want to display, using a colourmap but only within the boundaries of mainland USA.
So far I have tried the following. I get a shape file from here contained in "nationp010g.shp.tar.gz" and I use the Basemap module in python to plot the USA. Note that this is the only method I have found which gives me the ability get a polygon of the area I need. If there are alternative methods I would also be interested in them. I then create a polygon called "mainpoly" which is almost the polygon I want coloured in blue:
Notice how only one body has been coloured, all other disjoint polygons remain white:
So the area coloured blue is almost what I want, note that there are unwanted borderlines near canada because the border actually goes through some lakes, but that is a minor problem. The real problem is, why doesn't my imshow data display inside the USA? Comparing my first and second example codes I can't see why I don't get a clipped imshow in my second example, the way I do in the first. Any help would be appreciated in understanding what I am missing.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap as Basemap
from matplotlib.patches import Polygon
# Lambert Conformal map of lower 48 states.
m = Basemap(llcrnrlon=-119,llcrnrlat=22,urcrnrlon=-64,urcrnrlat=49,
projection='lcc',lat_1=33,lat_2=45,lon_0=-95)
shp_info = m.readshapefile('nationp010g/nationp010g', 'borders', drawbounds=True) # draw country boundaries.
for nshape,seg in enumerate(m.borders):
if nshape == 1873: #This nshape denotes the large continental body of the USA, which we want
mainseg = seg
mainpoly = Polygon(mainseg,facecolor='blue',edgecolor='k')
nx, ny = 10, 10
lons, lats = m.makegrid(nx, ny) # get lat/lons of ny by nx evenly space grid.
x, y = m(lons, lats) # compute map proj coordinates.
Z = np.zeros((nx,ny))
Z[:] = np.NAN
for i in np.arange(len(x)):
for j in np.arange(len(y)):
Z[i,j] = x[0,i]
ax = plt.gca()
im = ax.imshow(Z, cmap = plt.get_cmap('coolwarm') )
im.set_clip_path(mainpoly)
ax.add_patch(mainpoly)
plt.show()
Update
I realise that the line
ax.add_patch(mainpoly)
does not even add the polygon shape to a plot. Am I not using it correctly? As far as I know mainpoly was calculated correctly using the Polygon() method. I checked that the coordinate inputs are a sensible:
plt.plot(mainseg[:,0], mainseg[:,1] ,'.')
which gives
I have also considered about this problem for so long.
And I found NCL language has the function to mask the data outside some border.
Here is the example:
http://i5.tietuku.com/bdb1a6c007b82645.png
The contourf plot only show within China border. Click here for the code.
I know python has a package called PyNCL which support all NCL code in Python framework.
But I really want to plot this kind of figure using basemap. If you have figured it out, please post on the internet. I'll learn at the first time.
Thanks!
Add 2016-01-16
In a way, I have figured it out.
This is my idea and code, and it's inspired from this question I have asked today.
My method:
1. Make the shapefile of the interesting area(like U.S) into shapely.polygon.
2. Test each value point within/out of the polygon.
3. If the value point is out of the study area, mask it as np.nan
Intro
* the polygon xxx was a city in China in ESRI shapefile format.
* fiona, shapely package were used here.
# generate the shapely.polygon
shape = fiona.open("xxx.shp")
pol = shape.next()
geom = shape(pol['geometry'])
poly_data = pol["geometry"]["coordinates"][0]
poly = Polygon(poly_data)
It shows like:
http://i4.tietuku.com/2012307faec02634.png
### test the value point
### generate the grid network which represented by the grid midpoints.
lon_med = np.linspace((xi[0:2].mean()),(xi[-2:].mean()),len(x_grid))
lat_med = np.linspace((yi[0:2].mean()),(yi[-2:].mean()),len(y_grid))
value_test_mean = dsu.mean(axis = 0)
value_mask = np.zeros(len(lon_med)*len(lat_med)).reshape(len(lat_med),len(lon_med))
for i in range(0,len(lat_med),1):
for j in range(0,len(lon_med),1):
points = np.array([lon_med[j],lat_med[i]])
mask = np.array([poly.contains(Point(points[0], points[1]))])
if mask == False:
value_mask[i,j] = np.nan
if mask == True:
value_mask[i,j] = value_test_mean[i,j]
# Mask the np.nan value
Z_mask = np.ma.masked_where(np.isnan(so2_mask),so2_mask)
# plot!
fig=plt.figure(figsize=(6,4))
ax=plt.subplot()
map = Basemap(llcrnrlon=x_map1,llcrnrlat=y_map1,urcrnrlon=x_map2,urcrnrlat=y_map2)
map.drawparallels(np.arange(y_map1+0.1035,y_map2,0.2),labels= [1,0,0,1],size=14,linewidth=0,color= '#FFFFFF')
lon_grid = np.linspace(x_map1,x_map2,len(x_grid))
lat_grid = np.linspace(y_map1,y_map2,len(y_grid))
xx,yy = np.meshgrid(lon_grid,lat_grid)
pcol =plt.pcolor(xx,yy,Z_mask,cmap = plt.cm.Spectral_r ,alpha =0.75,zorder =2)
result
http://i4.tietuku.com/c6620c5b6730a5f0.png
http://i4.tietuku.com/a22ad484fee627b9.png
original result
http://i4.tietuku.com/011584fbc36222c9.png