Plotting a map using geopandas and matplotlib - python

I have small csv that has 6 coordinates from Birmingham England. I read the csv with pandas then transformed it into GeoPandas DataFrame changing my latitude and longitude columns with Shapely Points. I am now trying to plot my GeoDataframe and all I can see are the points. How do I get the Birmingham map represented as well? A good documentation source on GeoPandas would be strongly appreciated too.
from shapely.geometry import Point
import geopandas as gpd
import pandas as pd
df = pd.read_csv('SiteLocation.csv')
df['Coordinates'] = list(zip(df.LONG, df.LAT))
df['Coordinates'] = df['Coordinates'].apply(Point)
# Building the GeoDataframe
geo_df = gpd.GeoDataFrame(df, geometry='Coordinates')
geo_df.plot()

The GeoPandas documentation contains an example on how to add a background to a map (https://geopandas.readthedocs.io/en/latest/gallery/plotting_basemap_background.html), which is explained in more detail below.
You will have to deal with tiles, that are (png) images served through a web server, with a URL like
http://.../Z/X/Y.png, where Z is the zoom level, and X and Y identify the tile
And geopandas's doc shows how to set tiles as backgrounds for your plots, fetching the correct ones and doing all the otherwise difficult job of spatial syncing, etc...
Installation
Assuming GeoPandas is already installed, you need the contextily package in addition. If you are under windows, you may want to pick a look at How to install Contextily?
Use case
Create a python script and define the contextily helper function
import contextily as ctx
def add_basemap(ax, zoom, url='http://tile.stamen.com/terrain/tileZ/tileX/tileY.png'):
xmin, xmax, ymin, ymax = ax.axis()
basemap, extent = ctx.bounds2img(xmin, ymin, xmax, ymax, zoom=zoom, url=url)
ax.imshow(basemap, extent=extent, interpolation='bilinear')
# restore original x/y limits
ax.axis((xmin, xmax, ymin, ymax))
and play
import matplotlib.pyplot as plt
from shapely.geometry import Point
import geopandas as gpd
import pandas as pd
# Let's define our raw data, whose epsg is 4326
df = pd.DataFrame({
'LAT' :[-22.266415, -20.684157],
'LONG' :[166.452764, 164.956089],
})
df['coords'] = list(zip(df.LONG, df.LAT))
# ... turn them into geodataframe, and convert our
# epsg into 3857, since web map tiles are typically
# provided as such.
geo_df = gpd.GeoDataFrame(
df, crs ={'init': 'epsg:4326'},
geometry = df['coords'].apply(Point)
).to_crs(epsg=3857)
# ... and make the plot
ax = geo_df.plot(
figsize= (5, 5),
alpha = 1
)
add_basemap(ax, zoom=10)
ax.set_axis_off()
plt.title('Kaledonia : From Hienghène to Nouméa')
plt.show()
Note: you can play with the zoom to find the good resolution for the map. E.g./I.e. :
... and such resolutions implicitly call for changing the x/y limits.

Just want to add the use case concerning zooming whereby the basemap is updated according to the new xlim and ylim coordinates. A solution I have come up with is:
First set callbacks on the ax that can detect xlim_changed and ylim_changed
Once both have been detected as changed get the new plot_area calling ax.get_xlim() and ax.get_ylim()
Then clear the ax and re-plot the basemap and any other data
Example for a world map showing the capitals. You notice when you zoom in the resolution of the map is being updated.
import geopandas as gpd
import matplotlib.pyplot as plt
import contextily as ctx
figsize = (12, 10)
osm_url = 'http://tile.stamen.com/terrain/{z}/{x}/{y}.png'
EPSG_OSM = 3857
EPSG_WGS84 = 4326
class MapTools:
def __init__(self):
self.cities = gpd.read_file(
gpd.datasets.get_path('naturalearth_cities'))
self.cities.crs = EPSG_WGS84
self.cities = self.convert_to_osm(self.cities)
self.fig, self.ax = plt.subplots(nrows=1, ncols=1, figsize=figsize)
self.callbacks_connect()
# get extent of the map for all cities
self.cities.plot(ax=self.ax)
self.plot_area = self.ax.axis()
def convert_to_osm(self, df):
return df.to_crs(epsg=EPSG_OSM)
def callbacks_connect(self):
self.zoomcallx = self.ax.callbacks.connect(
'xlim_changed', self.on_limx_change)
self.zoomcally = self.ax.callbacks.connect(
'ylim_changed', self.on_limy_change)
self.x_called = False
self.y_called = False
def callbacks_disconnect(self):
self.ax.callbacks.disconnect(self.zoomcallx)
self.ax.callbacks.disconnect(self.zoomcally)
def on_limx_change(self, _):
self.x_called = True
if self.y_called:
self.on_lim_change()
def on_limy_change(self, _):
self.y_called = True
if self.x_called:
self.on_lim_change()
def on_lim_change(self):
xlim = self.ax.get_xlim()
ylim = self.ax.get_ylim()
self.plot_area = (*xlim, *ylim)
self.blit_map()
def add_base_map_osm(self):
if abs(self.plot_area[1] - self.plot_area[0]) < 100:
zoom = 13
else:
zoom = 'auto'
try:
basemap, extent = ctx.bounds2img(
self.plot_area[0], self.plot_area[2],
self.plot_area[1], self.plot_area[3],
zoom=zoom,
url=osm_url,)
self.ax.imshow(basemap, extent=extent, interpolation='bilinear')
except Exception as e:
print(f'unable to load map: {e}')
def blit_map(self):
self.ax.cla()
self.callbacks_disconnect()
cities = self.cities.cx[
self.plot_area[0]:self.plot_area[1],
self.plot_area[2]:self.plot_area[3]]
cities.plot(ax=self.ax, color='red', markersize=3)
print('*'*80)
print(self.plot_area)
print(f'{len(cities)} cities in plot area')
self.add_base_map_osm()
self.callbacks_connect()
#staticmethod
def show():
plt.show()
def main():
map_tools = MapTools()
map_tools.show()
if __name__ == '__main__':
main()
Runs on Linux Python3.8 with following pip installs
affine==2.3.0
attrs==19.3.0
autopep8==1.4.4
Cartopy==0.17.0
certifi==2019.11.28
chardet==3.0.4
Click==7.0
click-plugins==1.1.1
cligj==0.5.0
contextily==1.0rc2
cycler==0.10.0
descartes==1.1.0
Fiona==1.8.11
geographiclib==1.50
geopandas==0.6.2
geopy==1.20.0
idna==2.8
joblib==0.14.0
kiwisolver==1.1.0
matplotlib==3.1.2
mercantile==1.1.2
more-itertools==8.0.0
munch==2.5.0
numpy==1.17.4
packaging==19.2
pandas==0.25.3
Pillow==6.2.1
pluggy==0.13.1
py==1.8.0
pycodestyle==2.5.0
pyparsing==2.4.5
pyproj==2.4.1
pyshp==2.1.0
pytest==5.3.1
python-dateutil==2.8.1
pytz==2019.3
rasterio==1.1.1
requests==2.22.0
Rtree==0.9.1
Shapely==1.6.4.post2
six==1.13.0
snuggs==1.4.7
urllib3==1.25.7
wcwidth==0.1.7
Note especially requirement for contextily==1.0rc2
On windows I use Conda (P3.7.3) and don't forget to set the User variables:
GDAL c:\Users\<username>\Anaconda3\envs\<your environment>\Library\share\gdal
PROJLIB c:\Users\<username>\Anaconda3\envs\<your environment>\Library\share

Try df.unary_union. The function will aggregate points into a single geometry.
Jupyter Notebook can plot it

Related

Raster and Shapefiles not lining up using Geopandas, Rasterio, and Contextily

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))

Issues Using geoplot.kdeplot with geopandas

Im trying to make a kdeplot using geopandas.
this is my code:
Downloading shape file
URL = "https://data.sfgov.org/api/geospatial/wkhw-cjsf?method=export&format=Shapefile"
response = requests.get(URL)
open('pd_data.zip', 'wb').write(response.content)
with zipfile.ZipFile('./pd_data.zip', 'r') as zip_ref:
zip_ref.extractall('./ShapeFiles')
Making the geopandas data frame
data = train.groupby(['PdDistrict']).count().iloc[:,0]
data = pd.DataFrame({ "district": data.index,
"incidences": data.values})
california_map = str(list(pathlib.Path('./ShapeFiles').glob('*.shp'))[0])
gdf = gdp.read_file(california_map)
gdf = pd.merge(gdf, data, on = 'district')
Note: I didn't include the link to the train set because it's not important for this question(use any data you want)
This is the part that I don't get,
what arguments should I pass to the kdeplot function, like where I pass the shape file and where I pass the data?
ax = gplt.kdeplot(
data, clip=gdf.geometry,
shade=True, cmap='Reds',
projection=gplt.crs.AlbersEqualArea())
gplt.polyplot(boroughs, ax=ax, zorder=1)
had a few challenges setting up an environment where I did not get kernel crashes. Used none wheel versions of shapely and pygeos
a few things covered in documentation kdeplot A basic kdeplot takes pointwise data as input. You did not provide sample for data I'm not sure that it is point wise data. Have simulated point wise data, 100 points within each of the districts in referenced geometry
I have found I cannot use clip and projection parameters together. One or the other not both
shape file is passed to clip
import geopandas as gpd
import pandas as pd
import numpy as np
import geoplot as gplt
import geoplot.crs as gcrs
# setup starting point to match question
url = "https://data.sfgov.org/api/geospatial/wkhw-cjsf?method=export&format=Shapefile"
gdf = gpd.read_file(url)
# generate 100 points in each of the districts
r = np.random.RandomState(42)
N = 5000
data = pd.concat(
[
gpd.GeoSeries(
gpd.points_from_xy(*[r.uniform(*g.bounds[s::2], N) for s in (0, 1)]),
crs=gdf.crs,
).loc[lambda s: s.intersects(g.buffer(-0.003))]
for _, g in gdf["geometry"].iteritems()
]
)
data = (
gpd.GeoDataFrame(geometry=data)
.sjoin(gdf)
.groupby("district")
.sample(100, random_state=42)
.reset_index(drop=True)
)
ax = gplt.kdeplot(
data,
clip=gdf,
fill=True,
cmap="Reds",
# projection=gplt.crs.AlbersEqualArea(),
)
gplt.polyplot(gdf, ax=ax, zorder=1)

Plot polylines on top of OSMnx map

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:

how to plot geotiff data in specific area (lat/lon) with python

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()

Set the zoom level of a bokeh map when using a tile provider

I've followed the example here: http://docs.bokeh.org/en/latest/docs/user_guide/geo.html#tile-providers
I got a basic map loading a GeoJSON file with a list of polygons (already projected to Web Mercator EPSG:3857) so then I could use STAMEN_TONER as a tile provider.
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.tile_providers import STAMEN_TONER, STAMEN_TERRAIN
from bokeh.models import Range1d, GeoJSONDataSource
# bokeh configuration for jupyter
from bokeh.io import output_notebook
output_notebook()
# bounding box (x,y web mercator projection, not lon/lat)
mercator_extent_x = dict(start=x_low, end=x_high, bounds=None)
mercator_extent_y = dict(start=y_low, end=y_high, bounds=None)
x_range1d = Range1d(**mercator_extent_x)
y_range1d = Range1d(**mercator_extent_y)
fig = figure(
tools='pan, zoom_in, zoom_out, box_zoom, reset, save',
x_range=x_range1d,
y_range=y_range1d,
plot_width=800,
plot_height=600
)
fig.axis.visible = False
fig.add_tile(STAMEN_TERRAIN)
# the GeoJSON is already in x,y web mercator projection, not lon/lat
with open('/path/to/my_polygons.geojson', 'r') as f:
my_polygons_geo_json = GeoJSONDataSource(geojson=f.read())
fig.multi_line(
xs='xs',
ys='ys',
line_color='black',
line_width=1,
source=my_polygons_geo_json
)
show(fig)
However I am not able to set a default zoom level for the tiles. I thought it could have been a tool setting (http://docs.bokeh.org/en/latest/docs/user_guide/tools.html) but in there I can not find a default value for the zoom capabilities.
How can I set a default value for the zoom level of the tiles?
Old question but answering if someone would have the same problem. Set range for your map and this way you can zoom into the desired area on load. Below example with Papua New Guinea
p = figure(title="PNG Highlands Earthquake 7.5 Affected Villages",y_range=(-4.31509, -7.0341),x_range=( 141.26667, 145.56598))
p.xaxis.axis_label = 'longitude'
p.yaxis.axis_label = 'latitude'
I've just run into this issue myself, and found a good solution that should work under most circumstances. This requires making sure the data and the x_range/y_range to be projected properly (I used Proj and transform from pyproj but I'm sure there are other packages that will work the same).
Import modules:
import pandas as pd
import numpy as np
from pyproj import Proj, transform
import datashader as ds
from datashader import transfer_functions as tf
from datashader.bokeh_ext import InteractiveImage
from datashader.utils import export_image
from datashader.colors import colormap_select, Greys9, Hot, viridis, inferno
from IPython.core.display import HTML, display
from bokeh.plotting import figure, output_notebook, output_file, show
from bokeh.tile_providers import CARTODBPOSITRON
from bokeh.tile_providers import STAMEN_TONER
from bokeh.tile_providers import STAMEN_TERRAIN
from bokeh.embed import file_html
from functools import partial
output_notebook()
Read in data (I took a few extra steps to try and clean the coordinates since I'm working with an extremely messy dataset that contains NaN and broken text in the coordinates columns):
df = pd.read_csv('data.csv', usecols=['latitude', 'longitude'])
df.apply(lambda x: pd.to_numeric(x,errors='coerced')).dropna()
df = df.loc[(df['latitude'] > - 90) & (df['latitude'] < 90) & (df['longitude'] > -180) & (df['longitude'] < 180)]
Reproject data:
# WGS 84
inProj = Proj(init='epsg:4326')
# WGS84 Pseudo Web Mercator, projection for most WMS services
outProj = Proj(init='epsg:3857')
df['xWeb'],df['yWeb'] = transform(inProj,outProj,df['longitude'].values,df['latitude'].values)
Reproject the x_range, y_range. This is critical as these values set the extent of the bokeh map - the coordinates of these values need to match the projection. To make sure you have the correct coordinates, I suggest using http://bboxfinder.com to create a bounding box AOI and get the correct min/max and min/max coordinates (making sure EPSG:3857 - WGS 84/Pseudo-Mercator is selected). Using this method, just copy the coodinates next to "box" - these are in the order of minx,miny,maxx,maxy and should then be reordered as minx,maxx,miny,maxy (x_range = (minx,maxx))(y_range=(miny,maxy)):
world = x_range, y_range = ((-18706892.5544, 21289852.6142), (-7631472.9040, 12797393.0236))
plot_width = int(950)
plot_height = int(plot_width//1.2)
def base_plot(tools='pan,wheel_zoom,save,reset',plot_width=plot_width,
plot_height=plot_height, **plot_args):
p = figure(tools=tools, plot_width=plot_width, plot_height=plot_height,
x_range=x_range, y_range=y_range, outline_line_color=None,
min_border=0, min_border_left=0, min_border_right=0,
min_border_top=0, min_border_bottom=0, **plot_args)
p.axis.visible = False
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
return p
options = dict(line_color=None, fill_color='blue', size=1.5, alpha=0.25)
background = "black"
export = partial(export_image, export_path="export", background=background)
cm = partial(colormap_select, reverse=(background=="white"))
def create_image(x_range, y_range, w=plot_width, h=plot_height):
cvs = ds.Canvas(plot_width=w, plot_height=h, x_range=x_range, y_range=y_range)
agg = cvs.points(df, 'xWeb', 'yWeb')
magma = ['#3B0F6F', '#8C2980', '#DD4968', '#FD9F6C', '#FBFCBF']
img = tf.shade(agg, cmap=magma, how='eq_hist') # how='linear', 'log', 'eq_hist'
return tf.dynspread(img, threshold=.05, max_px=15)
p = base_plot()
p.add_tile("WMS service")
#used to export image (without the WMS)
export(create_image(*world),"TweetGeos")
#call interactive image
InteractiveImage(p, create_image)
The notion of a zoom "level" only applies to GMapPlot and there only because google controls the presentation of the maps very carefully, and that is the API they provide. All other Bokeh plots have explicitly user-settable x_range and y_range properties. You can set the start and end of these ranges to be whatever you want, and the plot will display the corresponding area defined by those bounds.

Categories