How to add Search plugin in folium for multiple fields? - python

I'm trying to add a search bar in folium map using folium plugins.
Data:
import geopandas
states = geopandas.read_file(
"https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json",
driver="GeoJSON",
)
states_sorted = states.sort_values(by="density", ascending=False)
states_sorted.head(5).append(states_sorted.tail(5))[["name", "density"]]
def rd2(x):
return round(x, 2)
minimum, maximum = states["density"].quantile([0.05, 0.95]).apply(rd2)
mean = round(states["density"].mean(), 2)
import branca
colormap = branca.colormap.LinearColormap(
colors=["#f2f0f7", "#cbc9e2", "#9e9ac8", "#756bb1", "#54278f"],
index=states["density"].quantile([0.2, 0.4, 0.6, 0.8]),
vmin=minimum,
vmax=maximum,
)
colormap.caption = "Population Density in the United States"
id name density geometry
0 01 Alabama 94.650 POLYGON ((-87.35930 35.00118, -85.60667 34.984...
1 02 Alaska 1.264 MULTIPOLYGON (((-131.60202 55.11798, -131.5691...
2 04 Arizona 57.050 POLYGON ((-109.04250 37.00026, -109.04798 31.3...
3 05 Arkansas 56.430 POLYGON ((-94.47384 36.50186, -90.15254 36.496...
4 06 California 241.700 POLYGON ((-123.23326 42.00619, -122.37885 42.0...
Folium Map:
import folium
from folium.plugins import Search
m = folium.Map(location=[38, -97], zoom_start=4)
def style_function(x):
return {
"fillColor": colormap(x["properties"]["density"]),
"color": "black",
"weight": 2,
"fillOpacity": 0.5,
}
stategeo = folium.GeoJson(
states,
name="US States",
style_function=style_function,
tooltip=folium.GeoJsonTooltip(
fields=["name", "density"], aliases=["State", "Density"], localize=True
),
).add_to(m)
statesearch = Search(
layer=stategeo,
geom_type="Polygon",
placeholder="Search for a US State",
collapsed=False,
search_label="name",
weight=3,
).add_to(m)
folium.LayerControl().add_to(m)
colormap.add_to(m)
m
In the above map user can search only by US state name, is it possible to include multiple fields for search, like searching based on density/ id / Name??

Related

Folium Choropleth Highlight map elements is not working

This is my first question so hopefully, I'm asking it in a way that makes sense. If not, please correct me.
I want to use the highlight parameter in folium.Choropleth to achieve this sort of behaviour on mouse hover:
but it's not working.
I noticed one strange thing:
I also have folium.features.GeoJsonTooltip in my code and if I disable it, highlighting works. But when it's enabled, highlighting does not work. When folium.features.GeoJsonTooltip is enabled, the code compiles without errors but it's not highlighting countries as it should. All other functionalities work as expected.
folium.Choropleth(
geo_data=df1,
name="choropleth",
data=df3,
columns=["Country", "Estimate_UN"],
key_on="feature.properties.name",
fill_color="YlGnBu",
fill_opacity=0.8,
line_opacity=0.5,
legend_name="GDP Per Capita (in EUR)",
bins=bins,
highlight=True
).add_to(my_map)
Here's my full code:
import folium
import pandas
import geopandas
pandas.set_option('display.max_columns',25)
pandas.set_option('display.width',2000)
pandas.set_option('display.max_rows',300)
url = 'http://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal)_per_capita'
tables1 = pandas.read_html(url, match='Country/Territory')
df1 = tables1[0] # changes data from List to DataFrame
# makes it single index
df1.columns = ['Country', 'Region', 'Estimate_IMF', 'Year1', 'Estimate_UN', 'Year2', 'Estimate_WB', 'Year3']
# makes it two columns only (Country, Estimate_UN)
df1 = df1.drop(columns=['Region', 'Year1', 'Year2', 'Year3', 'Estimate_IMF', 'Estimate_WB'])
df1['Country'] = df1['Country'].map(lambda x: x.rstrip('*'))
df1['Country'] = df1['Country'].map(lambda x: x.strip())
df1['Country'] = df1['Country'].str.replace('United States', 'United States of America')
df1['Country'] = df1['Country'].str.replace('DR Congo', 'Dem. Rep. Congo')
df1['Country'] = df1['Country'].str.replace('Central African Republic', 'Central African Rep.')
df1['Country'] = df1['Country'].str.replace('South Sudan', 'S. Sudan')
df1['Country'] = df1['Country'].str.replace('Czech Republic', 'Czechia')
df1['Country'] = df1['Country'].str.replace('Bosnia and Herzegovina', 'Bosnia and Herz.')
df1['Country'] = df1['Country'].str.replace('Ivory Coast', """Côte d'Ivoire""")
df1['Country'] = df1['Country'].str.replace('Dominican Republic', 'Dominican Rep.')
df1['Country'] = df1['Country'].str.replace('Eswatini', 'eSwatini')
df1['Country'] = df1['Country'].str.replace('Equatorial Guinea', 'Eq. Guinea')
df1.drop(df1[df1['Estimate_UN'] == '—'].index, inplace = True)
df1['Estimate_UN'] = df1['Estimate_UN'].apply(lambda g:int(str(g)))
### --- Change 'GDP Per Capita' values in GeoJsonToolTip from format of 12345.0 (USD) to €11,604 --- ###
df2 = df1.copy()
df2['Estimate_UN'] = df2['Estimate_UN'].apply(lambda g:g*0.94) # Convert USD to EUR
df3 = df2.copy()
df2['Estimate_UN'] = df2['Estimate_UN'].apply(lambda g:str(int(g)))
df2['Estimate_UN'] = '€' + df2['Estimate_UN'].astype(str)
length = (df2['Estimate_UN'].str.len())
df2.loc[length == 7, 'Estimate_UN'] = df2[['Estimate_UN']].astype(str).replace(r"(\d{3})(\d+)", r"\1,\2", regex=True)
df2.loc[length == 6, 'Estimate_UN'] = df2[['Estimate_UN']].astype(str).replace(r"(\d{2})(\d+)", r"\1,\2", regex=True)
df2.loc[length == 5, 'Estimate_UN'] = df2[['Estimate_UN']].astype(str).replace(r"(\d{1})(\d+)", r"\1,\2", regex=True)
### --- Create map --- ###
world = geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres'))
df1 = world.merge(df1, how='left', left_on=['name'], right_on=['Country'])
df1 = df1.dropna(subset=['Estimate_UN'])
df2 = world.merge(df2, how='left', left_on=['name'], right_on=['Country'])
df2 = df2.dropna(subset=['Estimate_UN'])
df3 = world.merge(df3, how='left', left_on=['name'], right_on=['Country'])
df3 = df3.dropna(subset=['Estimate_UN'])
my_map = folium.Map(location=(39.22753573470106, -3.650093262568073),
zoom_start=2,
tiles = 'https://server.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
attr = 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye,Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
min_zoom=2,
min_lot=-179,
max_lot=179,
min_lat=-65,
max_lat=179,
max_bounds=True)
### --- Add tooltip --- ###
gdp = folium.FeatureGroup(name="GDP")
gdp.add_child(folium.GeoJson(data=df2, tooltip = folium.features.GeoJsonTooltip(
fields=['Country','Estimate_UN'],
aliases=['Country:','GDP Per Capita:'],
style=("background-color: white; color: #333333; font-family: arial; font-size: 12px; padding: 10px;"),
localize = True),
style_function= lambda y:{
'stroke':'false',
'opacity':'0',
}))
### --- Color countries --- ###
bins = [100,1000,5000,10000,20000,35000,50000,112000]
folium.Choropleth(
geo_data=df1,
name="choropleth",
data=df3,
columns=["Country", "Estimate_UN"],
key_on="feature.properties.name",
fill_color="YlGnBu",
fill_opacity=0.8,
line_opacity=0.5,
legend_name="GDP Per Capita (in EUR)",
bins=bins,
highlight=True
).add_to(my_map)
my_map.add_child(gdp)
my_map.save('index.html')
I'm looking forward to your suggestions on why GeoJsonTooltip is stopping the highlight parameter from working!
My understanding is that folium.Choropleth() has a highlighting feature, but no popup or tooltip feature. If you want to use the tooltip and popup functions, use folium.Geojson(). I will respond with a df3 of the data you presented.
I have implemented my own color map for color coding. The index of
the color map is modified according to the number of colors. See this
for more information about our own colormaps.
The tooltip is introduced as you set it up. We have also added a
pop-up feature. You can add supplementary information. If you don't
need it, please delete it.
The color fill is specified by the style function, which gets the
color name from the estimated value for the previously specified
colormap function. At the same time, a highlight function is added to
change the transparency of the map drawing. The basic code can be found here.
import folium
from folium.features import GeoJsonPopup, GeoJsonTooltip
import branca
bins = [5000,25000,45000,65000,112000]
my_map = folium.Map(location=(39.22753573470106, -3.650093262568073),
zoom_start=2,
tiles = 'https://server.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
attr = 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye,Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
min_zoom=2,
min_lot=-179,
max_lot=179,
min_lat=-65,
max_lat=179,
max_bounds=True)
colormap = branca.colormap.LinearColormap(
vmin=df3['Estimate_UN'].quantile(0.0),
vmax=df3['Estimate_UN'].quantile(1),
colors=["red", "orange", "lightblue", "green", "darkgreen"],
caption="Original Colormap",
index=bins
)
tooltip = folium.features.GeoJsonTooltip(
fields=['Country','Estimate_UN'],
aliases=['Country:','GDP Per Capita:'],
style=("background-color: white; color: #333333; font-family: arial; font-size: 12px; padding: 10px;"),
localize=True)
popup = GeoJsonPopup(
fields=['Country','Estimate_UN'],
aliases=['Country:','GDP Per Capita:'],
localize=True,
labels=True,
style="background-color: yellow;",
)
folium.GeoJson(data=df3,
tooltip=tooltip,
popup=popup,
style_function= lambda y:{
"fillColor": colormap(y["properties"]["Estimate_UN"]),
'stroke':'false',
'opacity': 0.4
},
highlight_function=lambda x: {'fillOpacity': 0.8},
).add_to(my_map)
colormap.add_to(my_map)
# my_map.save('index.html')
my_map

Hover over an area a text should appear

I am currently viewing the neighborhoods. However, when I hover the mouse over it, I want a text to be displayed with the neighborhood and the average price. I have pasted my code and my card what I made. Below you can see how I would like it.
How can I display a text when I hover over it?
import folium
from folium.plugins import FastMarkerCluster
from branca.colormap import LinearColormap
map_ams_price = folium.Map(location=[52.3680, 4.9036], zoom_start=11, tiles="cartodbpositron")
map_ams_price.choropleth(
geo_data=r'C:\neighbourhoods.geojson',
data=df,
columns=['neighbourhood_cleansed', 'price'],
key_on='feature.properties.neighbourhood',
fill_color='BuPu',
fill_opacity=0.7,
line_opacity=0.2,
legend_name='Avg',
reset=True,
tooltip=folium.features.GeoJsonTooltip(fields=['neighbourhood_cleansed', 'price'],
labels=True,
sticky=False),
)
map_ams_price
Minium Example
# the price is the avg price
d = {'neighbourhood_cleansed': ['Oostelijk Havengebied - Indische Buurt', 'Centrum-Oost',
'Centrum-West', 'Zuid', 'De Baarsjes - Oud-West', 'Bos en Lommer',
'De Pijp - Rivierenbuurt', 'Oud-Oost', 'Noord-West', 'Westerpark',
'Slotervaart', 'Oud-Noord', 'Watergraafsmeer',
'IJburg - Zeeburgereiland', 'Noord-Oost', 'Buitenveldert - Zuidas',
'Geuzenveld - Slotermeer', 'De Aker - Nieuw Sloten', 'Osdorp',
'Bijlmer-Centrum', 'Gaasperdam - Driemond', 'Bijlmer-Oost'],
'price': [20.0,30.0,40.0,50.0,60.0,70.0,80.0,20.0,30.0,40.0,50.0,60.0,70.0,80.0,20.0,30.0,40.0,50.0,60.0,70.0,80.0,20.0]}
df = pd.DataFrame(data=d)
You can find the geojson here https://pastecode.io/s/a76fdvey
As I mentioned in the comments, in order to give tooltips in the choropleth map, they have to be in the geojson file, not in the dataframe value, in order to be displayed. So I used geopandas for the geojson file to be used, combined the data frames and added the price information to the geojson file. The column names in the original data frame have been modified to match the geojson file. It can also be used as a label by adding an alias name. The tooltip can be styled, so I added that as well.
import json
import requests
import geopandas as gpd
url = "http://data.insideairbnb.com/the-netherlands/north-holland/amsterdam/2021-09-07/visualisations/neighbourhoods.geojson"
gpd_geo = gpd.read_file(url)
gpd_geo = gpd_geo.merge(df, on='neighbourhood')
geo_json_data = gpd_geo.to_json()
import folium
from folium.plugins import FastMarkerCluster
from branca.colormap import LinearColormap
from folium.features import GeoJsonPopup, GeoJsonTooltip
map_ams_price = folium.Map(location=[52.3680, 4.9036], zoom_start=11, tiles="cartodbpositron")
choropleth = folium.Choropleth(
geo_data=geo_json_data,
data=df,
columns=['neighbourhood', 'price'],
key_on='feature.properties.neighbourhood',
fill_color='BuPu',
fill_opacity=0.7,
line_opacity=0.2,
legend_name='Avg',
reset=True,
highlight=True,
).add_to(map_ams_price)
choropleth.geojson.add_child(
folium.features.GeoJsonTooltip(fields=['neighbourhood', 'price'],
aliases=['neighbourhood:', 'average_price:'],
labels=True,
localize=True,
sticky=False,
style="""
background-color: #F0EFEF;
border: 2px solid black;
border-radius: 3px;
box-shadow: 3px;
""",)
)
map_ams_price

How can I show specific data on a mapbox from plotly express

I have linked the data and chosen the right parameters, but the map is empty. I have also looked at the documentation of https://plotly.com/python/mapbox-county-choropleth/ , but that doesn't help me.
Here is the code I made:
# IMPORTEREN VAN LIBARIES
import plotly.express as px
import pandas as pd
import plotly
import plotly.offline as po
from urllib.request import urlopen
import json
# LEZEN VAN DATASET
fietsdata = pd.read_excel('Fietsdata.xlsx')
with urlopen('http://larscuny.info/projecten/dip/data/countries.json') as response:
countries = json.load(response)
fig = px.choropleth_mapbox(
fietsdata,
geojson=countries,
locations='country',
color='total_profit',
color_continuous_scale="Viridis",
mapbox_style="open-street-map", # carto-positron
)
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
# LAYOUT VAN DE VISUALISATIE
fig.update_layout(
font_family="Poppins",
font_color="#002072",
title_font_family="Poppins",
title_font_color="#002072",
legend_title="Gender",
legend_title_font_color="#002072",
)
# OPEN VISUALISATIE
po.plot(fig, filename="total-revenue.html")
And the dataset I used:
country
state
total_costs
total_revenue
total_profit
Canada
Brittish Columbia
360.00
950.00
590.00
Australia
New South Wales
1,035.00
2,401.00
1366.00
United States
Oregon
405.00
929.00
524.00
I hope someone can help me
your data preparation does not consider that country is not in the geojson as a property. Have used geopandas for prep and joining. NB - USA is lost as join keys are inconsistent
you should consider where you want to be the center and the zoom level
# IMPORTEREN VAN LIBARIES
import plotly.express as px
import pandas as pd
import geopandas as gpd
import plotly
import plotly.offline as po
from urllib.request import urlopen
import json
# LEZEN VAN DATASET
# fietsdata = pd.read_excel('Fietsdata.xlsx')
fietsdata =pd.DataFrame({'country': ['Canada', 'Australia', 'United States'],
'state': ['Brittish Columbia', 'New South Wales', 'Oregon'],
'total_costs': ['360.00', '1,035.00', '405.00'],
'total_revenue': ['950.00', '2,401.00', '929.00'],
'total_profit': [590.0, 1366.0, 524.0]})
with urlopen('http://larscuny.info/projecten/dip/data/countries.json') as response:
countries = json.load(response)
# use geopandas and join data together
gdf = gpd.GeoDataFrame.from_features(countries)
gdf = gdf.merge(fietsdata, left_on="ADMIN", right_on="country")
fig = px.choropleth_mapbox(
gdf,
geojson=gdf.geometry,
locations=gdf.index,
color='total_profit',
color_continuous_scale="Viridis",
center={"lat": gdf.iloc[0].geometry.centroid.y, "lon": gdf.iloc[0].geometry.centroid.x},
zoom=1,
mapbox_style="open-street-map", # carto-positron
)
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
# LAYOUT VAN DE VISUALISATIE
fig.update_layout(
font_family="Poppins",
font_color="#002072",
title_font_family="Poppins",
title_font_color="#002072",
legend_title="Gender",
legend_title_font_color="#002072",
)
# OPEN VISUALISATIE
fig

How to know if a request is fulfilled on python?

I making a request to download some data on python from Copernicus website. The thing is that I want to know when the request is fulfilled and when the download is finished.
Is is solid enough to work with 2 flags(request_finished and download_finished)?
import cdsapi
c = cdsapi.Client()
latitude = 43.1 # North, South
longitude = -1.5 # West , East
#str(latitude)+'/'+str(longitude)+'/'+str(latitude)+'/'+str(longitude)
r = c.retrieve(
'reanalysis-era5-single-levels',
{
'product_type':'reanalysis',
'variable':[
'100m_u_component_of_wind','100m_v_component_of_wind','10m_u_component_of_wind','10m_v_component_of_wind','2m_temperature',
'surface_pressure'
],
'area' : str(latitude)+'/'+str(longitude)+'/'+str(latitude)+'/'+str(longitude), # North, West, South, East. Default: global
'year':'2018',
'grid':'0.1/0.1', # Latitude/longitude grid in degrees: east-west (longitude) and north-south resolution (latitude). Default: reduced Gaussian grid
'month':'01',
'day':[
'01','02','03',
'04','05','06',
'07','08','09',
'10','11','12',
'13','14','15',
'16','17','18',
'19','20','21',
'22','23','24',
'25','26','27',
'28','29','30',
'31'
],
'time':[
'00:00','01:00','02:00',
'03:00','04:00','05:00',
'06:00','07:00','08:00',
'09:00','10:00','11:00',
'12:00','13:00','14:00',
'15:00','16:00','17:00',
'18:00','19:00','20:00',
'21:00','22:00','23:00'
],
'format':'netcdf'
}
)
request_finished = 1
r.download('download_grid_reduction_one_month_point_limit.nc')
download_finished = 1

Why is matplotlib basemap not plotting the colours of some areas in my map?

The code below is supposed to colour all the states of Vietnam:
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig, ax = plt.subplots(figsize=(10,20))
# create the map
map = Basemap(resolution='l', # c, l, i, h, f or None
projection='merc',
lat_0=15.95, lon_0=105.85,
llcrnrlon=102., llcrnrlat= 8.31, urcrnrlon=109.69, urcrnrlat=23.61)
# load the shapefile, use the name 'states'
map.readshapefile(r'path\to\gadm36_VNM_1', name='states', drawbounds=True)
# shapefile downloaded from http://www.gadm.org/
# collect the state names from the shapefile attributes so we can
# look up the shape obect for a state by it's name
state_names = []
for shape_dict in map.states_info:
state_names.append(shape_dict['VARNAME_1'])
ax = plt.gca() # get current axes instance
# NOR, CEN, SOU and MEK are some subdivisions I have created for the states of Vietnam
NOR = ['Lai Chau',
'Lao Cai',
'Ha Giang',
'Cao Bang',
'Dien Bien',
'Son La',
'Yen Bai',
'Tuyen Quang',
'Bac Kan',
'Lang Son',
'Thai Nguyen',
'Phu Tho',
'Vinh Phuc',
'Hoa Binh',
'Ha Noi',
'Bac Ninh',
'Hai Duong',
'Hung Yen',
'Ha Nam',
'Quang Ninh',
'Hai Phong',
'Thai Binh',
'Nam Dinh',
'Bac Giang',
'Ninh Binh']
CEN = ['Thanh Hoa',
'Nghe An',
'Ha Tinh',
'Quang Binh',
'Quang Tri',
'Thua Thien Hue',
'Da Nang']
SOU = ['Quang Nam',
'Kon Tum',
'Quang Ngai',
'Gia Lai',
'Binh Dinh',
'Dak Lak',
'Phu Yen',
'Khanh Hoa',
'Dak Nong',
'Lam Dong',
'Ninh Thuan']
MEK = ['Binh Phuoc',
'Dong Nai',
'Binh Thuan',
'Tay Ninh',
'Binh Duong',
'Dong Nai',
'Ba Ria - Vung Tau',
'Ho Chi Minh',
'Long An',
'An Giang',
'Dong Thap',
'Tien Giang',
'Kien Giang',
'Can Tho',
'Vinh Long',
'Ben Tre',
'Hau Giang',
'Tra Vinh',
'Soc Trang',
'Bac Lieu',
'Ca Mau']
# Define the colours to be used to colour the states
from matplotlib import cm
from numpy import linspace
start = 0.5
stop = 1.0
number_of_lines= 4
cm_subsection = linspace(start, stop, number_of_lines)
cm_subsection[0] = cm_subsection[0]*4
cm_subsection[1] = cm_subsection[1]*0.6
cm_subsection[2] = cm_subsection[2]*0.8
cm_subsection[3] = cm_subsection[3]*0.1
colors = [ cm.Blues(x) for x in cm_subsection ]
for state in NOR:
seg = map.states[state_names.index(state)]
poly = Polygon(seg, facecolor=colors[0], edgecolor=colors[0])
ax.add_patch(poly)
for state in CEN:
seg = map.states[state_names.index(state)]
poly = Polygon(seg, facecolor=colors[1], edgecolor=colors[1])
ax.add_patch(poly)
for state in SOU:
seg = map.states[state_names.index(state)]
poly = Polygon(seg, facecolor=colors[2], edgecolor=colors[2])
ax.add_patch(poly)
for state in MEK:
seg = map.states[state_names.index(state)]
poly = Polygon(seg, facecolor=colors[3], edgecolor=colors[3])
ax.add_patch(poly)
import matplotlib.patches as mpatches
NOR_patch = mpatches.Patch(color=colors[0], label='Rate: 34.85%')
CEN_patch = mpatches.Patch(color=colors[1], label='Rate: 25.61%')
SOU_patch = mpatches.Patch(color=colors[2], label='Rate: 32.66%')
MEK_patch = mpatches.Patch(color=colors[3], label='Rate: 20.02%')
plt.legend(handles=[NOR_patch, CEN_patch, SOU_patch, MEK_patch])
plt.show()
But this produces the map below, where some of the states are not coloured even though they are present in the state names and in the subdivisions:
In fact, if I try to colour a state whose name is not present in the lists, it throws an error:
MEK.append('ABCDE')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-619-a89da62a0831> in <module>()
134
135 for state in MEK:
--> 136 seg = map.states[state_names.index(state)]
137 poly = Polygon(seg, facecolor=colors[3], edgecolor=colors[3])
138 ax.add_patch(poly)
ValueError: 'ABCDE' is not in list
So, clearly the states that are not colored are present in the list, as I'm not getting any error. So, what's going on?
EDIT: It just struck me that almost all the states that aren’t coloured share at least some part of their border with sea/ocean in the real world. The 6 exceptions are highlighted in red below:
Now that’s very interesting! Could it have anything to do with the issue? If yes, what? And why? And why do those 6 exceptions exist?
EDIT 2: I get similar results when drawing the map of Philippines:
In shapefiles, a country/province/whatever, may be broken down into several line segments. Why that is, I don't know, but in order to plot the shape correctly, you need to use all the necessary segments. Actually, in the Basemap documentation for shapefiles there is an example under 'filling polygons', how to do this correctly. I adapted their example to your use case. It's probably not the most optimal solution, but it seems to work.
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib import patches as mpatches
from matplotlib import cm
from numpy import linspace
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection
fig, ax = plt.subplots(figsize=(4,8))
# create the map
map = Basemap(resolution='l', # c, l, i, h, f or None
projection='merc',
lat_0=15.95, lon_0=105.85,
llcrnrlon=102., llcrnrlat= 8.31, urcrnrlon=109.69, urcrnrlat=23.61)
# load the shapefile, use the name 'states'
map.readshapefile(r'shapefiles/gadm36_VNM_1', name='states', drawbounds=True)
# shapefile downloaded from http://www.gadm.org/
# collect the state names from the shapefile attributes so we can
# look up the shape obect for a state by it's name
state_names = []
for shape_dict in map.states_info:
state_names.append(shape_dict['VARNAME_1'])
ax = plt.gca() # get current axes instance
# NOR, CEN, SOU and MEK are some subdivisions I have created for the states of Vietnam
NOR = ['Lai Chau',
'Lao Cai',
'Ha Giang',
'Cao Bang',
'Dien Bien',
'Son La',
'Yen Bai',
'Tuyen Quang',
'Bac Kan',
'Lang Son',
'Thai Nguyen',
'Phu Tho',
'Vinh Phuc',
'Hoa Binh',
'Ha Noi',
'Bac Ninh',
'Hai Duong',
'Hung Yen',
'Ha Nam',
'Quang Ninh',
'Hai Phong',
'Thai Binh',
'Nam Dinh',
'Bac Giang',
'Ninh Binh']
CEN = ['Thanh Hoa',
'Nghe An',
'Ha Tinh',
'Quang Binh',
'Quang Tri',
'Thua Thien Hue',
'Da Nang']
SOU = ['Quang Nam',
'Kon Tum',
'Quang Ngai',
'Gia Lai',
'Binh Dinh',
'Dak Lak',
'Phu Yen',
'Khanh Hoa',
'Dak Nong',
'Lam Dong',
'Ninh Thuan']
MEK = ['Binh Phuoc',
'Dong Nai',
'Binh Thuan',
'Tay Ninh',
'Binh Duong',
'Dong Nai',
'Ba Ria - Vung Tau',
'Ho Chi Minh',
'Long An',
'An Giang',
'Dong Thap',
'Tien Giang',
'Kien Giang',
'Can Tho',
'Vinh Long',
'Ben Tre',
'Hau Giang',
'Tra Vinh',
'Soc Trang',
'Bac Lieu',
'Ca Mau']
# Define the colours to be used to colour the states
start = 0.5
stop = 1.0
number_of_lines= 4
cm_subsection = linspace(start, stop, number_of_lines)
cm_subsection[0] = cm_subsection[0]*4
cm_subsection[1] = cm_subsection[1]*0.6
cm_subsection[2] = cm_subsection[2]*0.8
cm_subsection[3] = cm_subsection[3]*0.1
colors = [ cm.Blues(x) for x in cm_subsection ]
##collecting the line segments for the provinces:
patches = {state: [] for state in NOR+CEN+SOU+MEK}
for info, shape in zip(map.states_info, map.states):
for state in NOR+CEN+SOU+MEK:
if info['VARNAME_1'] == state:
patches[state].append(mpatches.Polygon(
shape, True,
))
##coloring the the provinces by group:
for state in NOR:
ax.add_collection(PatchCollection(
patches[state], facecolor = colors[0], edgecolor=colors[0]
))
for state in CEN:
ax.add_collection(PatchCollection(
patches[state], facecolor = colors[1], edgecolor=colors[1]
))
for state in SOU:
ax.add_collection(PatchCollection(
patches[state], facecolor = colors[2], edgecolor=colors[2]
))
for state in MEK:
ax.add_collection(PatchCollection(
patches[state], facecolor = colors[3], edgecolor=colors[3]
))
NOR_patch = mpatches.Patch(color=colors[0], label='Rate: 34.85%')
CEN_patch = mpatches.Patch(color=colors[1], label='Rate: 25.61%')
SOU_patch = mpatches.Patch(color=colors[2], label='Rate: 32.66%')
MEK_patch = mpatches.Patch(color=colors[3], label='Rate: 20.02%')
plt.legend(handles=[NOR_patch, CEN_patch, SOU_patch, MEK_patch])
plt.show()
The result looks as expected:
Note that I could only test the code under Python 3.6, so there might be some adjustments necessary. Hope this helps.

Categories