I am currently creating a Dash app that uses drop down menus to determine what data to plot on a global map (usually contour plots).
I am aware that Basemap is being phased out, so want to change to Cartopy as the source of the maps (Mapbox is ace but not suitable) but am unsure how to get the map information as a trace in order to use it with Dash?
I have no problems changing over for a simple plot- its just within the Dash interface where the map data needs to go in as a trace.
The tutorial I followed is Basemap only and I can't find any information on how to convert the method into Cartopy whilst using Dash.
Any ideas much appreciated!
Code taken from: https://plot.ly/ipython-notebooks/basemap-maps/ Full example available there- I've just copied what I think is the key bit I need to alter
getting contours into a trace
trace1 = Contour(
z=air,
x=lon,
y=lat,
colorscale="RdBu",
zauto=False, zmin=-5, zmax=5 )
making the map
m = Basemap()
def make_scatter(x,y):
return Scatter(
x=x,
y=y,
mode='lines',
line=Line(color="black"),
name=' ' # no name on hover
)
def polygons_to_traces(poly_paths, N_poly):
'''
pos arg 1. (poly_paths): paths to polygons
pos arg 2. (N_poly): number of polygon to convert
'''
traces = [] # init. plotting list
for i_poly in range(N_poly):
poly_path = poly_paths[i_poly]
# get the Basemap coordinates of each segment
coords_cc = np.array(
[(vertex[0],vertex[1])
for (vertex,code) in poly_path.iter_segments(simplify=False)]
)
# convert coordinates to lon/lat by 'inverting' the Basemap projection
lon_cc, lat_cc = m(coords_cc[:,0],coords_cc[:,1], inverse=True)
# add plot.ly plotting options
traces.append(make_scatter(lon_cc,lat_cc))
return traces
def get_coastline_traces():
poly_paths = m.drawcoastlines().get_paths() # coastline polygon paths
N_poly = 91 # use only the 91st biggest coastlines (i.e. no rivers)
return polygons_to_traces(poly_paths, N_poly)
def get_country_traces():
poly_paths = m.drawcountries().get_paths() # country polygon paths
N_poly = len(poly_paths) # use all countries
return polygons_to_traces(poly_paths, N_poly)
traces_cc = get_coastline_traces()+get_country_traces()
data = Data([trace1]+traces_cc)
You can create a trace with the coastline as a graph object for example like this:
import plotly.graph_objects as go
import cartopy.feature as cf
# create the list of coordinates separated by nan to avoid connecting the lines
x_coords = []
y_coords = []
for coord_seq in cf.COASTLINE.geometries():
x_coords.extend([k[0] for k in coord_seq.coords] + [np.nan])
y_coords.extend([k[1] for k in coord_seq.coords] + [np.nan])
## in your app callback for dash
fig = go.Figure()
fig.add_trace(
go.Scatter(
x = x_coords,
y = y_coords,
mode = 'lines'))
The result looks like this
You can add more traces besides the COASTLINE, like BORDERS. More seem to be available here:
https://scitools.org.uk/cartopy/docs/latest/matplotlib/feature_interface.html#cartopy.feature.BORDERS
Related
I am trying to create custom cross sections of archived HRRR Grib2 output data. I had been following the cross section example provided here and followed up on all issues I had with the file format itself also on the unidata site here. I have produced some cross section plots such as the following where my x-axis utilizes latitude and y-axis utilizes isobaric pressure as seen in the plot here:
My goal is to output my plots with the x-axis showing distance from the start of my transect line to the end of the transect line. This would help me determine the horizontal scale of certain near-surface meteorological features including lakebreeze, outflow, etc. An example of what I am hoping to do is in the following photo, where the x axis indicates distance along the transect line in meters or km instead of gps coordinates:
How can I convert the coordinates to distances for my plot?
My Code:
#input points for the transect line via longitude/latitude coordinates
startpoint = (42.857, -85.381)
endpoint = (43.907, -83.910)
# Import
grib = pygrib.open('file.grib2')
#use grib message to apply lat/long info to data set
msg = grib.message(1)
#Convert grib file into xarray and assign x and y coordinate values
#(HRRR utilizes lambert_conformal_conic by default remember this)
ds = xr.open_dataset(file, engine="cfgrib",
backend_kwargs={'filter_by_keys':{'typeOfLevel': 'isobaricInhPa'}})
ds = ds.metpy.assign_crs(CRS(msg.projparams).to_cf()).metpy.assign_y_x()
#metpy cross section function to create a transect line and create cross section.
cross = cross_section(ds, startpoint, endpoint).set_coords(('latitude', 'longitude'))
#create variables
temperature = cross['t']
pressure = cross['isobaricInhPa']
cross['Potential_temperature'] = mpcalc.potential_temperature(cross['isobaricInhPa'],cross['t'])
cross['u_wind'] = cross['u'].metpy.convert_units('knots')
cross['v_wind'] = cross['v'].metpy.convert_units('knots')
cross['t_wind'], cross['n_wind'] = mpcalc.cross_section_components(cross['u_wind'],cross['v_wind'])
cross['qv'] = cross['q'] *1000* units('g/kg')
#HRRR plot test
fig = plt.figure(1, figsize=(20,9))
ax = plt.axes()
#levels = np.linspace(325,365,50)
temp = ax.contourf(cross['latitude'], cross['isobaricInhPa'], cross['qv'], 100, cmap='rainbow')
clb = fig.colorbar(temp)
clb.set_label('g $\mathregular{kg^{-1}}$')
theta_contour = ax.contour(cross['latitude'], cross['isobaricInhPa'], cross['Potential_temperature'],
400, colors='k', linewidths=2)
theta_contour.clabel(theta_contour.levels[1::2], fontsize=8, colors='k', inline=1,
inline_spacing=8, fmt='%i', rightside_up=True, use_clabeltext=True)
ax.set_ylim(775,1000)
ax.invert_yaxis()
plt.title('HRRR contour fill of Mixing ratio(g/kg), contour of Potential Temperature (K),\n Tangential/Normal Winds (knots)')
plt.title('Run: '+date+'', loc='left', fontsize='small')
plt.title('Valid: '+date+' '+f_hr, loc='right', fontsize='small')
plt.xlabel('Latitude')
plt.ylabel('Pressure (hPa)')
wind_slc_vert = list(range(0, 19, 2)) + list(range(19, 29))
wind_slc_horz = slice(5, 100, 5)
ax.barbs(cross['latitude'][wind_slc_horz], cross['isobaricInhPa'][wind_slc_vert],
cross['t_wind'][wind_slc_vert, wind_slc_horz],
cross['n_wind'][wind_slc_vert, wind_slc_horz], color='k')
# Adjust y-axis to log scale
ax.set_yscale('symlog')
ax.set_yticklabels(np.arange(1000, 775,-100))
#ax.set_ylim(cross['isobaricInhPa'].max(), cross['isobaricInhPa'].min())
ax.set_yticks(np.arange(1000, 775,-100))
plt.show()
You should be able to do this using PyPROJ's Geod class, which is what MetPy uses under the hood to calculate e.g. lat_lon_grid_deltas. Something like this I think will work:
import pyproj
geod = pyproj.Geod(ellps='sphere')
_, _, dist = geod.inv(cross['longitude'][0], cross['latitude'][0],
cross['longitude'], cross['latitude'])
I haven't actually tried that code, so you may need to convert the xarray DataArrays into numpy plain arrays.
I'm using plotly's Scattermapbox to overlay a map with a shaded image of polygons created by datashader's shade function (based on https://plotly.com/python/datashader/), but the projections do not seem to align, see picture below. Any suggestions how I can overcome this problem using plotly's Scattermapbox and datashader?
Reproducible example:
import geopandas as gpd
import plotly.graph_objects as go
import spatialpandas as spd
import datashader as ds
from colorcet import fire
import datashader.transfer_functions as tf
# load data
world = gpd.read_file(
gpd.datasets.get_path('naturalearth_lowres')
)
# world = world.to_crs(epsg=3857)
# create spatialpandas DataFrame
df_world = spd.GeoDataFrame(world)
# create datashader canvas and aggregate
cvs = ds.Canvas(plot_width=1000, plot_height=1000)
agg = cvs.polygons(df_world, geometry='geometry', agg=ds.mean('pop_est'))
# create shaded image
tf.shade(agg, cmap=fire)
shaded image
# create shaded image and convert to Python image
img = tf.shade(agg, cmap=fire)[::-1].to_pil()
coords_lat, coords_lon = agg.coords["y"].values, agg.coords["x"].values
# Corners of the image, which need to be passed to mapbox
coordinates = [
[coords_lon[0], coords_lat[0]],
[coords_lon[-1], coords_lat[0]],
[coords_lon[-1], coords_lat[-1]],
[coords_lon[0], coords_lat[-1]],
]
fig = go.Figure(go.Scattermapbox())
fig.update_layout(
mapbox_style="open-street-map",
mapbox_layers=[
{
"sourcetype": "image",
"source": img,
"coordinates": coordinates,
}
]
)
fig.show()
overlayed map
I read that Scattermapbox only supports Mercator projection which I found confusing as the examples in plotly's documentation seem to be in long/lat format, but I tried converting the coordinates of the GeoDataFrame to epsg 3857, see
# world = world.to_crs(epsg=3857)
The results is that the shaded image becomes invisible. Any help would be highly appreciated.
Have you tried with epsg:4326? In my case, I use this one and the geometries are placed correctly.
On the other hand, with geopandas to convert the geometry column of the dataframe you have to use the parameter "inplace=True".
We have discovered the solution to this issue: Below is each step / function code and description:
Imports for reference :
import datashader as ds
import datashader.transfer_functions as tf
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import rasterio
import shapely.geometry
import xarray as xr
_helper_add_pseudomercator_optimized : Creates array from the meshgrid with the proper mercator coordinates from the original raster with epsg:4326.
def _helper_add_pseudomercator_optimized(raster):
"""Adds mercator coordinates epsg:3857 from a raster with epsg:4326.
Originally defined as `add_psuedomercator_adam_manuel_optimized`
Args:
raster: xr.DataArray: `xr.DataArray` to transform coordinates
Returns:
`xr.DataArray` with coordinates (x, y) transformed from epsg:4326 to epsg:3857
"""
# Transformer that converts coordinates from epsg 4326 to 3857
gcs_to_3857 = Transformer.from_crs(4326, 3857, always_xy=True)
x_vals = list(raster.x.values.squeeze()) # x values from the raster dimension x
y_vals = list(raster.y.values.squeeze()) # x values from the raster dimension x
# Allows transformation of non-square coordinates
y_dummy_vals = [raster.y.values[0] for v in raster.x.values] # dummy values
x_dummy_vals = [raster.x.values[0] for v in raster.y.values] # dummy values
x, _ = gcs_to_3857.transform(x_vals, y_dummy_vals) # Obtain x output here only
_, y = gcs_to_3857.transform(x_dummy_vals, y_vals) # Obtain y output here only\
# Create meshgrid with the x and y mercator converted coordinates
lon, lat = np.meshgrid(x, y)
# Add meshgrid to raster -> raster now has mercator coordinates for every point
raster["x_mercator"] = xr.DataArray(lon, dims=("y", "x"))
raster["y_mercator"] = xr.DataArray(lat, dims=("y", "x"))
return raster
def _helper_affine_transform(raster):
"""Create new affine from a raster. Used to get new affine from the transformed affine.
Args:
raster: xr.DataArray: `xr.DataArray` to get the original affine and then transform
Returns:
New affine transform for a coarsened array
"""
res = (raster.x[-1].values - raster.x[0].values) / raster.x.shape[0]
scale = Affine.scale(res, -res)
transform = (
Affine.translation(raster.x[0].values - res / 2, raster.y[0].values - res / 2)
* scale
)
return transform
def _helper_to_datashader_quadmesh(raster, y="lat", x="lon"):
"""Create lower level quadmesh with data based on flood raster. Map Flooding
to lower level map.
Args:
raster: xr.DataArray: `xr.DataArray` raster of flooded regions
Returns:
`datashader.Canvas` based on quadmesh from original flood raster
"""
cvs = ds.Canvas(plot_height=5000, plot_width=5000)
z = xr.DataArray(
raster.values.squeeze(),
dims=["y", "x"],
coords={
"Qy": (["y", "x"], raster[y].values),
"Qx": (["y", "x"], raster[x].values),
},
name="z",
)
return cvs.quadmesh(z, x="Qx", y="Qy")
def _helper_img_coordinates(raster):
"""Get coordinates of the corners of the baseline raster.
Args:
raster: xr.DataArray: `xr.DataArray` to get corner coordinates from
Returns:
coordinates of where to plot the flooded raster on the map
"""
coords_lat, coords_lon = (raster.y.values, raster.x.values)
if len(coords_lat.shape) > 1:
coords_lat = coords_lat[:, 0]
coords_lon = coords_lon[0, :]
coordinates = [
[coords_lon[0], coords_lat[0]],
[coords_lon[-1], coords_lat[0]],
[coords_lon[-1], coords_lat[-1]],
[coords_lon[0], coords_lat[-1]],
]
return coordinates
All operations together for the below sequence :
# Add mercator coordinates to the raster
raster = _helper_add_pseudomercator_optimized(raster)
# Create quadmesh from the burned raster
agg_mesh = _helper_to_datashader_quadmesh(raster, x="x_mercator", y="y_mercator")
# Don't plot values where the flooding is zero
agg_mesh = agg_mesh.where(agg_mesh < 0)
# Convert to datashader shade
im = tf.shade(agg_mesh, Theme.color_scale)
# Convert to image
img = im.to_pil()
# Get coordinates to plot raster on map
coordinates = _helper_img_coordinates(baseline_raster)
Then this image produced by datashader can be added to a plotly plot using the plotly objects layer, and providing this layer to the figure
layer = go.layout.mapbox.Layer(
below="water",
coordinates=coordinates,
sourcetype="image",
source=img,
)
This question already has answers here:
Setting Transparency Based on Pixel Values in Matplotlib
(3 answers)
Closed 3 years ago.
I am interpolating point data to generate dynamic flood inundation maps in a for loop. This produces a flood map in each iteration where the pixel values show the probability of water presence. However, I'm unable to make the dry pixels (values < 0.2) transparent so the base map can be seen when added.
Initially I created a geodataframe of predicted probabilities. Then generated raster files using rasterio library using the condition ( <0.2 = np.nan) to set dry pixels. My code is:
geom = [Point(xy) for xy in zip(df.X, df.Y)]
crs = {'init': 'epsg:27700'}
gdf = geopandas.GeoDataFrame(df, crs=crs, geometry=geom)
###Rasterization
#Set up filenames
rst_fn = 'dem_9m_study_area.asc' #template raster
out_fn = 'Raster.tif'
rst = rasterio.open(rst_fn)
#copy and update the metadata from the input raster for the output
meta = rst.meta.copy()
meta.update(compress='lzw')
with rasterio.open(out_fn, 'w', **meta) as out:
out_arr = out.read(1)
shapes = ((geom,value) for geom, value in zip(gdf.geometry, gdf.Prob))
burned = features.rasterize(shapes=shapes, fill=0, out=out_arr, transform=out.transform)
burned[burned < 0.2] = np.nan
out.write_band(1, burned)
Now i would like to import the saved raster and plot it over a background raster which is also in the same coordinate (EPSG:27700), and also show the color bar.
I tried this:
plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.imshow(burned, cmap='Blues')
plt.colorbar()
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Flood Extent: T=%i h'%op)
Extent with values <0.2 set to nan
this works fine without the background though the x and y coordinates are not showing correctly. But does not work if I add this to the above code:
bmap = rasterio.open("background_upton.tif") #import basemap
src = bmap.read(1)
plt.imshow(src, cmap = 'pink')
I also tried the method described in "Adding a background map to plots": https://geopandas.readthedocs.io/en/latest/gallery/plotting_basemap_background.html
But this does not seem to solve the problem. It would be great if I can get some suggestions how to solve the issue.
I want to overlay the extent map using this background image
Try setting the minimum value for your color map and then specifying values below the minimum to be completely transparent.
I'll create an example array with values of either 1 or 2.
import numpy as np
import matplotlib.pyplot as plt
arr = np.ones([64,64])
arr[0:32] = 2
plt.imshow(arr, cmap='viridis')
plt.colorbar()
Now say we want values of 1 (purple) to be transparent in this case. We can do that by setting color map lower limit and then specifying how to map values below the limit:
cmap = plt.get_cmap('viridis')
cmap.set_under('k', alpha=0)
plt.imshow(arr, cmap=cmap, vmin=1.5)
plt.colorbar()
'k' is actually black but alpha=0 makes it transparent
I'm trying to plot circles on a miller projection map using a center latitude, longitude and radius. I can't get the circles to show up on the map projection. I've tried plotting them using different techniques as shown in the links.
How to plot a circle in basemap or add artiste
How to make smooth circles on basemap projections
Here is my code:
def plot_notams(dict_of_filtered_notams):
''' Create a map of the US and plot all NOTAMS from a given time period.'''
'''Create the map'''
fig = plt.figure(figsize=(8,6), dpi=200)
ax = fig.add_subplot(111)
m = Basemap(projection='mill',llcrnrlat=20, urcrnrlat=55, llcrnrlon=-135, urcrnrlon=-60, resolution='h')
m.drawcoastlines()
m.drawcountries(linewidth=2)
m.drawstates()
m.fillcontinents(color='coral', lake_color='aqua')
m.drawmapboundary(fill_color='aqua')
m.drawmeridians(np.arange(-130, -65, 10), labels=[1,0,0,1], textcolor='black')
m.drawparallels(np.arange(20, 60, 5), labels=[1,0,0,1], textcolor='black')
''' Now add the NOTAMS to the map '''
notam_data = dict_of_filtered_notams['final_notam_list']
for line in notam_data:
notam_lat = float(line.split()[0])
notam_lon = float(line.split()[1])
coords = convert_coords(notam_lon, notam_lat)
notam_lon, notam_lat = coords[0], coords[1]
FL400_radius = np.radians(float(line.split()[2]))
x,y = m(notam_lon, notam_lat)
print("notam_lon = ",notam_lon, "notam_lat = ", notam_lat,"\n")
print("x,y values = ",'%.3f'%x,",",'%.3f'%y,"\n")
print("FL400_radius = ",('% 3.2f' % FL400_radius))
print("")
cir = plt.Circle((x,y), FL400_radius, color="white", fill=False)
ax.add_patch(cir)
(The convert_coords function is simply formatting the notam_lon/notam_lat values into a usable format as shown in the data below.)
Here is what my data looks like (you can see where it's being printed in the code above):
notam_lon = -117.7839 notam_lat = 39.6431
x,y values = 1914342.075 , 2398770.441
FL400_radius = 6.98
Here's an image of what my code above produces:
I also tried using the map.plot() function (specifically, m.plot(x,y, "o")) in place of "ax.add_patch(cir)." That worked but plotted points or "o's," of course. Here's the image produced by replacing "ax.add_patch(cir)" with "m.plot(x,y, "o")."
And as a final note, I'm using basemap 1.2.0-1 and matplotlib 3.0.3. I haven't found any indication that these versions are incompatible. Also, this inability to plot a circle wasn't an issue 2 months ago when I did this last. I'm at a loss here. I appreciate any feedback. Thank you.
To plot circles on a map, you need appropriate locations (x,y) and radius. Here is a working code and resulting plot.
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
# make up 10 data points for location of circles
notam_lon = np.linspace(-117.7839, -100, 10)
notam_lat = np.linspace(39.6431, 52, 10)
# original radius of circle is too small
FL400_radius = 6.98 # what unit?
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
m = Basemap(projection='mill', llcrnrlat=20, urcrnrlat=55, llcrnrlon=-135, urcrnrlon=-60, resolution='l')
# radiusm = (m.ymax-m.ymin)/10. is good for check plot
radiusm = FL400_radius*10000 # meters, you adjust as needed here
for xi,yi in zip(notam_lon, notam_lat):
# xy=m(xi,yi): conversion (long,lat) to (x,y) on map
circle1 = plt.Circle(xy=m(xi,yi), radius=radiusm, \
edgecolor="blue", facecolor="yellow", zorder=10)
#ax.add_patch(circle1) # deprecated
ax.add_artist(circle1) # use this instead
m.drawcoastlines()
m.drawcountries(linewidth=2)
m.drawstates()
m.fillcontinents(color='coral', lake_color='aqua')
# m.drawmapboundary(fill_color='aqua') <-- causes deprecation warnings
# use this instead:
rect = plt.Rectangle((m.xmin,m.ymin), m.xmax-m.xmin, m.ymax-m.ymin, facecolor="aqua", zorder=-10)
ax.add_artist(rect)
m.drawmeridians(np.arange(-130, -65, 10), labels=[1,0,0,1], textcolor='black')
m.drawparallels(np.arange(20, 60, 5), labels=[1,0,0,1], textcolor='black')
plt.show()
The output map:
Hope this is useful.
Given a geographical coordinate in U.S., how to find out if it is in Urban or Rural areas?
I have about 10000 geographical coordinates all in the U.S., and I want to use Python + basemap to find out if a point is urban or rural.
I'm not sure which library or shape file to use.
I'll need a function like this:
def is_urban(coordinate):
# use the shapefile
urban = False
return urban
import shapefile
from shapely.geometry import Point # Point class
from shapely.geometry import shape # shape() is a function to convert geo objects through the interface
pt = (-97.759615,30.258773) # an x,y tuple
shp = shapefile.Reader('/home/af/Downloads/cb_2016_us_ua10_500k/cb_2016_us_ua10_500k.shp') #open the shapefile
all_shapes = shp.shapes() # get all the polygons
all_records = shp.records()
def is_urban(pt):
result = False
for i in range(len(all_shapes)):
boundary = all_shapes[i] # get a boundary polygon
#name = all_records[i][3] + ', ' + all_records[i][4] # get the second field of the corresponding record
if Point(pt).within(shape(boundary)): # make a point and see if it's in the polygon
result = True
return result
result = is_urban(pt)
I ended up using shapely and shapefile downloaded from
https://www.census.gov/geo/maps-data/data/cbf/cbf_ua.html, which has urban areas of the U.S., so if a point is not within any of these areas, it's rural.
I tested it, and it works to my expectation.