Using the OSMnx library, I'm trying to draw lines as polygons on top of a base map (with per-defined coordinates not adhering to the underlying network), but with no luck. I'm certain that the coordinates I have are inside of the boundary, and I get no error when adding them.
Here's my current code, which generates the base map and also adds a multi polygon layer below the network. So it's possible to add polygons, which makes me think there might a projection issue with my coordinates, but I haven't had any luck setting different projections.
Any help would be much appreciated!
import matplotlib.pyplot as plt
from descartes import PolygonPatch
from shapely.geometry import Polygon, MultiPolygon
import osmnx as ox
ox.config(log_console=True, use_cache=True)
ox.__version__
def plot(geometries):
# get the place shape
gdf = ox.gdf_from_place('Copenhagen Municipality,Denmark')
gdf = ox.project_gdf(gdf)
# get the street network, with retain_all=True to retain all the disconnected islands' networks
G = ox.graph_from_place('Copenhagen Municipality,Denmark', network_type='drive', retain_all=True)
G = ox.project_graph(G)
fig, ax = ox.plot_graph(G, fig_height=10, show=False, close=False, edge_color='#777777')
# Add shape from gdf
for geometry in gdf['geometry'].tolist():
if isinstance(geometry, (Polygon, MultiPolygon)):
if isinstance(geometry, Polygon):
geometry = MultiPolygon([geometry])
for polygon in geometry:
patch = PolygonPatch(polygon, fc='#cccccc', ec='k', linewidth=3, alpha=0.1, zorder=-1)
ax.add_patch(patch)
# Add lines:
for geometry in geometries:
if isinstance(geometry, (Polygon, MultiPolygon)):
if isinstance(geometry, Polygon):
geometry = MultiPolygon([geometry])
for polygon in geometry:
patch = PolygonPatch(polygon, fc='#148024', ec='#777777', linewidth=10, alpha=1, zorder=2)
ax.add_patch(patch)
plt.savefig('images/cph.png', alpha=True, dpi=300)
plot(geometries)
geometries is a list which contains polygons like these:
POLYGON ((55.6938796 12.5584122, 55.6929711 12.5585957, 55.6921317 12.5579927, 55.6916918 12.5564539, 55.6909246 12.5553629, 55.6901215 12.554119, 55.6891181 12.5531433, 55.6881469 12.5526575, 55.687502 12.5538862, 55.6866445 12.5530816, 55.6856769 12.5524416, 55.6848185 12.5515929, 55.6838506 12.551074, 55.6829915 12.5504047, 55.6821492 12.5498124, 55.6812104 12.5492503, 55.680311 12.5486803, 55.6792187 12.547724, 55.6783172 12.5472156, 55.6774282 12.5466767, 55.6765291 12.5461124, 55.6755652 12.5453961, 55.6747743 12.5445313, 55.6738159 12.5439029, 55.673417 12.5454132, 55.6733398 12.5470051, 55.6731045 12.5486561, 55.6726013 12.5501493, 55.6727833 12.5520672, 55.6716717 12.5525378, 55.6706619 12.5528382, 55.6698239 12.5521737))
POLYGON ((55.6693768 12.5509383, 55.6684025 12.5511539, 55.6677405 12.5500371, 55.6668188 12.5501435, 55.6658323 12.550075, 55.665264 12.5487917, 55.6649187 12.5473085, 55.6645313 12.5457653))
In geopandas dataframes, the geo-coordinates are (longitude, latitude). Here is a simple demonstration code that plots some sample data.
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
import osmnx as ox
from shapely import wkt #need wkt.loads
# simple plot of OSM data
gdf = ox.gdf_from_place('Copenhagen Municipality,Denmark')
gdf = gpd.GeoDataFrame(gdf, crs={'init': 'epsg:4326'}) #set CRS
ax1 = gdf.plot(color='lightgray') # grab axis as `ax1` for reuse
# prep the polygons to plot on the axis `ax1`
# use (longitude latitude), and the last point must equal the 1st
pgon1 = "POLYGON((12.5584122 55.6938796, 12.5585957 55.6929711, 12.5579927 55.6921317, 12.5564539 55.6916918, 12.5553629 55.6909246, 12.554119 55.6901215, 12.5531433 55.6891181, 12.5526575 55.6881469, 12.5538862 55.687502, 12.5530816 55.6866445, 12.5524416 55.6856769, 12.5515929 55.6848185, 12.551074 55.6838506, 12.5504047 55.6829915, 12.5498124 55.6821492, 12.5492503 55.6812104, 12.5486803 55.680311, 12.547724 55.6792187, 12.5472156 55.6783172, 12.5466767 55.6774282, 12.5461124 55.6765291, 12.5453961 55.6755652, 12.5445313 55.6747743, 12.5439029 55.6738159, 12.5454132 55.673417, 12.5470051 55.6733398, 12.5486561 55.6731045, 12.5501493 55.6726013, 12.5520672 55.6727833, 12.5525378 55.6716717, 12.5528382 55.6706619, 12.5521737 55.6698239, 12.5584122 55.6938796))"
pgon2 = "POLYGON((12.5509383 55.6693768, 12.5511539 55.6684025, 12.5500371 55.6677405, 12.5501435 55.6668188, 12.550075 55.6658323, 12.5487917 55.665264, 12.5473085 55.6649187, 12.5457653 55.6645313, 12.5509383 55.6693768))"
# create dataframe of the 2 polygons
d = {'col1': [1, 2], 'wkt': [pgon1, pgon2]}
df = pd.DataFrame( data=d )
# make geo-dataframe from it
geometry = [wkt.loads(pgon) for pgon in df.wkt]
gdf2 = gpd.GeoDataFrame(df, \
crs={'init': 'epsg:4326'}, \
geometry=geometry)
# plot it as red polygons
gdf2.plot(ax=ax1, color='red', zorder=5)
The output plot:
Related
I want to create an annular buffer in GeoPandas (with an inner radius of 2 km and an outer radius of 2.1 km) around a specific point of latitude -33.0679433 and longitude -71.5524818
I did so by creating 2 buffers and turned them into GeoDataFrames so I could overlay them by difference like
res_difference = buffer2_1gdf.overlay(buffer2gdf, how='difference')
They are correctly overlayed, however, when I plot the map of the area I'm using it won't correctly plot. I viewed res_difference and it's geometry does not seem to be correct as its coordinates are not what I expect, it looks as if the coordinates are correlated with the values I gave the radiuses
You have to transform the crs of your point's geometry to an appropriate one and get the buffer in meters. Example:
import geopandas as gpd
import matplotlib.pyplot as plt
from shapely.geometry import Point
pt = Point( -33.0679433, -71.5524818)
pt_df = gpd.GeoDataFrame(geometry=[pt], crs=4326)
buff1 = pt_df.copy()
buff1 = buff1.to_crs(3857)
buff1['geometry'] = buff1.geometry.buffer(2000)
buff1 = buff1.to_crs(4326)
buff2 = pt_df.copy()
buff2 = buff2.to_crs(3857)
buff2['geometry'] = buff2.geometry.buffer(2100)
buff2 = buff2.to_crs(4326)
res = gpd.overlay(buff2, buff1, how='difference')
ax = pt_df.plot()
res.plot(color='red', ax=ax)
plt.show()
I am trying to get a DEM raster to line up with a shapefile in Python, but it will not show up no matter what I do. This is for lab exercise, the entire rest of the exercise relies on these lining up, as I will be extracting data from the raster and polygon layers to a point layer.
I know how to do all this "by hand" in ArcGIS, but the point of the exercise is to use R or Python (the professor did an example with R, but we can use whichever, and I have been learning Python the past couple of months for a work project). In the class notes, he says that both files are in EPSG 3847, but the shapefile was missing the CRS, so I added the CRS to it in geopandas.
The DEM appears to be EPSG 3006 (even though it was supposed to be in 3847), so I tried converting it to EPSG 3847 and it still does not show up. So then I tried going the other way and converting the shapefile to EPSG 3006, which did not help either.
import contextily as cx
import geopandas as gpd
import rasterio
from rasterio.plot import show
from rasterio.crs import CRS
from rasterio.plot import show as rioshow
import matplotlib.pyplot as plt
#data files
abisveg = gpd.read_file(r'/content/drive/MyDrive/Stackoverflow/Sweden/abisveg_polygon.shp')
abisveg_3847 = abisveg.set_crs(epsg = 3847)
abisveg_3006 = abisveg_3847.to_crs(epsg = 3006)
src = rasterio.open(r'/content/drive/MyDrive/Stackoverflow/Sweden/nh_75_6.tif')
DEM = src.read()
### creating plot grid
fig = plt.figure(figsize = (20,20), constrained_layout = True)
gs = fig.add_gridspec(1,3)
ax1 = fig.add_subplot(gs[0,0])
ax2 = fig.add_subplot(gs[0,1], sharex = ax1, sharey = ax1)
ax3 = fig.add_subplot(gs[0,2], sharex = ax1, sharey = ax1)
### Plot 1 - Basemap Only
abisveg_3006.plot(ax = ax1, color = 'none')
cx.add_basemap(ax1, crs = 3006)
ax1.set_aspect('equal')
ax1.set_title("Basemap of AOI")
### Plot 2 - DEM
# abisveg_3847.plot(ax = ax2, color = 'none')
show(DEM, ax=ax2, cmap = "Greys")
cx.add_basemap(ax2, crs = 3006)
ax2.set_aspect('equal')
ax2.set_title('Digitial Elevation Model of AOI')
### Plot 3 - Vegetation Types
abisveg_3006.plot(ax = ax3, column = "VEGKOD", cmap = "viridis")
cx.add_basemap(ax3, crs = 3006)
ax3.set_aspect('equal')
ax3.set_title("Vegetation Types")
3 Panel map with missing DEM:
https://i.imgur.com/taG2U9Q.jpg
Trying to plot the files in Matplotlib has not worked, b/c they do not align at all. I am using contextily for the basemap, and have set the basemap CRS to EPSG 3847 (or 3006, depending on which version of the GIS files I was using). The shapefile shows up in the correct location no matter the projection, but the Raster does not show up. What's weird is that if I open everything up in ArcGIS, it all lines up correctly.
If I plot just the DEM all by itself, it shows up, though I don't know where on the earth it is plotting.
fig = plt.figure(figsize = (10,10), constrained_layout = True)
show(DEM, cmap = "Greys")
DEM just by itself:
https://i.imgur.com/KyYu7jc.jpg
I have my code in a colab notebook here:
https://colab.research.google.com/drive/1VAZ3dgf0QS2PPBOl8KJ2FXtB2oRj0qJ8?usp=share_link
The files are here:
https://drive.google.com/drive/folders/1t-xvpIcLOIR9uYXOguJ7KyKqt7wuYSNc?usp=share_link
You could give EOmaps a try... it uses matplotlib/cartopy for plotting and handles re-projecting the data and shapes to the plot-crs
from pathlib import Path
from eomaps import Maps
import geopandas as gpd
p = Path(r"path to the data folder")
# read shapefile
abisveg = gpd.read_file(p / 'abisveg_polygon.shp').set_crs(epsg = 3847)
# create a map in epsg=3006
m = Maps(crs=3006, figsize=(10, 8))
# add stamen-terrain basemap
m.add_wms.OpenStreetMap.add_layer.stamen_terrain()
# plot shapefile (zorder=2 to be on top of the DEM)
m.add_gdf(abisveg, column=abisveg.VEGKOD, cmap="viridis", ec="k", lw=0.2, alpha=0.5, zorder=2)
# plot DEM
m2 = m.new_layer_from_file.GeoTIFF(p / "nh_75_6.tif", cmap="Greys", zorder=1)
m.ax.set_extent((589913.0408156103, 713614.6619114348, 7495264.310799116, 7618965.93189494),
Maps.CRS.epsg(3006))
I have a geotiff raster data sets with elevation data init and i want to plot it in specific area, such as 60°E - 70° E ,70°S - 80°E.
I have a bit of code from here,but the pcolormesh seem couldn't plot my geotif.it's all red. picture. The picture is shown by imshow as really picture
When I try to make a plot with this code below:
path = "F:\\Mosaic_h1112v28_ps.tif"
dataset = gdal.Open(path)
data = dataset.ReadAsArray()
x0, dx, dxdy, y0, dydx, dy = dataset.GetGeoTransform()
nrows, ncols = data.shape
londata = np.linspace(x0, x0+dx*ncols)
latdata = np.linspace(y0, y0+dy*nrows)
lons, lats = np.meshgrid(lonarray, latarray)
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', lon_0=67.5, lat_0=-68.5, height=950000,
width=580000, resolution='h')
m.drawcoastlines()
x, y = m(lons, lats)
Then i dont know how to continue it . I just want to use imshow, but the imshow dont specify area(lat/lon).
I will really appreciate your help.
It's a good question, here is my solution.
Required packages: georaster with its dependencies (gdal, etc).
Data for demo purposes downloadable from http://dwtkns.com/srtm/
import georaster
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(8,8))
# full path to the geotiff file
fpath = r"C:\\path_to_your\geotiff_file\srtm_57_10.tif" # Thailand east
# read extent of image without loading
# good for values in degrees lat/long
# geotiff may use other coordinates and projection
my_image = georaster.SingleBandRaster(fpath, load_data=False)
# grab limits of image's extent
minx, maxx, miny, maxy = my_image.extent
# set Basemap with slightly larger extents
# set resolution at intermediate level "i"
m = Basemap( projection='cyl', \
llcrnrlon=minx-2, \
llcrnrlat=miny-2, \
urcrnrlon=maxx+2, \
urcrnrlat=maxy+2, \
resolution='i')
m.drawcoastlines(color="gray")
m.fillcontinents(color='beige')
# load the geotiff image, assign it a variable
image = georaster.SingleBandRaster( fpath, \
load_data=(minx, maxx, miny, maxy), \
latlon=True)
# plot the image on matplotlib active axes
# set zorder to put the image on top of coastlines and continent areas
# set alpha to let the hidden graphics show through
plt.imshow(image.r, extent=(minx, maxx, miny, maxy), zorder=10, alpha=0.6)
plt.show()
The resulting plot:
Edit1
My original answer places focus on how to plot simple geotiff image on the most basic projection with Basemap. A better answer was not possible without access to all required resources (i.e. geotiff file).
Here I try to improve my answer.
I have clipped a small portion from whole world geotiff file. Then reproject (warp) it to LCC projection specifications defined by Basemap() to be used. All the process were done with GDAL softwares. The resulting file is named "lcc_2.tiff". With this geotiff file, the plotting of the image is done with the code below.
The most important part is that geotiff file must have the same coordinate system (same projection) as the projection used by Basemap.
import georaster
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(8,8))
m = Basemap(projection='lcc', lon_0=67.5, lat_0=-68.5, \
height=950000, width=580000, resolution='h')
m.drawcoastlines()
m.fillcontinents(color='beige')
image = georaster.SingleBandRaster( "lcc_2.tiff", latlon=False)
plt.imshow(image.r, extent=image.extent, zorder=10, alpha=0.6)
plt.show()
The output map:
Here is my solution.
1. Import GEOTIF file and transform it into 2-D array data
from osgeo import gdal
pathToRaster = r'./xxxx.tif'
raster = gdal.Open(pathToRaster, gdal.GA_ReadOnly)
data = raster.GetRasterBand(1).ReadAsArray()
data = data[::-1]
2. Plot it using Pcolormesh
kk = plt.pcolormesh(data,cmap = plt.cm.Reds,alpha = 0.45, zorder =2)
You can use rioxarray
import rioxarray as rio
ds = rio.open_rasterio(path)
# Example lat lon range for subset
geometries = [
{
'type': 'Polygon',
'coordinates': [[
[33.97301017665958, -118.45830810580743],
[33.96660083660732, -118.37455741054782],
[33.92304171545437, -118.37151348516299],
[33.915042933806724, -118.42909440702563]
]]
}
]
clipped = ds.rio.clip(geometries)
clipped.plot()
I would like to produce a plot of New Zealand with each region colour coded according to some data. However, the shapefiles I have used to produce the image extend beyond the edge of the land and into the ocean. This means that when I colour the polygons they end up colouring portions of the ocean as well. Not the desired response!
The code I am using is:
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
plt.figure(figsize=(15,15))
lonmax = 180
lonmin = 165
latmax = -33
latmin = -48
map = Basemap(llcrnrlon=lonmin,llcrnrlat=latmin,urcrnrlon=lonmax,urcrnrlat=latmax, resolution = 'i')
map.drawmapboundary(fill_color='white')
map.fillcontinents(color='white',lake_color='white')
map.drawcoastlines()
map.readshapefile('../data/raw/statsnzregional-council-2016-generalised-version-SHP/regional-council-2016-generalised-version', 'regional_council')
ax = plt.gca() # get current axes instance
cm = matplotlib.cm.get_cmap('viridis')
norm = matplotlib.colors.Normalize(vmin=0.05, vmax=0.35)
for info, shape in zip(map.regional_council_info, map.regional_council):
poly = Polygon(shape, facecolor=cm(norm(region_percent[info['REGC2016_N']])),edgecolor='k')
ax.add_patch(poly)
plt.show()
Which produces the below image. This image is very close to what I want but I would like the colouring to stop at the land boundary rather than colouring the ocean as well.
I have looked into Basemap's maskoceans() and believe this is probably the best way to solve this but I don't understand how to apply it to my particular situation (eg, how to access the lat,lons? What is the data array in this case?)
Alternatively is there a way to make the map boundary of New Zealand a hard boundary so only the interior overlap with the polygon patches is printed?
You need some polygons to mask out the excess areas.
Get the mask files (nz_mask_w.shp, nz_mask_e.shp) here:
https://github.com/swatchai/cartopy_asean_proj/tree/master/shape_files
And this is the code:
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
import shapefile # used to read my shapefiles
fig = plt.figure(figsize=(15,15))
lonmax = 179.95
lonmin = 165
latmax = -33
latmin = -48
map = Basemap(llcrnrlon=lonmin, \
llcrnrlat=latmin, \
urcrnrlon=lonmax, \
urcrnrlat=latmax, \
resolution = 'i')
# this is map theme (change to your need)
map.readshapefile('data/statsnzregional/regional-council-2016-generalised-version', \
name='regional_council')
ax = plt.gca() # get current axes instance
#cm = matplotlib.cm.get_cmap('viridis')
#norm = matplotlib.colors.Normalize(vmin=0.05, vmax=0.35)
for info, shape in zip(map.regional_council_info, map.regional_council):
poly = Polygon(shape, \
facecolor=cm(norm(int(info['REGC2016']))/100.), \
edgecolor='k', \
zorder=1)
# original:- facecolor=cm(norm(info['REGC2016']))
ax.add_patch(poly)
# mask out the excess areas (use files in data sub folder)
sf = shapefile.Reader("data/nz_mask_w")
ss = sf.shapes()
poly1 = Polygon(ss[0].points)
sf = shapefile.Reader("data/nz_mask_e")
ss = sf.shapes()
poly2 = Polygon(ss[0].points)
ax.add_collection( PatchCollection([poly1,poly2], \
zorder=12, \
facecolor='lightblue', \
edgecolor='lightblue' ) )
map.drawcoastlines(color='blue', linewidth=0.3)
plt.show()
I have a rotated pole projection (taken from the Rapid Refresh model parameters) that I am able to plot correctly in matplotlib-basemap, but can't figure out how to reproduce with cartopy. Here is the Python code using basemap:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
bm = Basemap(projection = "rotpole",
o_lat_p = 36.0,
o_lon_p = 180.0,
llcrnrlat = -10.590603,
urcrnrlat = 46.591976,
llcrnrlon = -139.08585,
urcrnrlon = 22.661009,
lon_0 = -106.0,
rsphere = 6370000,
resolution = 'l')
fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1,0.1,0.8,0.8])
bm.drawcoastlines(linewidth=.5)
print bm.proj4string
plt.savefig("basemap_map.png")
plt.close(fig)
The proj4 string that prints is:
+o_proj=longlat +lon_0=-106.0 +o_lat_p=36.0 +R=6370000.0 +proj=ob_tran +units=m +o_lon_p=180.0
If I use the RotatedPole projection in cartopy and supply the projection parameters from above, I get an image in the south pole. Here is a snippet (manually typed in from a real example, be warned):
from cartopy import crs
import matplotlib.pyplot as plt
cart = crs.RotatedPole(pole_longitude=180.0,
pole_latitude=36.0,
central_rotated_longitude=-106.0,
globe = crs.Globe(semimajor_axis=6370000,
semiminor_axis=6370000))
fig = plt.figure(figsize=(8,8))
ax = plt.axes([0.1,0.1,0.8,0.8], projection=cart)
ax.set_extent([-139.08585, 22.661009, -10.590603, 46.591976], crs.Geodetic())
plt.savefig("cartopy_map.png")
plt.close(fig)
I've also tried modifying arguments to the RotatedPole class to produce the proj4 parameters from above, and even tried making my own subclass of _CylindricalProjection and setting the proj4 parameters directly in the constructor, but still no luck.
What is the right way in cartopy to produce the same result as basemap?
Here is the basemap image:
Here is what cartopy produces for the above example:
Thanks for your help!
Bill
There is an attribute available on a cartopy CRS which gives you the proj4 parameters.
from cartopy import crs
rp = crs.RotatedPole(pole_longitude=180.0,
pole_latitude=36.0,
central_rotated_longitude=-106.0,
globe=crs.Globe(semimajor_axis=6370000,
semiminor_axis=6370000))
print(rp.proj4_params)
Gives:
{'a': 6370000, 'o_proj': 'latlon',
'b': 6370000, 'to_meter': 0.017453292519943295,
'ellps': 'WGS84', 'lon_0': 360.0,
'proj': 'ob_tran', 'o_lat_p': 36.0,
'o_lon_p': -106.0}
So it looks like you only need to set the pole longitude and latitude in order to match your desired projection. The important point being that pole longitude is the position of the dateline of the new projection, not its central longitude - from memory, I seem to remember that this is consistent with bodies such as the WMO, but inconsistent with proj.4:
>>> rp = ccrs.RotatedPole(pole_longitude=-106.0 - 180,
pole_latitude=36,
globe=ccrs.Globe(semimajor_axis=6370000,
semiminor_axis=6370000))
>>> print(rp.proj4_params)
{'a': 6370000, 'o_proj': 'latlon', 'b': 6370000, 'to_meter': 0.017453292519943295,
'ellps': 'WGS84', 'lon_0': -106.0, 'proj': 'ob_tran',
'o_lat_p': 36, 'o_lon_p': 0.0}
With all of that in place, the final code might look something like:
import cartopy.crs as ccrs
import cartopy.feature
import matplotlib.pyplot as plt
import numpy as np
rp = ccrs.RotatedPole(pole_longitude=-106.0 - 180,
pole_latitude=36,
globe=ccrs.Globe(semimajor_axis=6370000,
semiminor_axis=6370000))
pc = ccrs.PlateCarree()
ax = plt.axes(projection=rp)
ax.coastlines('50m', linewidth=0.8)
ax.add_feature(cartopy.feature.LAKES,
edgecolor='black', facecolor='none',
linewidth=0.8)
# In order to reproduce the extent, we can't use cartopy's smarter
# "set_extent" method, as the bounding box is computed based on a transformed
# rectangle of given size. Instead, we want to emulate the "lower left corner"
# and "upper right corner" behaviour of basemap.
xs, ys, zs = rp.transform_points(pc,
np.array([-139.08, 22.66]),
np.array([-10.59, 46.59])).T
ax.set_xlim(xs)
ax.set_ylim(ys)
plt.show()