How to Build Interactive Geospatial Dashboards Using Folium with Heatmaps, Choropleths, Time Animation, Marker Clustering, and Advanced Interactive Plugins

Editor
7 Min Read


def create_marker_cluster_map():
   """Create a map with marker clustering for large datasets"""
   np.random.seed(123)
   n_locations = 5000
  
   lats = np.random.uniform(25, 49, n_locations)
   lons = np.random.uniform(-125, -65, n_locations)
   values = np.random.randint(1, 100, n_locations)
  
   df_markers = pd.DataFrame({
       'lat': lats,
       'lon': lons,
       'value': values
   })
  
   m = folium.Map(location=[37.8, -96], zoom_start=4)
  
   marker_cluster = MarkerCluster(
       name="Location Cluster",
       overlay=True,
       control=True
   ).add_to(m)
  
   for idx, row in df_markers.iterrows():
       if row['value'] < 33:
           color="green"
       elif row['value'] < 66:
           color="orange"
       else:
           color="red"
      
       folium.Marker(
           location=[row['lat'], row['lon']],
           popup=f"Value: {row['value']}",
           tooltip=f"Location {idx}",
           icon=folium.Icon(color=color, icon='info-sign')
       ).add_to(marker_cluster)
  
   folium.LayerControl().add_to(m)
  
   title_html=""'
                <div style="position: fixed;
                            top: 10px; left: 50px; width: 350px; height: 60px;
                            background-color: white; border:2px solid grey; z-index:9999;
                            font-size:14px; padding: 10px">
                <h4 style="margin: 0;">Marker Clustering Demo</h4>
                <p style="margin: 5px 0 0 0; font-size: 12px;">5000 markers - zoom to see individual points</p>
                </div>
                '''
   m.get_root().html.add_child(folium.Element(title_html))
  
   return m


def create_time_series_map():
   """Create an animated map showing data changes over time"""
   start_date = datetime(2024, 8, 1)
   features = []
  
   path = [
       [25.0, -70.0], [26.5, -72.0], [28.0, -74.5], [29.5, -76.5],
       [31.0, -78.0], [32.5, -79.5], [34.0, -80.5], [35.5, -81.0]
   ]
  
   for i, (lat, lon) in enumerate(path):
       timestamp = start_date + timedelta(hours=i*6)
      
       feature = {
           'type': 'Feature',
           'geometry': {
               'type': 'Point',
               'coordinates': [lon, lat]
           },
           'properties': {
               'time': timestamp.isoformat(),
               'popup': f'Hurricane Position<br>Time: {timestamp.strftime("%Y-%m-%d %H:%M")}<br>Category: {min(5, i//2 + 1)}',
               'icon': 'circle',
               'iconstyle': {
                   'fillColor': ['yellow', 'orange', 'red', 'darkred', 'purple'][min(4, i//2)],
                   'fillOpacity': 0.8,
                   'stroke': 'true',
                   'radius': 8 + i * 2
               }
           }
       }
       features.append(feature)
  
   m = folium.Map(
       location=[30.0, -75.0],
       zoom_start=5,
       tiles="CartoDB Positron"
   )
  
   TimestampedGeoJson(
       {'type': 'FeatureCollection', 'features': features},
       period='PT6H',
       add_last_point=True,
       auto_play=True,
       loop=True,
       max_speed=2,
       loop_button=True,
       date_options="YYYY-MM-DD HH:mm",
       time_slider_drag_update=True
   ).add_to(m)
  
   title_html=""'
                <div style="position: fixed;
                            top: 10px; left: 50px; width: 300px; height: 80px;
                            background-color: white; border:2px solid grey; z-index:9999;
                            font-size:14px; padding: 10px">
                <h4 style="margin: 0;">Hurricane Path Animation</h4>
                <p style="margin: 5px 0 0 0; font-size: 12px;">Simulated hurricane tracking<br>
                Use controls below to play/pause</p>
                </div>
                '''
   m.get_root().html.add_child(folium.Element(title_html))
  
   return m


def create_interactive_plugins_map():
   """Create a map with multiple interactive plugins"""
   m = folium.Map(
       location=[40.7128, -74.0060],
       zoom_start=12,
       tiles="OpenStreetMap"
   )
  
   minimap = MiniMap(toggle_display=True)
   m.add_child(minimap)
  
   draw = Draw(
       export=True,
       filename="drawn_shapes.geojson",
       position='topleft',
       draw_options={
           'polyline': True,
           'polygon': True,
           'circle': True,
           'rectangle': True,
           'marker': True,
           'circlemarker': True
       },
       edit_options={'edit': True}
   )
   m.add_child(draw)
  
   Fullscreen(
       position='topright',
       title="Expand map",
       title_cancel="Exit fullscreen",
       force_separate_button=True
   ).add_to(m)
  
   plugins.MeasureControl(
       position='bottomleft',
       primary_length_unit="kilometers",
       secondary_length_unit="miles",
       primary_area_unit="sqkilometers",
       secondary_area_unit="acres"
   ).add_to(m)
  
   plugins.MousePosition(
       position='bottomright',
       separator=" | ",
       empty_string='NaN',
       lng_first=True,
       num_digits=20,
       prefix='Coordinates:',
   ).add_to(m)
  
   plugins.LocateControl(
       auto_start=False,
       position='topleft'
   ).add_to(m)
  
   folium.Marker(
       [40.7128, -74.0060],
       popup='<b>NYC</b><br>Try the drawing tools!',
       icon=folium.Icon(color="red", icon='info-sign')
   ).add_to(m)
  
   return m


def create_earthquake_map():
   """Create comprehensive earthquake visualization using real USGS data"""
   url="https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson"
  
   try:
       response = requests.get(url)
       earthquake_data = response.json()
       print(f"Successfully loaded {len(earthquake_data['features'])} earthquakes")
   except Exception as e:
       print(f"Error fetching data: {e}")
       earthquake_data = {
           'features': [
               {
                   'properties': {'mag': 5.2, 'place': 'Sample Location 1', 'time': 1640000000000},
                   'geometry': {'coordinates': [-122.0, 37.0, 10]}
               },
               {
                   'properties': {'mag': 6.1, 'place': 'Sample Location 2', 'time': 1640100000000},
                   'geometry': {'coordinates': [140.0, 35.0, 20]}
               }
           ]
       }
  
   earthquakes = []
   for feature in earthquake_data['features']:
       props = feature['properties']
       coords = feature['geometry']['coordinates']
      
       earthquakes.append({
           'lat': coords[1],
           'lon': coords[0],
           'depth': coords[2],
           'magnitude': props.get('mag', 0),
           'place': props.get('place', 'Unknown'),
           'time': datetime.fromtimestamp(props.get('time', 0) / 1000)
       })
  
   df_eq = pd.DataFrame(earthquakes)
  
   print(f"\nEarthquake Statistics:")
   print(f"Total earthquakes: {len(df_eq)}")
   print(f"Magnitude range: {df_eq['magnitude'].min():.1f} - {df_eq['magnitude'].max():.1f}")
   print(f"Depth range: {df_eq['depth'].min():.1f} - {df_eq['depth'].max():.1f} km")
  
   m = folium.Map(
       location=[20, 0],
       zoom_start=2,
       tiles="CartoDB dark_matter"
   )
  
   minor = folium.FeatureGroup(name="Minor (< 4.0)")
   moderate = folium.FeatureGroup(name="Moderate (4.0-5.0)")
   strong = folium.FeatureGroup(name="Strong (5.0-6.0)")
   major = folium.FeatureGroup(name="Major (≥ 6.0)")
  
   for idx, eq in df_eq.iterrows():
       mag = eq['magnitude']
      
       if mag < 4.0:
           color="green"
           radius = 3
           group = minor
       elif mag < 5.0:
           color="yellow"
           radius = 6
           group = moderate
       elif mag < 6.0:
           color="orange"
           radius = 9
           group = strong
       else:
           color="red"
           radius = 12
           group = major
      
       popup_html = f"""
       <div style="font-family: Arial; width: 250px;">
           <h4 style="margin: 0; color: {color};">Magnitude {mag:.1f}</h4>
           <hr style="margin: 5px 0;">
           <p><b>Location:</b> {eq['place']}</p>
           <p><b>Depth:</b> {eq['depth']:.1f} km</p>
           <p><b>Time:</b> {eq['time'].strftime('%Y-%m-%d %H:%M:%S')}</p>
           <p><b>Coordinates:</b> {eq['lat']:.4f}, {eq['lon']:.4f}</p>
       </div>
       """
      
       folium.CircleMarker(
           location=[eq['lat'], eq['lon']],
           radius=radius,
           popup=folium.Popup(popup_html, max_width=270),
           tooltip=f"M{mag:.1f} - {eq['place']}",
           color=color,
           fill=True,
           fillColor=color,
           fillOpacity=0.7,
           weight=2
       ).add_to(group)
  
   minor.add_to(m)
   moderate.add_to(m)
   strong.add_to(m)
   major.add_to(m)
  
   heat_data = [[row['lat'], row['lon'], row['magnitude']] for idx, row in df_eq.iterrows()]
   heatmap = folium.FeatureGroup(name="Density Heatmap", show=False)
   HeatMap(
       heat_data,
       min_opacity=0.3,
       radius=15,
       blur=20,
       gradient={0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1: 'red'}
   ).add_to(heatmap)
   heatmap.add_to(m)
  
   folium.LayerControl(position='topright', collapsed=False).add_to(m)
  
   legend_html=""'
   <div style="position: fixed;
               bottom: 50px; right: 50px; width: 200px; height: 180px;
               background-color: white; border:2px solid grey; z-index:9999;
               font-size:14px; padding: 10px; border-radius: 5px;">
       <h4 style="margin: 0 0 10px 0;">Earthquake Magnitude</h4>
       <p style="margin: 5px 0;"><span style="color: green;">●</span> Minor (< 4.0)</p>
       <p style="margin: 5px 0;"><span style="color: yellow;">●</span> Moderate (4.0-5.0)</p>
       <p style="margin: 5px 0;"><span style="color: orange;">●</span> Strong (5.0-6.0)</p>
       <p style="margin: 5px 0;"><span style="color: red;">●</span> Major (≥ 6.0)</p>
       <hr style="margin: 10px 0;">
       <p style="margin: 5px 0; font-size: 11px;">Data: USGS (Past 30 days)</p>
   </div>
   '''
   m.get_root().html.add_child(folium.Element(legend_html))
  
   title_html=""'
   <div style="position: fixed;
               top: 10px; left: 50px; width: 400px; height: 80px;
               background-color: rgba(255, 255, 255, 0.95); border:2px solid grey; z-index:9999;
               font-size:14px; padding: 10px; border-radius: 5px;">
       <h3 style="margin: 0;">🌍 Global Earthquake Monitor</h3>
       <p style="margin: 5px 0 0 0; font-size: 12px;">
           Real-time earthquake data (M ≥ 2.5)<br>
           Click markers for details | Toggle layers to explore
       </p>
   </div>
   '''
   m.get_root().html.add_child(folium.Element(title_html))
  
   Fullscreen(position='topright').add_to(m)
  
   return m


if __name__ == "__main__":
   print("=" * 80)
   print("ADVANCED FOLIUM TUTORIAL - ALL EXAMPLES")
   print("=" * 80)
   print("\nGenerating all maps...\n")
  
   maps = {
       'multi_tile_map': create_multi_tile_map(),
       'advanced_markers_map': create_advanced_markers_map(),
       'heatmap': create_heatmap(),
       'choropleth_map': create_choropleth_map(),
       'marker_cluster_map': create_marker_cluster_map(),
       'time_series_map': create_time_series_map(),
       'interactive_plugins_map': create_interactive_plugins_map(),
       'earthquake_map': create_earthquake_map()
   }
  
   print("\n" + "=" * 80)
   print("SAVING MAPS TO HTML FILES")
   print("=" * 80)
  
   for name, map_obj in maps.items():
       if map_obj is not None:
           filename = f"{name}.html"
           map_obj.save(filename)
           print(f"✓ Saved: {filename}")
       else:
           print(f"✗ Skipped: {name} (map generation failed)")
  
   print("\n" + "=" * 80)
   print("ALL MAPS GENERATED SUCCESSFULLY!")
   print("=" * 80)
   print("\nYou can now:")
   print("1. Open any HTML file in your browser to view the interactive map")
   print("2. Access the map objects in code using the 'maps' dictionary")
   print("3. Display maps in Jupyter/Colab by returning the map object")
   print("\nExample: To display the earthquake map in a notebook, just run:")
   print("  maps['earthquake_map']")
   print("\n" + "=" * 80)
Share this Article
Please enter CoinGecko Free Api Key to get this plugin works.