Which query reproduces city street network orientation plots using OSMnx? - python

We are interested in reproducing the street network orientation plots from this blog post using OSMnx. However, using the modified example notebook code
import matplotlib.pyplot as plt
import numpy as np
import osmnx as ox
ox.config(log_console=True)
weight_by_length = False
ox.__version__
# define the study sites as label : query
places = {
"Berlin": "Berlin, Germany",
"Munich": "München, Germany",
"Minneapolis": "Minneapolis, MN, USA",
}
# verify OSMnx geocodes each query to what you expect (i.e., a [multi]polygon geometry)
gdf = ox.geocode_to_gdf(list(places.values()))
gdf
# create figure and axes
n = len(places)
ncols = int(np.ceil(np.sqrt(n)))
nrows = int(np.ceil(n / ncols))
figsize = (ncols * 5, nrows * 5)
fig, axes = plt.subplots(nrows, ncols, figsize=figsize, subplot_kw={"projection": "polar"})
# plot each city's polar histogram
for ax, place in zip(axes.flat, sorted(places.keys())):
print(ox.utils.ts(), place)
# get undirected graphs with edge bearing attributes
G = ox.graph_from_place(place, network_type="drive")
Gu = ox.add_edge_bearings(ox.get_undirected(G))
fig, ax = ox.bearing.plot_orientation(Gu, ax=ax, title=place, area=True)
our results differ from those in the blog post:
our Berlin Geoff's Berlin our Minneapolis Geoff's Minneapolis (sorry, I'm not allowed to embed images)
As Geoff mentions in his blog post, that "cities proper (municipalities), not wider metro areas or urban agglomerations" are used, we speculated that choosing the appropriate admin-level in the query might help. However using, e.g.,
gdf = ox.geometries.geometries_from_place('Minneapolis, MN, USA', {'boundary':'administrative','admin_level':'8'})
returns a multitude of polygons, where it is unclear to us how to select from them. The unary union
gdf_unified = gdf[gdf.admin_level=='8'].unary_union
does not seem to do the trick:
Minneapolis, unary union, admin-level 8
Can anyone advise which query to use to successfully reproduce the plots?

Related

Why is GeoPandas overlay intersection empty between LINESTRING and MULTIPOLYGON geometries?

I want to split British Columbia's road network by health regions. Specifically, for each health region I want to save a shape file of the roads within that region. The roads have a LINESTRING geometry while the health regions have a MULTIPOLYGON geometry.
I thought that the geopandas overlay with how=intersection might help me do this, but it telling me that the intersection is empty. That doesn't make sense in light of the fact that plotting (not shown) the roads and the health authorities together shows clear overlap, so I suspect I have misunderstood how geopandas handles intersection of different geometries.
Here is some minimum code, although it won't run unless you happen to locally have these files stored.
import geopandas as gpd
import matplotlib.pyplot as plt
health_regions_df = gpd.read_file(
'../data/canada.geojson'
)
bc_roads_df = gpd.read_file(
'../data/geobase_nrn_rrn/bc/nrn_rrn_bc_shp_en.zip',
layer='NRN_bc_14_0_ROADSEG'
)
bc_roads_df.to_crs(
'EPSG:4326',
inplace=True
)
bc_hrs = health_regions_df[health_regions_df['province'] == 'British Columbia']
intersection = gpd.overlay(bc_hrs, bc_roads_df, how='intersection')
print(intersection)
The above gives the following printout:
Empty GeoDataFrame
Columns: [fid, hr_uid, province, engname, frename, count, NID, ROADSEGID, ADRANGENID, DATASETNAM, SPECVERS, ACCURACY, ACQTECH, PROVIDER, CREDATE, REVDATE, METACOVER, ROADCLASS, RTNUMBER1, RTNUMBER2, RTNUMBER3, RTNUMBER4, RTNUMBER5, RTENAME1FR, RTENAME2FR, RTENAME3FR, RTENAME4FR, RTENAME1EN, RTENAME2EN, RTENAME3EN, RTENAME4EN, EXITNBR, NBRLANES, PAVSTATUS, PAVSURF, UNPAVSURF, STRUCTID, STRUCTTYPE, STRUNAMEEN, STRUNAMEFR, L_ADDDIRFG, L_HNUMF, L_HNUML, L_STNAME_C, L_PLACENAM, R_ADDDIRFG, R_HNUMF, R_HNUML, R_STNAME_C, R_PLACENAM, CLOSING, ROADJURIS, SPEED, TRAFFICDIR, geometry]
Index: []
Why is GeoPandas overlay intersection empty between LINESTRING and MULTIPOLYGON geometries?
The overlay method should be applied to a geodataframe or series:
intersection = bc_hrs.overlay(bc_roads_df, how='intersection')
Here is some sample code to illustrate:
from shapely.geometry import Polygon
from shapely.geometry import LineString
import geopandas
from matplotlib import pyplot as plt
polys1 = geopandas.GeoSeries([Polygon([(3,3), (3,7), (7,7), (7,3), (3,3)])])
line1= geopandas.GeoSeries([LineString([(0,0), (10,10)])])
df1 = geopandas.GeoDataFrame({'geometry': polys1})
df2 = geopandas.GeoDataFrame({'geometry': line1})
# Perform the intersect operation
intersect = df1.overlay(df2, how='intersection', keep_geom_type=False)
# Plot the process
fig, ax = plt.subplots(2,2, figsize=(8, 8))
df1.plot(ax=ax[0,0], color='blue', alpha=0.7)
df2.plot(ax=ax[0,0], color='magenta')
df1.plot(ax=ax[0,1], color='blue', alpha=0.7)
df2.plot(ax=ax[0,1], color='magenta', alpha=0.7)
intersect.plot(ax=ax[0,1], color='black')
intersect.plot(ax=ax[1,0], color='black', legend=True)
ax[0,0].set_title('Original')
ax[0,1].set_title('Intersection with polygon & Line')
ax[1,0].set_title('Line')
ax[1,1].set_visible(False)
custom_xlim = (0, 10)
custom_ylim = (0, 10)
plt.setp(ax, xlim=custom_xlim, ylim=custom_ylim)
plt.show()
Image of plots

Fill U.S. counties by value using Python & Cartopy?

I'd like to know how to fill in a map of U.S. counties by value (i.e., a chloropleth map), using Python 3 and Cartopy, and I haven't yet found anything online to guide me in that. That filled value could be, for instance, highest recorded tornado rating (with counties left blank for no recorded tornadoes), or even something arbitrary such as whether I've visited (=1) or lived (=2) in the county. I found a helpful MetPy example to get the county boundaries on a map:
https://unidata.github.io/MetPy/latest/examples/plots/US_Counties.html
What I envision is somehow setting a list (or dictionary?) of county names to a certain value, and then each value would be assigned to a particular fill color. This is my current script, which generates a nice blank county map of the CONUS/lower 48 (though I'd eventually also like to add Alaska/Hawaii insets).
import cartopy
import cartopy.crs as ccrs
import matplotlib as mpl
import matplotlib.pyplot as plt
from metpy.plots import USCOUNTIES
plot_type = 'png'
borders = cartopy.feature.BORDERS
states = cartopy.feature.NaturalEarthFeature(category='cultural', scale='10m', facecolor='none', name='admin_1_states_provinces_lakes')
oceans = cartopy.feature.OCEAN
lakes = cartopy.feature.LAKES
mpl.rcParams['figure.figsize'] = (12,10)
water_color = 'lightblue'
fig = plt.figure()
ax = plt.axes(projection=ccrs.LambertConformal(central_longitude=-97.5, central_latitude=38.5, standard_parallels=(38.5,38.5)))
ax.set_extent([-120, -74, 23, 50], ccrs.Geodetic())
ax.coastlines()
ax.add_feature(borders, linestyle='-')
ax.add_feature(states, linewidth=0.50, edgecolor='black')
ax.add_feature(oceans, facecolor=water_color)
ax.add_feature(lakes, facecolor=water_color, linewidth=0.50, edgecolor='black')
ax.add_feature(USCOUNTIES.with_scale('500k'), linewidth=0.10, edgecolor='black')
plt.savefig('./county_map.'+plot_type)
plt.close()
Any ideas or tips on how to assign values to counties and fill them accordingly?
So Cartopy's shapereader.Reader can give you access to all of the records in the shapefile, including their attributes. Putting this together with MetPy's get_test_data to get access to the underlying shapefile you can get what you want, assuming you have a dataset that maps e.g. FIPSCODE to EF rating:
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
cmap = plt.get_cmap('magma')
norm = plt.Normalize(0, 5)
# Fake tornado dataset with a value for each county code
tor_data = dict()
# This will only work (have access to the shapefile's database of
# attributes after it's been download by using `USCOUNTIES` or
# running get_test_data() for the .shx and .dbf files as well.
for rec in shpreader.Reader(get_test_data('us_counties_20m.shp',
as_file_obj=False)).records():
# Mimic getting data, but actually getting a random number
# GEOID seems to be the FIPS code
max_ef = tor_data.get(rec.attributes['GEOID'], np.random.randint(0, 5))
# Normalize the data to [0, 1] and colormap manually
color = tuple(cmap(norm(max_ef)))
# Add the geometry to the plot, being sure to specify the coordinate system
ax.add_geometries([rec.geometry], crs=ccrs.PlateCarree(), facecolor=color)
ax.set_extent((-125, -65, 25, 48))
That gives me:
I'm not sure about passing in a dict, but you can pass in a list to facecolor.
ax.add_feature(USCOUNTIES.with_scale('500k'), linewidth=0.10, edgecolor='black', facecolor=["red", "blue", "green"])
If you know how many counties there are you can make a list that long by:
import matplotlib.cm as cm
import numpy as np
number_of_counties = 3000
color_scale = list(cm.rainbow(np.linspace(0, 1, number_of_counties)))
ax.add_feature(USCOUNTIES.with_scale('500k'), linewidth=.10, edgecolor="black", facecolor=color_scale)
but they didn't make it easy to extract the names from USCOUNTIES. You can see where it is defined in your source code:
from metpy import plots
print(plots.__file__)
If you go inside the directory printed there is a file named cartopy_utils.py and inside the class definition for class MetPyMapFeature(Feature): you will see USCOUNTIES. You might have better luck than I did mapping county names to the geometric shapes.
EDIT: Also, I just used cm.rainbow as an example, you can choose from any color map https://matplotlib.org/stable/tutorials/colors/colormaps.html. Not sure if it even goes up to 3000, but you get the idea.

Excluding points within list of buildings' shapes in Python

I have a dataframe that contains thousands of points with geolocation (longitude, latitude) for Washington D.C. The following is a snippet of it:
import pandas as pd
df = pd.DataFrame({'lat': [ 38.897221,38.888100,38.915390,38.895100,38.895100,38.901005,38.960491,38.996342,38.915310,38.936820], 'lng': [-77.031048,-76.898480,-77.021380,-77.036700,-77.036700 ,-76.990784,-76.862907,-77.028131,-77.010403,-77.184930]})
If you plot the points in the map you can see that some of them are clearly within some buildings:
import folium
wash_map = folium.Map(location=[38.8977, -77.0365], zoom_start=10)
for index,location_info in df.iterrows():
folium.CircleMarker(
location=[location_info["lat"], location_info["lng"]], radius=5,
fill=True, fill_color='red',).add_to(wash_map)
wash_map.save('example_stack.html')
import webbrowser
import os
webbrowser.open('file://'+os.path.realpath('example_stack.html'), new=2)
My goal is to exclude all the points that are within buildings. For that, I first download bounding boxes for the city buildings and then try to exclude points within those polygons as follows:
import osmnx as ox
#brew install spatialindex this solves problems in mac
%matplotlib inline
ox.config(log_console=True)
ox.__version__
tags = {"building": True}
gdf = ox.geometries.geometries_from_point([38.8977, -77.0365], tags, dist=1000)
gdf.shape
For computational simplicity I have requested the shapes of all buildings around the White house with a radius of 1 km. On my own code I have tried with bigger radiuses to make sure all the buildings are included.
In order to exclude points within the polygons I developed the following function (which includes the shape obtention):
def buildings(df,center_point,dist):
import osmnx as ox
#brew install spatialindex this solves problems in mac
%matplotlib inline
ox.config(log_console=True)
ox.__version__
tags = {"building": True}
gdf = ox.geometries.geometries_from_point(center_point, tags,dist)
from shapely.geometry import Point,Polygon
# Next step is to put our coordinates in the correct shapely format: remember to run the map funciton before
#df['within_building']=[]
for point in range(len(df)):
if gdf.geometry.contains(Point(df.lat[point],df.lng[point])).all()==False:
df['within_building']=False
else :
df['within_building']=True
buildings(df,[38.8977, -77.0365],1000)
df['within_building'].all()==False
The function always returns that points are outside building shapes although you can clearly see in the map that some of them are within. I don't know how to plot the shapes over my map so I am not sure if my polygons are correct but for the coordinates they appear to be so. Any ideas?
The example points you provided don't seem to fall within those buildings' footprints. I don't know what your points' coordinate reference system is, so I guessed EPSG4326. But to answer your question, here's how you would exclude them, resulting in gdf_points_not_in_bldgs:
import geopandas as gpd
import matplotlib.pyplot as plt
import osmnx as ox
import pandas as pd
# the coordinates you provided
df = pd.DataFrame({'lat': [38.897221,38.888100,38.915390,38.895100,38.895100,38.901005,38.960491,38.996342,38.915310,38.936820],
'lng': [-77.031048,-76.898480,-77.021380,-77.036700,-77.036700 ,-76.990784,-76.862907,-77.028131,-77.010403,-77.184930]})
# create GeoDataFrame of point geometries
geom = gpd.points_from_xy(df['lng'], df['lat'])
gdf_points = gpd.GeoDataFrame(geometry=geom, crs='epsg:4326')
# get building footprints
tags = {"building": True}
gdf_bldgs = ox.geometries_from_point([38.8977, -77.0365], tags, dist=1000)
gdf_bldgs.shape
# get all points that are not within a building footprint
mask = gdf_points.within(gdf_bldgs.unary_union)
gdf_points_not_in_bldgs = gdf_points[~mask]
print(gdf_points_not_in_bldgs.shape) # (10, 1)
# plot buildings and points
ax = gdf_bldgs.plot()
ax = gdf_points.plot(ax=ax, c='r')
plt.show()
# zoom in to see better
ax = gdf_bldgs.plot()
ax = gdf_points.plot(ax=ax, c='r')
ax.set_xlim(-77.04, -77.03)
ax.set_ylim(38.89, 38.90)
plt.show()

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

Street names in OSMNX network maps

I am constructing street networks on osmnx using below code.I see that I can print lat/lon information, but
Is there a way to include street/road names in network maps as well? I don't see how to do this in the documentation. Thanks!
import osmnx as ox
G = ox.graph_from_bbox(37.79, 37.78, -122.41, -122.43, network_type='drive')
G_projected = ox.project_graph(G)
ox.plot_graph(G_projected)
Output:
Here's how you annotate your map with OSMnx to show street/road names (or any other edge attributes in the plot). The same logic would apply to labeling nodes instead.
import matplotlib.pyplot as plt
import osmnx as ox
ox.config(use_cache=True, log_console=True)
G = ox.graph_from_address('Piedmont, CA, USA', dist=200, network_type='drive')
G = ox.get_undirected(G)
fig, ax = ox.plot_graph(G, bgcolor='k', edge_linewidth=3, node_size=0,
show=False, close=False)
for _, edge in ox.graph_to_gdfs(G, nodes=False).fillna('').iterrows():
c = edge['geometry'].centroid
text = edge['name']
ax.annotate(text, (c.x, c.y), c='w')
plt.show()
The only aesthetic challenge being the label placement problem, which is one of the most difficult problems in computational cartography.

Categories

Resources