I have an array of longitude-latitude points that defines the boundaries of an area. I would like to create a polygon based on these points and plot the polygon on a map and fill it. Currently, my polygon seems to consist of many patches that connect all the points, but the order of the points is not correct and when I try to fill the polygon I get a weird looking area (see attached).
I sort my longitude-latitude points (mypolyXY array) according to the center of the polygon, but my guess is that this is not correct:
cent=(np.sum([p[0] for p in mypolyXY])/len(mypolyXY),np.sum([p[1] for p in mypolyXY])/len(mypolyXY))
# sort by polar angle
mypolyXY.sort(key=lambda p: math.atan2(p[1]-cent[1],p[0]-cent[0]))
I plot the point locations (black circles) and my polygons (colored patches) using
scatter([p[0] for p in mypolyXY],[p[1] for p in mypolyXY],2)
p = Polygon(mypolyXY,facecolor=colors,edgecolor='none')
ax.add_artist(p)
My question is: how can I close my polygon based on my array of longitude-latitude points?
UPDATE: I tested some more on how to plot the polygon. I removed the sort routine and just used the data in the order they occur in the file. This seems to improve the result, but as @tcaswell mentioned, the polygon shape still undercuts itself (see new plot). I am hoping that there could be a path/polygon routine that could solve my problem and sort of merge all shapes or paths within the boundaries of the polygon. Suggestions are very welcome.
UPDATE 2:
I have now a working version of my script that is based on suggestions by @Rutger Kassies and Roland Smith. I ended up reading the Shapefile using org which worked relatively well. It worked well for the standard lmes_64.shp file but when I used more detailed LME files where each LME could consist of several polygons this script broke down. I would have to find a way to merge the various polygons for identical LME names to make that work. I attach my script I ended up with in case anyone would take a look at it. I very much appreciate comments for how to improve this script or to make it more generic. This script creates the polygons and extracts data within the polygon region that I read from a netcdf file. The grid of the input file is -180 to 180 and -90 to 90.
import numpy as np
import math
from pylab import *
import matplotlib.patches as patches
import string, os, sys
import datetime, types
from netCDF4 import Dataset
import matplotlib.nxutils as nx
from mpl_toolkits.basemap import Basemap
import ogr
import matplotlib.path as mpath
import matplotlib.patches as patches
def getLMEpolygon(coordinatefile,mymap,index,first):
ds = ogr.Open(coordinatefile)
lyr = ds.GetLayer(0)
numberOfPolygons=lyr.GetFeatureCount()
if first is False:
ft = lyr.GetFeature(index)
print "Found polygon:", ft.items()['LME_NAME']
geom = ft.GetGeometryRef()
codes = []
all_x = []
all_y = []
all_XY= []
if (geom.GetGeometryType() == ogr.wkbPolygon):
for i in range(geom.GetGeometryCount()):
r = geom.GetGeometryRef(i)
x = [r.GetX(j) for j in range(r.GetPointCount())]
y = [r.GetY(j) for j in range(r.GetPointCount())]
codes += [mpath.Path.MOVETO] + (len(x)-1)*[mpath.Path.LINETO]
all_x += x
all_y += y
all_XY +=mymap(x,y)
if len(all_XY)==0:
all_XY=None
mypoly=None
else:
mypoly=np.empty((len(all_XY[:][0]),2))
mypoly[:,0]=all_XY[:][0]
mypoly[:,1]=all_XY[:][3]
else:
print "Will extract data for %s polygons"%(numberOfPolygons)
mypoly=None
first=False
return mypoly, first, numberOfPolygons
def openCMIP5file(CMIP5name,myvar,mymap):
if os.path.exists(CMIP5name):
myfile=Dataset(CMIP5name)
print "Opened CMIP5 file: %s"%(CMIP5name)
else:
print "Could not find CMIP5 input file %s : abort"%(CMIP5name)
sys.exit()
mydata=np.squeeze(myfile.variables[myvar][-1,:,:]) - 273.15
lonCMIP5=np.squeeze(myfile.variables["lon"][:])
latCMIP5=np.squeeze(myfile.variables["lat"][:])
lons,lats=np.meshgrid(lonCMIP5,latCMIP5)
lons=lons.flatten()
lats=lats.flatten()
mygrid=np.empty((len(lats),2))
mymapgrid=np.empty((len(lats),2))
for i in xrange(len(lats)):
mygrid[i,0]=lons[i]
mygrid[i,1]=lats[i]
X,Y=mymap(lons[i],lats[i])
mymapgrid[i,0]=X
mymapgrid[i,1]=Y
return mydata, mygrid, mymapgrid
def drawMap(NUM_COLORS):
ax = plt.subplot(111)
cm = plt.get_cmap('RdBu')
ax.set_color_cycle([cm(1.*j/NUM_COLORS) for j in range(NUM_COLORS)])
mymap = Basemap(resolution='l',projection='robin',lon_0=0)
mymap.drawcountries()
mymap.drawcoastlines()
mymap.fillcontinents(color='grey',lake_color='white')
mymap.drawparallels(np.arange(-90.,120.,30.))
mymap.drawmeridians(np.arange(0.,360.,60.))
mymap.drawmapboundary(fill_color='white')
return ax, mymap, cm
"""Edit the correct names below:"""
LMEcoordinatefile='ShapefileBoundaries/lmes_64.shp'
CMIP5file='tos_Omon_CCSM4_rcp85_r1i1p1_200601-210012_regrid.nc'
mydebug=False
doPoints=False
first=True
"""initialize the map:"""
mymap=None
mypolyXY, first, numberOfPolygons = getLMEpolygon(LMEcoordinatefile, mymap, 0,first)
NUM_COLORS=numberOfPolygons
ax, mymap, cm = drawMap(NUM_COLORS)
"""Get the CMIP5 data together with the grid"""
SST,mygrid, mymapgrid = openCMIP5file(CMIP5file,"tos",mymap)
"""For each LME of interest create a polygon of coordinates defining the boundaries"""
for counter in xrange(numberOfPolygons-1):
mypolyXY,first,numberOfPolygons = getLMEpolygon(LMEcoordinatefile, mymap,counter,first)
if mypolyXY != None:
"""Find the indices inside the grid that are within the polygon"""
insideBoolean = plt.mlab.inside_poly(np.c_[mymapgrid[:,0],mymapgrid[:,1]],np.c_[mypolyXY[:,0],mypolyXY[:,1]])
SST=SST.flatten()
SST=np.ma.masked_where(SST>50,SST)
mymapgrid=np.c_[mymapgrid[:,0],mymapgrid[:,1]]
myaverageSST=np.mean(SST[insideBoolean])
mycolor=cm(myaverageSST/SST.max())
scaled_z = (myaverageSST - SST.min()) / SST.ptp()
colors = plt.cm.coolwarm(scaled_z)
scatter([p[0] for p in mypolyXY],[p[1] for p in mypolyXY],2)
p = Polygon(mypolyXY,facecolor=colors,edgecolor='none')
ax.add_artist(p)
if doPoints is True:
for point in xrange(len(insideBoolean)):
pointX=mymapgrid[insideBoolean[point],0]
pointY=mymapgrid[insideBoolean[point],1]
ax.scatter(pointX,pointY,8,color=colors)
ax.hold(True)
if doPoints is True:
colorbar()
print "Extracted average values for %s LMEs"%(numberOfPolygons)
plt.savefig('LMEs.png',dpi=300)
plt.show()
Final image attached. Thanks for all help.
Cheers, Trond