Python Basemap Inset Bug - python

I am attempting to create two Basemaps, the second of which is inside an inset. I can plot just fine inside the second Basemap, however fillcontinents, drawcoastlines, and drawstates are seemingly ignored at the expense of setting the inset xlim and ylim. Oddly enough, drawmapboundary works fine. If the xlim and ylim arguments for the inset are commented out, Basemap functions are plotted properly, however mark_inset is not in the correct location. I followed the example by Basemap (http://basemaptutorial.readthedocs.io/en/latest/locator.html) and tried changing my map projection from lcc to cyl (as in their example). For some reason, plotting lines and Basemap functions works correctly, which leads me to believe there is some bug associated with the map projection. Any help would be really appreciated!
from IPython import get_ipython
get_ipython().magic('reset -sf')
import matplotlib.pyplot as P
from pylab import * ## import scientific database
close("all") ## close all windows
import netCDF4
import numpy as np
from mpl_toolkits.basemap import Basemap
from matplotlib.font_manager import FontProperties
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
# Main Basemap lat/lon boundaries
ll_lon = -131.4
ll_lat = 22.8
ur_lon = -104.1
ur_lat = 44.1
# Center point of map
ref_lat = (ll_lat + ur_lat)/2.
ref_lon = -(abs(ll_lon) + abs(ur_lon))/2.
# Create first Basemap
fig = P.figure(figsize=(20,15))
m = Basemap(llcrnrlon=ll_lon,llcrnrlat=ll_lat,urcrnrlon=ur_lon,urcrnrlat=ur_lat,lon_0=ref_lon,lat_0=ref_lat,projection='lcc',resolution='h')
# Define corners for drawing inner domain
ll_x = 591679.34684650064
ul_x = 594344.51484522165
ll_y = 592839.05223913607
ul_y = 1806478.1475523338
ur_x = 1857338.6486264155
lr_x = 1807752.1722138769
ur_y = 1779256.3653244921
lr_y = 566631.12743243692
# Plot inner domain
m.plot([ll_x,ul_x],[ll_y,ul_y],linewidth=3,color='k')
m.plot([ul_x,ur_x],[ul_y,ur_y],linewidth=3,color='k')
m.plot([ur_x,lr_x],[ur_y,lr_y],linewidth=3,color='k')
m.plot([lr_x,ll_x],[lr_y,ll_y],linewidth=3,color='k')
# Customize map
m.drawmapboundary(fill_color='aqua')
m.fillcontinents(color='coral',lake_color='aqua')
m.drawcoastlines(linewidth=1.5)
m.drawparallels(np.arange(20.,55.,5.),labels=[True,False,False,False],fontsize=18)
m.drawmeridians(np.arange(-145.,-95.,5.),labels=[False,False,False,True],fontsize=18)
m.drawstates(linewidth=1.0)
m.drawcountries(linewidth=1.0)
##################################
########## Add inset #############
##################################
# Define new axis for inset
ax = fig.add_subplot(111)
axins = zoomed_inset_axes(ax, 5, loc=4)
# Secondary Basemap lat/lon boundaries
ll_lata = 33.374725341796875
ur_lata = 35.076236724853516
ll_lona = -121.02678680419922
ur_lona = -118.92620086669922
# Center point of map
ref_lat = (ll_lata + ur_lata)/2.
ref_lon = -(abs(ll_lona) + abs(ur_lona))/2.
# Create second Basemap
m2 = Basemap(llcrnrlon=ll_lona,llcrnrlat=ll_lata,urcrnrlon=ur_lona,urcrnrlat=ur_lata,lon_0=ref_lon,lat_0=ref_lat,ax=axins)
# Define corners for drawing inner domain
ll_x = 1180454.8544887367
ul_x = 1181076.4843945887
ll_y = 1172180.6247499525
ul_y = 1202324.6929654693
ur_x = 1245128.2633578097
lr_x = 1244450.5310503689
ur_y = 1200944.1870238895
lr_y = 1170801.3279816376
# Plot inner domain
m2.plot([ll_x,ul_x],[ll_y,ul_y],linewidth=3,color='k')
m2.plot([ul_x,ur_x],[ul_y,ur_y],linewidth=3,color='k')
m2.plot([ur_x,lr_x],[ur_y,lr_y],linewidth=3,color='k')
m2.plot([lr_x,ll_x],[lr_y,ll_y],linewidth=3,color='k')
# Define inset parameters
mark_inset(ax,axins,loc1=1,loc2=3,lw=2,fc="none")
# Set xlim and ylim for mark_inset
ll_x = 1114487.9924679566
ul_x = 1117995.9771456306
ll_y = 1094044.0480909257
ul_y = 1283251.5520485893
ur_x = 1311637.0004658864
lr_x = 1306989.5479092139
ur_y = 1279078.0132717683
lr_y = 1089895.0682288238
axins.set_xlim((ll_x+ul_x)/2.,(lr_x+ur_x)/2.)
axins.set_ylim((ll_y+lr_y)/2.,(ul_y+ur_y)/2.)
# Customize map --> this appears to be ignored with the exception of drawmapboundary
m2.drawmapboundary(fill_color='aqua')
m2.fillcontinents(color='coral',lake_color='aqua')
m2.drawcoastlines(linewidth=1.5)
m2.drawstates(linewidth=1.0)
P.show()

Related

matplotlib custom path markers not displaying correctly

just wondering if anybody has experience with matplotlib custom markers
I want each marker in my plot to be a pie chart. To achieve this, my strategy was to create custom markers using the path class, method wedge.
https://matplotlib.org/stable/api/path_api.html
However is not displaying correctly, in particular with wedges defined with angles in the left quadrants. However, the path defined by the wedge class method seems to be correct and wedges are displayed correctly if using PathPatch and .add_patch()
See example below
import numpy as np
import math
import matplotlib.path as mpath
import matplotlib.cm
import matplotlib.pyplot as plt
import matplotlib.patches as patches
#Create wedges from angles
angles = np.array( [0,140,160,360] ) #Wedges angles
wedges=[]
for i in range(len(angles)-1):
angle0= angles[i]
angle1= angles[i+1]
dangle = angle1-angle0
wedge0=None
if dangle>0:
wedge0= mpath.Path.wedge(angle0, angle1)
wedge0= mpath.Path.wedge(angle0, angle1)
wedges.append(wedge0)
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax1.set_xlim(-1, 1)
ax1.set_ylim(-1, 1)
ax2 = fig.add_subplot(122)
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
tab10 = matplotlib.cm.get_cmap('tab10')
for i, w0 in enumerate(wedges):
ax1.scatter(0,0, marker=w0, c = [tab10(i)], s=20000) #Use path markers
patch = patches.PathPatch(w0, color=tab10(i)) #Use patch
ax2.add_patch(patch)
plt.show()
Notice that the wedge on the left plot is sticking out, which is not supposed to.
Is this a bug in the matplotlib markers' code?
I managed to get the pie charts to display correctly.
Scaling by doing affine transforms does not help because the path markaers are all resized, as in
line 495 of markers.py .
def _set_custom_marker(self, path):
rescale = np.max(np.abs(path.vertices)) # max of x's and y's.
self._transform = Affine2D().scale(0.5 / rescale)
self._path = path
My solution is to modify the vertices in the created wedges by inserting new vertices that define a bounding box, slightly larger than the circle with radius 1.
Here is the modified code
import numpy as np
import matplotlib.path as mpath
import matplotlib.cm
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def getBoundedWedge(angle0, angle1):
wedge0= mpath.Path.wedge(angle0, angle1)
#print(f"wedge0:{wedge0}")
vertices = wedge0.vertices
codes = wedge0.codes
#Add ghost vertices to define bounding box
vertices= np.insert( vertices, 0, [[1.1,1.1], [-1.1,1.1] , [-1.1,-1.1], [1.1,-1.1]] , axis=0)
codes = np.insert( codes, 0, [1,1,1,1])
wedgeextra = mpath.Path(vertices, codes)
return wedgeextra
#Create wedges from angles
angles = np.array( [0,140,160,360] ) #Wedges angles
wedges=[]
for i in range(len(angles)-1):
angle0= angles[i]
angle1= angles[i+1]
dangle = angle1-angle0
wedge0=None
if dangle>0:
wedge0= getBoundedWedge(angle0, angle1)
wedges.append(wedge0)
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
ax1.set_xlim(-1, 1)
ax1.set_ylim(-1, 1)
ax2 = fig.add_subplot(122)
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
tab10 = matplotlib.cm.get_cmap('tab10')
for i, w0 in enumerate(wedges):
ax1.scatter(0,0, marker=w0, c = [tab10(i)], s=20000) #Use path markers
patch = patches.PathPatch(w0, color=tab10(i)) #Use patch
ax2.add_patch(patch)
plt.show()
And the output is as follows

How to use geopandas to plot latitude and longitude on a more detailed map with by using basemaps?

I am trying to plot some latitude and longitudes on the map of delhi which I am able to do by using a shape file in python3.8 using geopandas
Here is the link for the shape file:
https://drive.google.com/file/d/1CEScjlcsKFCgdlME21buexHxjCbkb3WE/view?usp=sharing
Following is my code to plot points on the map:
lo=[list of longitudes]
la=[list of latitudes]
delhi_map = gpd.read_file(r'C:\Users\Desktop\Delhi_Wards.shp')
fig,ax = plt.subplots(figsize = (15,15))
delhi_map.plot(ax = ax)
geometry = [Point(xy) for xy in zip(lo,la)]
geo_df = gpd.GeoDataFrame(geometry = geometry)
print(geo_df)
g = geo_df.plot(ax = ax, markersize = 20, color = 'red',marker = '*',label = 'Delhi')
plt.show()
Following is the result:
Now this map is not very clear and anyone will not be able to recognise the places marked so i tried to use basemap for a more detailed map through the following code:
df = gpd.read_file(r'C:\Users\Jojo\Desktop\Delhi_Wards.shp')
new_df = df.to_crs(epsg=3857)
print(df.crs)
print(new_df.crs)
ax = new_df.plot()
ctx.add_basemap(ax)
plt.show()
And following is the result:
I am getting the basemap but my shapefile is overlapping it. Can i get a map to plot my latitudes and longitudes where the map is much more detailed with names of places or roads or anything similar to it like in google maps or even something like the map which is being overlapped by the blue shapefile map?
Is it possible to plot on a map like this??
https://www.researchgate.net/profile/P_Jops/publication/324715366/figure/fig3/AS:618748771835906#1524532611545/Map-of-Delhi-reproduced-from-Google-Maps-12.png
use zorder parameter to adjust the layers' orders (lower zorder means lower layer), and alpha to the polygon. anyway, I guess, you're plotting df twice, that's why it's overlapping.
here's my script and the result
import geopandas as gpd
import matplotlib.pyplot as plt
import contextily as ctx
from shapely.geometry import Point
long =[77.2885437011719, 77.231931, 77.198767, 77.2750396728516]
lat = [28.6877899169922, 28.663863, 28.648287, 28.5429172515869]
geometry = [Point(xy) for xy in zip(long,lat)]
wardlink = "New Folder/wards delimited.shp"
ward = gpd.read_file(wardlink, bbox=None, mask=None, rows=None)
geo_df = gpd.GeoDataFrame(geometry = geometry)
ward.crs = {'init':"epsg:4326"}
geo_df.crs = {'init':"epsg:4326"}
# plot the polygon
ax = ward.plot(alpha=0.35, color='#d66058', zorder=1)
# plot the boundary only (without fill), just uncomment
#ax = gpd.GeoSeries(ward.to_crs(epsg=3857)['geometry'].unary_union).boundary.plot(ax=ax, alpha=0.5, color="#ed2518",zorder=2)
ax = gpd.GeoSeries(ward['geometry'].unary_union).boundary.plot(ax=ax, alpha=0.5, color="#ed2518",zorder=2)
# plot the marker
ax = geo_df.plot(ax = ax, markersize = 20, color = 'red',marker = '*',label = 'Delhi', zorder=3)
ctx.add_basemap(ax, crs=geo_df.crs.to_string(), source=ctx.providers.OpenStreetMap.Mapnik)
plt.show()
I don't know about google maps being in the contextily, I don't think it's available. alternatively, you can use OpenStreetMap base map which shows quite the same toponym, or any other basemap you can explore. use `source` keyword in the argument, for example, `ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik)` . here's how to check the available providers and the map each providers provides:
>>> ctx.providers.keys()
dict_keys(['OpenStreetMap', 'OpenSeaMap', 'OpenPtMap', 'OpenTopoMap', 'OpenRailwayMap', 'OpenFireMap', 'SafeCast', 'Thunderforest', 'OpenMapSurfer', 'Hydda', 'MapBox', 'Stamen', 'Esri', 'OpenWeatherMap', 'HERE', 'FreeMapSK', 'MtbMap', 'CartoDB', 'HikeBike', 'BasemapAT', 'nlmaps', 'NASAGIBS', 'NLS', 'JusticeMap', 'Wikimedia', 'GeoportailFrance', 'OneMapSG'])
>>> ctx.providers.OpenStreetMap.keys()
dict_keys(['Mapnik', 'DE', 'CH', 'France', 'HOT', 'BZH'])
I don't know geopandas. The idea I'm suggesting uses only basic python and matplotlib. I hope you can adapt it to your needs.
The background is the following map. I figured out the GPS coordinates of its corners using google-maps.
The code follows the three points of my remark. Note that the use of imread and imshow reverses the y coordinate. This is why the function coordinatesOnFigur looks non-symmetrical in x and y.
Running the code yields the map with a red bullet near Montijo (there is a small test at the end).
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import patches
from matplotlib.widgets import Button
NE = (-8.9551, 38.8799)
SE = (-8.9551, 38.6149)
SW = (-9.4068, 38.6149)
NW = (-9.4068, 38.8799)
fig = plt.figure(figsize=(8, 6))
axes = fig.add_subplot(1,1,1, aspect='equal')
img_array = plt.imread("lisbon_2.jpg")
axes.imshow(img_array)
xmax = axes.get_xlim()[1]
ymin = axes.get_ylim()[0] # the y coordinates are reversed, ymax=0
# print(axes.get_xlim(), xmax)
# print(axes.get_ylim(), ymin)
def coordinatesOnFigure(long, lat, SW=SW, NE=NE, xmax=xmax, ymin=ymin):
px = xmax/(NE[0]-SW[0])
qx = -SW[0]*xmax/(NE[0]-SW[0])
py = -ymin/(NE[1]-SW[1])
qy = NE[1]*ymin/(NE[1]-SW[1])
return px*long + qx, py*lat + qy
# plotting a red bullet that corresponds to a GPS location on the map
x, y = coordinatesOnFigure(-9, 38.7)
print("test: on -9, 38.7 we get", x, y)
axes.scatter(x, y, s=40, c='red', alpha=0.9)
plt.show()

Dynamic Visualisation of Global Plots

I have produced 17 global plots that show the decadal averages in maximum surface ozone from 1850-2015. Rather than plotting them individually, I wish to create an animation that cycles through them (almost like a gif), i.e. have the same coastlines, axes and colour bar throughout but change what is being plotted as the contour.
Any help on how to adapt my code to do this would be greatly appreciated - thank you in advance!!
import numpy as np
import netCDF4 as n4
import matplotlib.pyplot as plt
from matplotlib import colorbar, colors
import matplotlib.cm as cm
import cartopy as cart
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.feature as cfeature
nc = n4.Dataset('datafile.nc','r')
# daily maximum O3 VMR (units: mol mol-1)
sfo3max = nc.variables['sfo3max']
lon = nc.variables['lon'] # longitude
lat = nc.variables['lat'] # latitude
# (I manipulate the data to produce 17 arrays containing the decadal average O3 VMR which are
# listed below in sfo3max_avg)
sfo3max_avg = [sfo3max_1850_1860_avg, sfo3max_1860_1870_avg, sfo3max_1870_1880_avg,
sfo3max_1880_1890_avg, sfo3max_1890_1900_avg, sfo3max_1900_1910_avg,
sfo3max_1910_1920_avg, sfo3max_1920_1930_avg, sfo3max_1930_1940_avg,
sfo3max_1940_1950_avg, sfo3max_1950_1960_avg, sfo3max_1960_1970_avg,
sfo3max_1970_1980_avg, sfo3max_1980_1990_avg, sfo3max_1990_2000_avg,
sfo3max_2000_2010_avg, sfo3max_2010_2015_avg]
# find overall min & max values for colour bar in plots
min_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_min = np.amin(i)
min_sfo3max_avg = np.append(min_sfo3max_avg, sfo3max_avg_min)
overall_min_sfo3max_avg = np.amin(min_sfo3max_avg)
max_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_max = np.amax(i)
max_sfo3max_avg = np.append(max_sfo3max_avg, sfo3max_avg_max)
overall_max_sfo3max_avg = np.amax(max_sfo3max_avg)
# finally plot the 17 global plots of sfo3max_avg
for k in sfo3max_avg:
fig = plt.figure()
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines() # Adding coastlines
cs = ax.contourf(lon[:], lat[:], k[:], cmap='magma')
ax.set_title('Decadal Average of Maximum O3 Volume Mixing Ratio')
m = plt.cm.ScalarMappable(cmap=cm.magma)
m.set_array(i[:])
m.set_clim(overall_min_sfo3max_avg, overall_max_sfo3max_avg)
# Additional necessary information
cbar = plt.colorbar(m, boundaries=np.arange(overall_min_sfo3max_avg, overall_max_sfo3max_avg
+ 0.5e-08, 0.5e-08))
cbar.set_label('mol mol-1')
# Adding axis labels - latitude & longitude
gridl = ax.gridlines(color="black", linestyle="dotted", draw_labels=True)
gridl.xformatter=LONGITUDE_FORMATTER
gridl.yformatter=LATITUDE_FORMATTER
gridl.xlabels_top = False
gridl.ylabels_right = False
fig.set_size_inches(w=20,h=10)
plt.show() # show global plot
Several elements in your plotting can be kept out of the loop because they only need to be set up once. After you set up the plot elements you can update the plot and animate by looping over the list. This can be achieved by making use of matplotlib's interactive mode as shown in the code below:
import numpy as np
import netCDF4 as n4
import matplotlib
matplotlib.use("nbagg")
import matplotlib.pyplot as plt
from matplotlib import colorbar, colors
import matplotlib.cm as cm
import cartopy as cart
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.feature as cfeature
nc = n4.Dataset('datafile.nc','r')
# daily maximum O3 VMR (units: mol mol-1)
sfo3max = nc.variables['sfo3max']
lon = nc.variables['lon'] # longitude
lat = nc.variables['lat'] # latitude
# (I manipulate the data to produce 17 arrays containing the decadal average O3 VMR which are
# listed below in sfo3max_avg)
sfo3max_avg = [sfo3max_1850_1860_avg, sfo3max_1860_1870_avg, sfo3max_1870_1880_avg,
sfo3max_1880_1890_avg, sfo3max_1890_1900_avg, sfo3max_1900_1910_avg,
sfo3max_1910_1920_avg, sfo3max_1920_1930_avg, sfo3max_1930_1940_avg,
sfo3max_1940_1950_avg, sfo3max_1950_1960_avg, sfo3max_1960_1970_avg,
sfo3max_1970_1980_avg, sfo3max_1980_1990_avg, sfo3max_1990_2000_avg,
sfo3max_2000_2010_avg, sfo3max_2010_2015_avg]
# find overall min & max values for colour bar in plots
min_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_min = np.amin(i)
min_sfo3max_avg = np.append(min_sfo3max_avg, sfo3max_avg_min)
overall_min_sfo3max_avg = np.amin(min_sfo3max_avg)
max_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_max = np.amax(i)
max_sfo3max_avg = np.append(max_sfo3max_avg, sfo3max_avg_max)
overall_max_sfo3max_avg = np.amax(max_sfo3max_avg)
#setup the plot elements
fig = plt.figure()
fig.set_size_inches(w=20,h=10)
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines() # Adding coastlines
ax.set_title('Decadal Average of Maximum O3 Volume Mixing Ratio')
m = plt.cm.ScalarMappable(cmap=cm.magma)
m.set_array(i[:])
m.set_clim(overall_min_sfo3max_avg, overall_max_sfo3max_avg)
# Additional necessary information
cbar = plt.colorbar(m, boundaries=np.arange(overall_min_sfo3max_avg, overall_max_sfo3max_avg
+ 0.5e-08, 0.5e-08))
cbar.set_label('mol mol-1')
# plot here only the 1st item in your sfo3max_avg list.
cs = ax.contourf(lon[:], lat[:], sfo3max_avg[0][:], cmap='magma')
# Adding axis labels - latitude & longitude
gridl = ax.gridlines(color="black", linestyle="dotted", draw_labels=True)
gridl.xformatter=LONGITUDE_FORMATTER
gridl.yformatter=LATITUDE_FORMATTER
gridl.xlabels_top = False
gridl.ylabels_right = False
plt.ion() # set interactive mode
plt.show()
# finally plot the 17 global plots of sfo3max_avg
for k in sfo3max_avg:
cs = ax.contourf(lon[:], lat[:], k[:], cmap='magma')
plt.gcf().canvas.draw()
plt.pause(1) #control the interval between successive displays, currently set to 1 sec.

Add patch to Seaborn jointploint

I have created a patch and want to apply it to a jointplot in Seaborn. When I go to try to apply the patch, it either splits the plots into two graphics or, if I change the kind attribute in the jointplot function from kde to anything else, it throws an error inner got multiple values for keyword argument 'ax'.
When I try to apply this solution, the variable fg does not have the attribute axes and it does not work.
In the code below, if I use kind = "scatter" and omit the ax, I get a blank output then the jointpolot. If I use kind = "scatter" and add ax = ax, I get the above mentioned error. If I use kind = "kde" and ax = ax, I get the following images:
My code:
import descartes
import fiona
import matplotlib.pyplot as plt
import seaborn as sns
from shapely.geometry import shape
import pandas as pd
import time
#
start_time = time.time()
input_csv = r"C:\path\to\a\csv\with\coordinates.csv"
shapefile = r"C:\path\to\a\fun\shapefile.shp"
df = pd.read_csv(input_csv, delimiter = ",")
df = df[df["Latitude"] > 37.70833]
lat = "Latitude"
lon = "Longitude"
fig = plt.figure()
ax = fig.add_subplot(111, frameon = False)
shp = fiona.open(shapefile)
pol = shp.next()
geom = shape(pol["geometry"])
un_sf = geom.envelope.symmetric_difference(geom)
un_sf_patch = descartes.PolygonPatch(un_sf)
ax.add_patch( un_sf_patch )
my_fig = sns.jointplot(x = lon, y = lat, data = df, color = "grey", kind = "scatter")
end_time = round(time.time() - start_time, 5)
print "Seconds elapsed: {0}".format(end_time)
How can I add the patch to my Seaborn jointplot in a single graphic?
A seaborn jointplot creates its own figure, together with 3 axes.
g = sns.jointgrid(..)
g.ax_joint # big axes in the middle
g.ax_marg_y # marginal axes
g.ax_marg_x
Here you want to add your patch to the ax_joint.
g = sns.jointgrid(..)
g.ax_joint.add_patch( un_sf_patch )

shapefile and matplotlib: plot polygon collection of shapefile coordinates

I'm trying to plot filled polygons of countries on the world map with matplotlib in python.
I've got a shapefile with country boundary coordinates of every country. Now, I want to convert these coordinates (for each country) into a polygon with matplotlib. Without using Basemap. Unfortunately, the parts are crossing or overlapping. Is there a workarund, maybe using the distance from point to point.. or reordering them ?
Ha!
I found out, how.. I completely neglected, the sf.shapes[i].parts information! Then it comes down to:
# -- import --
import shapefile
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
# -- input --
sf = shapefile.Reader("./shapefiles/world_countries_boundary_file_world_2002")
recs = sf.records()
shapes = sf.shapes()
Nshp = len(shapes)
cns = []
for nshp in xrange(Nshp):
cns.append(recs[nshp][1])
cns = array(cns)
cm = get_cmap('Dark2')
cccol = cm(1.*arange(Nshp)/Nshp)
# -- plot --
fig = plt.figure()
ax = fig.add_subplot(111)
for nshp in xrange(Nshp):
ptchs = []
pts = array(shapes[nshp].points)
prt = shapes[nshp].parts
par = list(prt) + [pts.shape[0]]
for pij in xrange(len(prt)):
ptchs.append(Polygon(pts[par[pij]:par[pij+1]]))
ax.add_collection(PatchCollection(ptchs,facecolor=cccol[nshp,:],edgecolor='k', linewidths=.1))
ax.set_xlim(-180,+180)
ax.set_ylim(-90,90)
fig.savefig('test.png')
Then it will look like this:
Here is another piece of code I used to plot polygon shapefiles. It uses GDAL/OGR to read shapefile and plots correctly donut shape polygons:
from osgeo import ogr
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
# Extract first layer of features from shapefile using OGR
ds = ogr.Open('world_countries_boundary_file_world_2002.shp')
nlay = ds.GetLayerCount()
lyr = ds.GetLayer(0)
# Get extent and calculate buffer size
ext = lyr.GetExtent()
xoff = (ext[1]-ext[0])/50
yoff = (ext[3]-ext[2])/50
# Prepare figure
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim(ext[0]-xoff,ext[1]+xoff)
ax.set_ylim(ext[2]-yoff,ext[3]+yoff)
paths = []
lyr.ResetReading()
# Read all features in layer and store as paths
for feat in lyr:
geom = feat.geometry()
codes = []
all_x = []
all_y = []
for i in range(geom.GetGeometryCount()):
# Read ring geometry and create path
r = geom.GetGeometryRef(i)
x = [r.GetX(j) for j in range(r.GetPointCount())]
y = [r.GetY(j) for j in range(r.GetPointCount())]
# skip boundary between individual rings
codes += [mpath.Path.MOVETO] + \
(len(x)-1)*[mpath.Path.LINETO]
all_x += x
all_y += y
path = mpath.Path(np.column_stack((all_x,all_y)), codes)
paths.append(path)
# Add paths as patches to axes
for path in paths:
patch = mpatches.PathPatch(path, \
facecolor='blue', edgecolor='black')
ax.add_patch(patch)
ax.set_aspect(1.0)
plt.show()
from fiona import collection
import matplotlib.pyplot as plt
from descartes import PolygonPatch
from matplotlib.collections import PatchCollection
from itertools import imap
from matplotlib.cm import get_cmap
cm = get_cmap('Dark2')
figure, axes = plt.subplots(1)
source_path = "./shapefiles/world_countries_boundary_file_world_2002"
with collection(source_path, 'r') as source:
patches = imap(PolygonPatch, (record['geometry'] for record in source)
axes.add_collection( PatchCollection ( patches, cmap=cm, linewidths=0.1 ) )
axes.set_xlim(-180,+180)
axes.set_ylim(-90,90)
plt.show()
Note this assumes polygons, MultiPolygons can be handles in a similar manner with
map(PolygonPatch, MultiPolygon(record['geometry']))
Regarding to #hannesk's answer, you should add the following imports: from numpy import array and import matplotlib and replace the line cm = get_cmap('Dark2') by cm = matplotlib.cm.get_cmap('Dark2')
(I'm not so famous to add a comment to the noticed post.)

Categories

Resources