Plotting a Map with geopy and matplotlib in Jupyter Notebook - python

I am trying to plot a map of the US and mark the various cities across the country. I got the map to work. But I am having two issues: the first is, I am getting this error message:
AttributeError: 'NoneType' object has no attribute 'longitude'
Secondly, I have tried to enlarge the graph using the plt.figsize attribute however my map still stays the same size.
Lastly, this is not really an issue but what if i wanted to label the dots with the city names How can i do so?
Here is my code for the map:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from geopy.geocoders import Nominatim
import math
city_list = list(flight_data["OriginCityName"].unique())
cities = city_list
scale = 1
map = Basemap(width=10000000,height=6000000,projection='lcc',
resolution=None,lat_1=45.,lat_2=55,lat_0=50,lon_0=-107.)
plt.figure(figsize=(19,20))
map.bluemarble()
# Get the location of each city and plot it
geolocator = Nominatim()
for city in cities:
loc = geolocator.geocode(city)
if not loc:
print("Could not locate {}".format(city))
continue
x, y = map(loc.longitude, loc.latitude)
map.plot(x,y,marker='o',color='Red',markersize=5)
plt.annotate(city, xy = (x,y), xytext=(-20,20))
plt.show()

I guess there is something in your city_list Nominatim can't resolve. I added a check for that below.
You have to call figure(num=1,figsize=(8,9)) before you plot anything (here: the map).
You can use plt.annotate, see below.
Hope this helps.
for city in cities:
loc = geolocator.geocode(city)
if not loc:
print("Could not locate {}".format(city))
continue
x, y = map(loc.longitude, loc.latitude)
map.plot(x,y,marker='o',color='Red',markersize=int(math.sqrt(count))*scale)
plt.annotate(city, xy = (x,y), xytext=(-20,20))

Related

Python: marker label on the map using gmap.marker_layer option not working using hover_text

I am trying to label the markers i am plotting on google maps using gmap. The markers are plotted but the label (their name) are not. I using hover_text option for name to show as i hover over the marker.
Its a simple file with 3 columns --> name, lat, long.. Below is the code. Im running it on jupyter notebook
import gmaps
locations = df[['latitude', 'longitude']]
name = list(map(str, list(df["name"])))
fig = gmaps.figure(map_type = "TERRAIN", center = (-34, -59), zoom_level = 2)
markers = gmaps.marker_layer(locations, hover_text = name)
fig.add_layer(markers)
gmaps.configure(api_key = 'MY API KEY')
fig
The map plots correctly except no label.
Thanks
File:
name latitude longitude
123 -34.000000 -59.166672
124 -32.233330 -64.433327
125 -40.166672 44.133331
126 -51.216671 5.083330
127 -51.333328 4.250000
I am not sure how your data is laid out, but I have encountered a similar problem which I resolved by running the following code:
name = [name['name'] for name in marker_locations]
My data is laid in the following way:
marker_locations = [
{'name':'Hamburg, Germany',.....},
{'name':'London, England',.....},
]
Hope this helps.
The documentation gives an example on what you want:
marker_layer = gmaps.marker_layer(
locations,
hover_text=['Atucha', 'Embalse', 'Armenia', 'Br']

Plotting with folium

The task is to make an adress popularity map for Moscow. Basically, it should look like this:
https://nbviewer.jupyter.org/github/python-visualization/folium/blob/master/examples/GeoJSON_and_choropleth.ipynb
For my map I use public geojson: http://gis-lab.info/qa/moscow-atd.html
The only data I have - points coordinates and there's no information about the district they belong to.
Question 1:
Do I have to manually calculate for each disctrict if the point belongs to it, or there is more effective way to do this?
Question 2:
If there is no way to do this easier, then, how can I get all the coordinates for each disctrict from the geojson file (link above)?
import pandas as pd
import numpy as np
import geopandas as gpd
from shapely.geometry import Point
Reading in the Moscow area shape file with geopandas
districts = gpd.read_file('mo-shape/mo.shp')
Construct a mock user dataset
moscow = [55.7, 37.6]
data = (
np.random.normal(size=(100, 2)) *
np.array([[.25, .25]]) +
np.array([moscow])
)
my_df = pd.DataFrame(data, columns=['lat', 'lon'])
my_df['pop'] = np.random.randint(500, 100000, size=len(data))
Create Point objects from the user data
geom = [Point(x, y) for x,y in zip(my_df['lon'], my_df['lat'])]
# and a geopandas dataframe using the same crs from the shape file
my_gdf = gpd.GeoDataFrame(my_df, geometry=geom)
my_gdf.crs = districts.crs
Then the join using default value of 'inner'
gpd.sjoin(districts, my_gdf, op='contains')
Thanks to #BobHaffner, I tried to solve the problem using geopandas.
Here are my steps:
I download a shape-files for Moscow using this link click
From a list of tuples containing x and y (latitude and logitude) coordinates I create list of Points (docs)
Assuming that in the dataframe from the first link I have polygons I can write a simple loop for checking if the Point is inside this polygon. For details read this.

Change background map for contextily

I have this code:
import pandas as pd
import numpy as np
from geopandas import GeoDataFrame
import geopandas
from shapely.geometry import LineString, Point
import matplotlib.pyplot as plt
import contextily
''' Do Something'''
df = start_stop_df.drop('track', axis=1)
crs = {'init': 'epsg:4326'}
gdf = GeoDataFrame(df, crs=crs, geometry=geometry)
ax = gdf.plot()
contextily.add_basemap(ax)
ax.set_axis_off()
plt.show()
Basically, this generates a background map that is in Singapore. However, when I run it, I get the following error: HTTPError: Tile URL resulted in a 404 error. Double-check your tile url:http://tile.stamen.com/terrain/29/268436843/268435436.png
However, it still produces this image:
How can I change the Tile URL? I would still like to have the map of Singapore as the base layer.
EDIT:
Also tried including this argument to add_basemap:
url ='https://www.openstreetmap.org/#map=12/1.3332/103.7987'
Which produced this error:
OSError: cannot identify image file <_io.BytesIO object at 0x000001CC3CC4BC50>
First make sure that your GeoDataframe is in Web Mercator projection (epsg=3857). Once your Geodataframe is correctly georeferenced, you can achieve this by Geopandas reprojection:
df = df.to_crs(epsg=3857)
Once you have this done, you easily choose any of the supported map styles. A full list can be found in contextily.sources module, at the time of writing:
### Tile provider sources ###
ST_TONER = 'http://tile.stamen.com/toner/tileZ/tileX/tileY.png'
ST_TONER_HYBRID = 'http://tile.stamen.com/toner-hybrid/tileZ/tileX/tileY.png'
ST_TONER_LABELS = 'http://tile.stamen.com/toner-labels/tileZ/tileX/tileY.png'
ST_TONER_LINES = 'http://tile.stamen.com/toner-lines/tileZ/tileX/tileY.png'
ST_TONER_BACKGROUND = 'http://tile.stamen.com/toner-background/tileZ/tileX/tileY.png'
ST_TONER_LITE = 'http://tile.stamen.com/toner-lite/tileZ/tileX/tileY.png'
ST_TERRAIN = 'http://tile.stamen.com/terrain/tileZ/tileX/tileY.png'
ST_TERRAIN_LABELS = 'http://tile.stamen.com/terrain-labels/tileZ/tileX/tileY.png'
ST_TERRAIN_LINES = 'http://tile.stamen.com/terrain-lines/tileZ/tileX/tileY.png'
ST_TERRAIN_BACKGROUND = 'http://tile.stamen.com/terrain-background/tileZ/tileX/tileY.png'
ST_WATERCOLOR = 'http://tile.stamen.com/watercolor/tileZ/tileX/tileY.png'
# OpenStreetMap as an alternative
OSM_A = 'http://a.tile.openstreetmap.org/tileZ/tileX/tileY.png'
OSM_B = 'http://b.tile.openstreetmap.org/tileZ/tileX/tileY.png'
OSM_C = 'http://c.tile.openstreetmap.org/tileZ/tileX/tileY.png'
Keep in mind that you should not be adding actual x,y,z tile numbers in your tile URL (like you did in your "EDIT" example). ctx will take care of all this.
You can find a working copy-pastable example and further info at GeoPandas docs.
import contextily as ctx
# Dataframe you want to plot
gdf = GeoDataFrame(df, crs= {"init": "epsg:4326"}) # Create a georeferenced dataframe
gdf = gdf.to_crs(epsg=3857) # reproject it in Web mercator
ax = gdf.plot()
# choose any of the supported maps from ctx.sources
ctx.add_basemap(ax, url=ctx.sources.ST_TERRAIN)
ax.set_axis_off()
plt.show()
Contextily's default crs is epsg:3857. However, your data-frame is in different CRS. Use the following,refer the manual here:
ctx.add_basemap(ax, crs='epsg:4326', source=ctx.providers.Stamen.TonerLite)
Please, refer to this link for using different sources such as Stamen.Toner, Stamen.Terrain etc. (Stamen.Terrain is used as default).
Also, you can cast your data frame to EPSG:3857 by using df.to_crs(). In this case, you should skip crs argument inside ctx.add_basemap() function.
im too new to add a comment but I wanted to point out to those saying in the comments that they get a 404 error. Check you capitaliations, etc. Stamen's urls are specifc on this. For instance there is not an all caps call. It is only capitalize the first letter. For example:
ctx.add_basemap(ax=ax,url=ctx.providers.Stamen.Toner, zoom=10)

How to generate Folium map using GeoDataFrame?

I have created a geoDataFrame using, and would like to create a Folium Map, plotting the population eat for each country. Do I have to create the Json file, or I can directly use the geoDataFrame file?
import folium
import fiona
import geopandas as gpd
world = fiona.open(gpd.datasets.get_path('naturalearth_lowres'))
world = gpd.GeoDataFrame.from_features([feature for feature in world])
world = world[(world.pop_est > 0) & (world.name != "Antarctica")]
I used folium.map and geojson function, but it failed to create correct JSON files.
Thanks for the help!
The m.cholopleth() code in #joris's answer is now deprecated. The following code produces the same result using the new folium.Chloropleth() function:
m = folium.Map()
folium.Choropleth(world, data=world,
key_on='feature.properties.name',
columns=['name', 'pop_est'],
fill_color='YlOrBr').add_to(m)
folium.LayerControl().add_to(m)
m
In recent releases of folium, you don't need to convert the GeoDataFrame to geojson, but you can pass it directly. Connecting the population column to color the polygons is still somewhat tricky to get correct:
m = folium.Map()
m.choropleth(world, data=world, key_on='feature.properties.name',
columns=['name', 'pop_est'], fill_color='YlOrBr')
m

Mapping GPS coordinates around Chicago using Basemap

I'm using python's matplotlib and Basemap libraries.
I'm attempting to plot a list of GPS points around the city of Chicago for a project that I'm working on but it's not working. I've looked at all of the available examples, but despite copying and pasting them verbatim (and then changing the gps points) the map fails to render with the points plotted.
Here are some example points as they are stored in my code:
[(41.98302392, -87.71849159),
(41.77351707, -87.59144826),
(41.77508317, -87.58899995),
(41.77511247, -87.58646695),
(41.77514645, -87.58515301),
(41.77538531, -87.58611272),
(41.71339537, -87.56963306),
(41.81685612, -87.59757281),
(41.81697313, -87.59910809),
(41.81695808, -87.60049861),
(41.75894604, -87.55560586)]
and here's the code that I'm using to render the map (which doesn't work).
# -*- coding: utf-8 -*-
from pymongo import *
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from collections import Counter
import ast
def routes_map():
"""
doesn't work :(
# map of chicago
"""
all_locations = [] #<-- this is the example data above
x = []
y = []
for loc in all_locations: #creates two lists for the x and y (lat,lon) coordinates
x.append(float(loc[0]))
y.append(float(loc[1]))
# llcrnrlat,llcrnrlon,urcrnrlat,urcrnrlon
# are the lat/lon values of the lower left and upper right corners
# of the map.
# resolution = 'i' means use intermediate resolution coastlines.
# lon_0, lat_0 are the central longitude and latitude of the projection.
loc = [41.8709, -87.6331]
# setup Lambert Conformal basemap.
m = Basemap(llcrnrlon=-90.0378,llcrnrlat=40.6046,urcrnrlon=-85.4277,urcrnrlat=45.1394,
projection='merc',resolution='h')
# draw coastlines.
m.drawcoastlines()
m.drawstates()
# draw a boundary around the map, fill the background.
# this background will end up being the ocean color, since
# the continents will be drawn on top.
m.drawmapboundary(fill_color='white')
x1, y1 = m(x[:100],y[:100])
m.plot(x1,y1,marker="o",alpha=1.0)
plt.title("City of Chicago Bus Stops")
plt.show()
This is what I get from running this code:
Does anyone have any tips as to what I'm doing wrong?
You are accidentally inputting latitude values as x and longitude values as y. In the example data you give, the first column is latitude and the second column is longitude, not the other way around as your code seems to think.
So use x.append(float(loc[1])) and y.append(float(loc[0])) instead of what you have.

Categories