4. Creating visualizations with maps

In this tutorial you will learn how to create maps with the folium and geopandas packages.

Note

To run this tutorial you will need geopandas, folium, and mapclassify installed. You will also need streamlit-folium to use folium with Streamlit. To install these packages, open a Terminal, activate your ist356 conda environment, and run:

pip install folium geopandas mapclassify streamlit-folium

Mapping

Mapping is a powerful way to visualize data. You can place points on a map, draw lines, and shade areas.

Map Visualizations with Folium

To create map visualizations, we will use folium, which is a Python wrapper for the Leaflet.js library.

Folium uses OpenStreetMap for drawing maps. It is a free/open source alternative to a service like Google Maps.

import folium
JMA = (43.0362, -76.1363)
map1 = folium.Map(location=JMA, zoom_start=18)
jma_marker = folium.Marker(JMA, popup='JMA Wireless Dome', tooltip="JMA").add_to(map1)
map1
Make this Notebook Trusted to load map: File -> Trust Notebook
NoteDon’t name maps map

Notice in the above block we named the map that we created map1. You may be tempted to name the map map instead. It’s perfectly fine for you to do so, however, map is also the name of a Python in-built function. The map function is used to apply a function to a list of values. If you name an instance of a map map in your code, you will overwrite the map function. This means that you will not be able to use the map function in the rest of your code! Even if you don’t use the map function often, you may find that you want to use it at some point in the future. For this reason, it’s best not to name map instances map. Call them something else, like map1, mp, etc.

Folium and Streamlit

To display folium maps to display in Streamlit, you need to use the streamlit_folium module.

This module has two functions:

  • folium_static is used to display the map only.
  • st_folium is use to display the map and return data while the user interacts with the map.

Here’s an example:

import folium
import streamlit as st
import pandas as pd

import streamlit_folium as sf


st.title('Streamlit Folium Example')
JMA = (43.0362, -76.1363)
HINDS = (43.0382, -76.1333)
m1 = folium.Map(location=JMA, zoom_start=16)
folium.Marker(JMA, popup="JMA Wireless Dome", tooltip="JMA", icon=folium.Icon(color="red")
).add_to(m1)
folium.Marker(HINDS, popup="iSchool", tooltip="Hinds Hall", icon=folium.Icon(color="blue")
).add_to(m1)

st.write('### Folium Map And Data')
st_data = sf.st_folium(m1, width=725)
st.write(st_data)

m2 = folium.Map(location=JMA, zoom_start=16)
folium.Marker(JMA, popup="JMA Wireless Dome", tooltip="JMA", icon=folium.Icon(color="red")
).add_to(m2)
folium.Marker(HINDS, popup="iSchool", tooltip="Hinds Hall", icon=folium.Icon(color="blue")
).add_to(m2)

st.write('### Folium Map Only')
sf.folium_static(m2, width=725)

Map Markers Colors and Icons

Through the icon named argument you can assign an icon to the popup. You must create a folium.Icon() to assign a custom icon. There are three named arguments:

color= the color of the marker icon_color= the color of the icon on the marker icon= the name of the icon.

Valid colors are: ['red', 'blue', 'green', 'purple', 'orange', 'darkred', 'lightred', 'beige', 'darkblue', 'darkgreen', 'cadetblue', 'darkpurple', 'white', 'pink', 'lightblue', 'lightgreen', 'gray', 'black', 'lightgray']

Valid icons can be found here: https://fontawesome.com/v4/icons/ Your mileage may vary here as not all the icons listed are available.

Mapping a dataframe

To map a dataframe:

  1. we need coordinates
  2. we need to use loop over the data, creating a marker for each row

Here’s an example using a dataframe that contains locations of various classes:

import pandas as pd
classesdf = pd.read_csv("https://raw.githubusercontent.com/mafudge/datasets/master/delimited/class-schedule.csv")
classesdf.head()
Course Day Time Building Room Lat Lon
0 IST256 M 3:45pm HBC Gifford 43.03819 -76.13413
1 MAT221 TuTh 12:45pm Bowne 104 43.03674 -76.13320
2 WRT206 MW 9:20am Crouse 111 43.03852 -76.13676
3 COM222 TuTh 9:20am NH II 232 43.04020 -76.13521
4 IST343 MW 2:15pm Hinds 243 43.03834 -76.13364

Let’s put a marker on the map showing the location of each class:

schine = (43.03986, -76.13375)
map2 = folium.Map(location=schine, zoom_start=17)

for index, row in classesdf.iterrows():
    text = f"{row['Course']} {row['Day']} {row['Time']} {row['Building']}"
    dd = (row['Lat'], row['Lon'])
    if row['Day'] == "TuTh":
        markercolor = 'orange'
    else:
        markercolor = 'darkblue'
    marker = folium.Marker(location=dd, popup=text, icon = folium.Icon(color = markercolor, icon="globe"))
    marker.add_to(map2)
map2
Make this Notebook Trusted to load map: File -> Trust Notebook
CautionCode Challenge 4.1

Streamlit version of Parkmagic!

The file

https://su-ist356-m003-fall-2025.github.io/course-home/assets/final_cuse_parking_violations.csv

contains a list parking tickets in Syracuse, along with their locations.

Write a Streamlit app that will load final_cuse_parking_violations.csv and create a map of the parking violations in Syracuse.

Steps to take:

  • drop rows without a latitude and longitude
  • provide a dropdown to select the status of the violation
  • place pins on the map with the street name and the violation description
  • color code the pins based on day of the week
  • to make the map more readable take a random sample of 50 rows from the dataframe (you can do this with the DataFrame’s sample method).
import pandas as pd
import streamlit as st
import folium
import streamlit_folium as sf


days  = {
    'Sunday': 'red',
    'Monday': 'orange',
    'Tuesday': 'beige',
    'Wednesday': 'green',
    'Thursday': 'blue',
    'Friday': 'purple',
    'Saturday': 'gray'
}

st.title('Streamlit Park magic')

mp = folium.Map(location=[43.0481, -76.1474], zoom_start=15)
df = pd.read_csv('https://su-ist356-m003-fall-2025.github.io/course-home/assets/final_cuse_parking_violations.csv')
df_dropped = df.dropna()
status = df_dropped['status'].unique()

status = st.selectbox('Select ticket status: ', status)

df_filtered = df_dropped[df_dropped['status'] == status]

df_sample = df_filtered.sample(50)

for i, row in df_sample.iterrows():
    lat, lon = row['coords'].strip("(").strip(")").split(',') 
    lat = float(lat.replace("'", ""))
    lon = float(lon.replace("'", ""))
    color = days[row['dayofweek']]
    folium.Marker((lat, lon), popup=row['location'], tooltip=row['location'], icon=folium.Icon(color=color)).add_to(mp)

sf.folium_static(mp)

Geopandas

Geopandas is a library that extends the Pandas library to work with geospatial data. It is built on top of the Shapely, Fiona, and Matplotlib libraries.

Geopandas can read and write data in many formats, including shapefiles, GeoJSON, and PostGIS. It can also display data on a map.

A Pandas DataFrame can be wrapped in a Geopandas’ GeoDataFrame. This adds a geometry column to the dataframe that contains information about the shape of the object. The geometry can be a POINT, LINE, or PLOYGON. The GeoDataFrame also has an explore method to display the data on a map.

Here is an example of a GeoDataFrame wrapped around the class-schedule DataFrame we used earlier:

import geopandas as gpd
classesdf = pd.read_csv("https://raw.githubusercontent.com/mafudge/datasets/master/delimited/class-schedule.csv")
gdf = gpd.GeoDataFrame(classesdf, geometry=gpd.points_from_xy(classesdf['Lon'], classesdf['Lat']))
gdf
Course Day Time Building Room Lat Lon geometry
0 IST256 M 3:45pm HBC Gifford 43.03819 -76.13413 POINT (-76.13413 43.03819)
1 MAT221 TuTh 12:45pm Bowne 104 43.03674 -76.13320 POINT (-76.1332 43.03674)
2 WRT206 MW 9:20am Crouse 111 43.03852 -76.13676 POINT (-76.13676 43.03852)
3 COM222 TuTh 9:20am NH II 232 43.04020 -76.13521 POINT (-76.13521 43.0402)
4 IST343 MW 2:15pm Hinds 243 43.03834 -76.13364 POINT (-76.13364 43.03834)

With the GeoDataFrame we can easily add markers to a folium map (compare to the multiple lines of code we had to use above to accomplish the same thing):

schine = (43.03986, -76.13375)
map3 = folium.Map(location=schine, zoom_start=17)
map_out = gdf.explore(m=map3, marker_type="marker")
map_out
Make this Notebook Trusted to load map: File -> Trust Notebook

You can read more about how to work with GeoDetaFrames here.

Choropleth Maps in Geopandas

A choropleth map is a map that uses shading to represent the value of a variable. The shading can be based on a single value or a range of values.

Choropleths maps require a GeoDataFrame with a geometry column. The geometry column contains the shapes that will be displayed on the map.

First, we’ll use a colormap from matplotlib. A colormap maps a sequence of numerical values to a sequence of colors. Here’s the list of available colormaps in matplotlib:

from matplotlib import colormaps
print(list(colormaps))
['magma', 'inferno', 'plasma', 'viridis', 'cividis', 'twilight', 'twilight_shifted', 'turbo', 'berlin', 'managua', 'vanimo', 'Blues', 'BrBG', 'BuGn', 'BuPu', 'CMRmap', 'GnBu', 'Greens', 'Greys', 'OrRd', 'Oranges', 'PRGn', 'PiYG', 'PuBu', 'PuBuGn', 'PuOr', 'PuRd', 'Purples', 'RdBu', 'RdGy', 'RdPu', 'RdYlBu', 'RdYlGn', 'Reds', 'Spectral', 'Wistia', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd', 'afmhot', 'autumn', 'binary', 'bone', 'brg', 'bwr', 'cool', 'coolwarm', 'copper', 'cubehelix', 'flag', 'gist_earth', 'gist_gray', 'gist_heat', 'gist_ncar', 'gist_rainbow', 'gist_stern', 'gist_yarg', 'gnuplot', 'gnuplot2', 'gray', 'hot', 'hsv', 'jet', 'nipy_spectral', 'ocean', 'pink', 'prism', 'rainbow', 'seismic', 'spring', 'summer', 'terrain', 'winter', 'Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c', 'grey', 'gist_grey', 'gist_yerg', 'Grays', 'magma_r', 'inferno_r', 'plasma_r', 'viridis_r', 'cividis_r', 'twilight_r', 'twilight_shifted_r', 'turbo_r', 'berlin_r', 'managua_r', 'vanimo_r', 'Blues_r', 'BrBG_r', 'BuGn_r', 'BuPu_r', 'CMRmap_r', 'GnBu_r', 'Greens_r', 'Greys_r', 'OrRd_r', 'Oranges_r', 'PRGn_r', 'PiYG_r', 'PuBu_r', 'PuBuGn_r', 'PuOr_r', 'PuRd_r', 'Purples_r', 'RdBu_r', 'RdGy_r', 'RdPu_r', 'RdYlBu_r', 'RdYlGn_r', 'Reds_r', 'Spectral_r', 'Wistia_r', 'YlGn_r', 'YlGnBu_r', 'YlOrBr_r', 'YlOrRd_r', 'afmhot_r', 'autumn_r', 'binary_r', 'bone_r', 'brg_r', 'bwr_r', 'cool_r', 'coolwarm_r', 'copper_r', 'cubehelix_r', 'flag_r', 'gist_earth_r', 'gist_gray_r', 'gist_heat_r', 'gist_ncar_r', 'gist_rainbow_r', 'gist_stern_r', 'gist_yarg_r', 'gnuplot_r', 'gnuplot2_r', 'gray_r', 'hot_r', 'hsv_r', 'jet_r', 'nipy_spectral_r', 'ocean_r', 'pink_r', 'prism_r', 'rainbow_r', 'seismic_r', 'spring_r', 'summer_r', 'terrain_r', 'winter_r', 'Accent_r', 'Dark2_r', 'Paired_r', 'Pastel1_r', 'Pastel2_r', 'Set1_r', 'Set2_r', 'Set3_r', 'tab10_r', 'tab20_r', 'tab20b_r', 'tab20c_r', 'grey_r', 'gist_grey_r', 'gist_yerg_r', 'Grays_r']

You can see what these look like on the matplotlib site.

In the example below we will use the inferno colormap to make a plot of fake population numbers of towns on the island of St. Lucia. To do this, we’ll use the file

https://su-ist356-m003-fall-2025.github.io/course-home/assets/stlucia.geojson

This is a GeoJSON file that gives the borders of towns and villages in St. Lucia:

gdf = gpd.read_file("https://su-ist356-m003-fall-2025.github.io/course-home/assets/stlucia.geojson")
gdf
source id name geometry
0 https://simplemaps.com LC06 Gros Islet POLYGON ((-60.97997 14.04352, -60.97606 14.061...
1 https://simplemaps.com LC02 Castries POLYGON ((-60.97997 14.04352, -60.96559 14.034...
2 https://simplemaps.com LC01 Anse-la-Raye POLYGON ((-61.0266 13.98737, -61.00945 13.9817...
3 https://simplemaps.com LC10 Soufrière POLYGON ((-60.98063 13.8928, -60.97185 13.8827...
4 https://simplemaps.com LC03 Choiseul POLYGON ((-61.0681 13.79218, -61.04956 13.8063...
5 https://simplemaps.com LC07 Laborie POLYGON ((-61.01892 13.75625, -61.01203 13.770...
6 https://simplemaps.com LC11 Vieux Fort POLYGON ((-60.98158 13.83577, -60.97522 13.839...
7 https://simplemaps.com LC08 Micoud POLYGON ((-60.97522 13.83923, -60.96433 13.845...
8 https://simplemaps.com LC09 Praslin POLYGON ((-60.96057 13.87901, -60.96057 13.900...
9 https://simplemaps.com LC05 Dennery POLYGON ((-60.96057 13.90032, -60.95681 13.927...
10 https://simplemaps.com LC04 Dauphin POLYGON ((-60.93559 14.01796, -60.933 14.03692...

Now let’s illustrate plotting, say, the population of each town. Since this is just for illustrative purposes, we’ll just create a population column with random numbers.

import numpy as np
gdf['Population'] = gdf.apply(lambda row: np.random.randint(5000,50000), axis=1)

We can now create the Choropleth map illustrating our fake population numbers with:

gdf.explore(column='Population', cmap='inferno')
Make this Notebook Trusted to load map: File -> Trust Notebook
CautionCode Challenge 4.2

The following CSV file contains US unemployment data from October, 2012:

https://raw.githubusercontent.com/wrobstory/vincent/master/examples/data/US_Unemployment_Oct2012.csv

This GeoJSON file contains the geometry information about US states:

https://su-ist356-m003-fall-2025.github.io/course-home/assets/us-states.geojson

Use these files to create a choropleth map of the unemployment data by state.

  1. Load the unemployment data as a Pandas DataFrame.
  2. Load the states’ geometry data into geopandas.
  3. Merge the data on a common key.
  4. Explore with geopandas!

To initially center map, use the latitude and longitude of the continental US, which is 39.8333333,-98.585522.

import folium
import pandas as pd
import geopandas as gpd

CENTER_US = (39.8333333,-98.585522)
u_df = pd.read_csv('https://raw.githubusercontent.com/wrobstory/vincent/master/examples/data/US_Unemployment_Oct2012.csv')
state_geojson = 'https://su-ist356-m003-fall-2025.github.io/course-home/assets/us-states.geojson'
geo = gpd.read_file(state_geojson)
combined = pd.merge(geo, u_df, left_on='id', right_on='State')
mp = folium.Map(location=CENTER_US, zoom_start=4)
out_map = combined.explore(m=mp, column='Unemployment', cmap='YlGn', legend=True)
out_map
Make this Notebook Trusted to load map: File -> Trust Notebook

Geopandas Choropleth Maps in streamlit.

If you plan to use GeoDataFrame.explore() then you will need to call folium_static() from the streamlit_folium module to display the map in Streamlit. Here’s an example:

from numpy import random
import geopandas as gpd
import pandas as pd
import streamlit as st
import streamlit_folium as sf
import folium

st.title('Streamlit Geopandas Examples')
st.caption('This is a simple example of how to use Streamlit to create visualizations with Geopandas')

# Load the GeoPandas data
st.write("## Saint Lucia Choropleth")
gdf = gpd.read_file("https://su-ist356-m003-fall-2025.github.io/course-home/assets/stlucia.geojson")
gdf['Amount'] = gdf.apply(lambda row: random.randint(50,275), axis=1)
st.write(gdf)

sf.folium_static(gdf.explore(gdf['Amount']))

wards_df = gpd.read_file("https://raw.githubusercontent.com/mafudge/datasets/refs/heads/master/city-of-syracuse/wards.geojson")
wards_df['amount'] = wards_df.apply(lambda row: random.randint(1,50), axis=1)

st.write('## City of Syracuse Wards - Choropleth')
st.write(wards_df)
sf.folium_static(wards_df.explore(wards_df['amount']))

stlucia_df = pd.read_csv("https://raw.githubusercontent.com/mafudge/datasets/refs/heads/master/st-lucia/parishes.csv")
stlucia_df['Amount'] = stlucia_df.apply(lambda row: random.randint(50,500), axis=1)


classesdf = pd.read_csv("https://raw.githubusercontent.com/mafudge/datasets/master/delimited/class-schedule.csv")
gdf = gpd.GeoDataFrame(classesdf, geometry=gpd.points_from_xy(classesdf['Lon'], classesdf['Lat']))
st.write(gdf)
schine = (43.03986, -76.13375)
m = folium.Map(location=schine, zoom_start=17)
mout = gdf.explore(popup="Course",lat="Lat", lon="Lon", m=m, marker_type="marker")
sf.folium_static(mout)