interactive module¶
Map (Map)
¶
Source code in forestspot/interactive.py
class Map(ipyleaflet.Map):
def __init__(self, center=[37.5, -95], zoom=4, height="600px", **kwargs):
"""Initialize the map with a given center and zoom level.
Args:
center (list, optional): Center coordinates of the map. Defaults to [37.5, -95].
zoom (int, optional): Zoom level of the map. Defaults to 4.
height (str, optional): Height of the map. Defaults to "600px".
**kwargs: Additional keyword arguments for ipyleaflet.Map.
"""
super().__init__(center=center, zoom=zoom, **kwargs)
self.layout.height = height
self.scroll_wheel_zoom = True
self.add_layer_control()
self.change_basemap()
self.add_search_marker()
self.upload_points()
self.upload_geojson_file()
self.change_built_in_shapefiles()
self.on_interaction(self.handle_click)
self.geojson_button()
def add_basemap(self, basemap="Esri.WorldImagery"):
"""Add basemap to the map.
Args:
basemap (str, optional): Basemap name. Defaults to "Esri.WorldImagery".
"""
url = eval(f"ipyleaflet.basemaps.{basemap}").build_url()
layer = ipyleaflet.TileLayer(url=url, name=basemap)
self.add(layer)
def add_google_map(self, map_type="ROADMAP"):
"""Add Google Map to the map.
Args:
map_type (str, optional): Map type. Defaults to "ROADMAP".
"""
map_types = {
"ROADMAP": "m",
"SATELLITE": "s",
"HYBRID": "y",
"TERRAIN": "p",
}
map_type = map_types[map_type.upper()]
url = (
f"https://mt1.google.com/vt/lyrs={map_type.lower()}&x={{x}}&y={{y}}&z={{z}}"
)
layer = ipyleaflet.TileLayer(url=url, name="Google Map")
self.add(layer)
def handle_click(self, **kwargs):
"""Handles click events on the map."""
if kwargs.get("type") == "click":
self.add_layer(ipyleaflet.Marker(location=kwargs.get("coordinates")))
print(f"Clicked at: {kwargs.get('coordinates')}")
def add_layer_control(self):
"""Adds a layer control widget to the map."""
control = ipyleaflet.LayersControl(position="topright")
self.add_control(control)
def add_geojson(self, data, hover_style=None, **kwargs):
"""Adds a GeoJSON layer to the map.
Args:
data (str or dict): The GeoJSON data. Can be a file path (str) or a dictionary.
zoom_to_layer (bool, optional): Whether to zoom to the layer's bounds. Defaults to True.
hover_style (dict, optional): Style to apply when hovering over features. Defaults to {"color": "yellow", "fillOpacity": 0.2}.
**kwargs: Additional keyword arguments for the ipyleaflet.GeoJSON layer.
Raises:
ValueError: If the data type is invalid.
"""
import geopandas as gpd
if hover_style is None:
hover_style = {"color": "gray", "fillOpacity": 0.2}
if isinstance(data, str):
gdf = gpd.read_file(data)
geojson = gdf.__geo_interface__
elif isinstance(data, dict):
geojson = data
style = {"color": "black", "weight": 1, "opacity": 1}
if (
hasattr(self, "current_geojson_layer")
and self.current_geojson_layer is not None
):
try:
self.remove_layer(self.current_geojson_layer)
except Exception:
pass # Layer might have already been removed
layer = ipyleaflet.GeoJSON(
data=geojson, hover_style=hover_style, style=style, **kwargs
)
self.current_geojson_layer = layer
self.add_layer(layer)
def add_points(self, data, **kwargs):
"""Adds points to the map.
Args:
data (str or dict): The GeoJSON data. Can be a file path (str) or a dictionary.
**kwargs: Additional keyword arguments for the ipyleaflet.GeoJSON layer.
"""
# Load data with safety checks
if isinstance(data, str):
coordinates = pd.read_csv(data)
gdf = gpd.GeoDataFrame(
coordinates,
geometry=gpd.points_from_xy(coordinates.LON, coordinates.LAT),
crs="EPSG:4326", # Directly set CRS during creation
)
elif isinstance(data, pd.DataFrame):
coordinates = data
gdf = gpd.GeoDataFrame(
coordinates,
geometry=gpd.points_from_xy(coordinates.LON, coordinates.LAT),
crs="EPSG:4326", # Directly set CRS during creation
)
else:
gdf = data.to_crs(epsg=4326) # Ensure WGS84
point_style = {
"radius": 2,
"color": "black",
"dashArray": "2",
"fillOpacity": 1,
"fillColor": "red",
"weight": 1,
} # 'color': 'white',
geo_data = ipyleaflet.GeoData(
geo_dataframe=gdf,
point_style=point_style,
)
self.add(geo_data)
def add_shp(self, data, **kwargs):
"""Adds a shapefile to the map.
Args:
data (str): The file path to the shapefile.
**kwargs: Additional keyword arguments for the GeoJSON layer.
"""
gdf = gpd.read_file(data)
gdf = gdf.to_crs(epsg=4326)
geojson = gdf.__geo_interface__
self.add_geojson(geojson, **kwargs)
def add_gdf(self, gdf, **kwargs):
"""Adds a GeoDataFrame to the map.
Args:
gdf (geopandas.GeoDataFrame): The GeoDataFrame to add.
**kwargs: Additional keyword arguments for the GeoJSON layer.
"""
gdf = gdf.to_crs(epsg=4326)
geojson = gdf.__geo_interface__
self.add_geojson(geojson, **kwargs)
def upload_points(self, **kwargs):
"""Uploads points to the map."""
from ipywidgets import FileUpload, Button, ToggleButton, HBox
button = Button(
description="Add Points",
disabled=False,
button_style="", # 'success', 'info', 'warning', 'danger' or ''
tooltip="Click me",
size=38,
icon="plus", # (FontAwesome names without the `fa-` prefix)
)
file_upload = FileUpload(
accept=".csv",
multiple=False,
size=38,
description="Upload CSV",
style={"description_width": "initial"},
)
close_button = ToggleButton(
icon="map-pin", value=True, tooltip="Upload points from CSV file"
)
close_button.layout = widgets.Layout(width="36px", height="36px")
hbox = HBox([close_button])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if is_open[0] and change["new"]:
hbox.children = (close_button,)
else:
hbox.children = (close_button, file_upload, button)
def on_button_clicked(change):
"""Handles the file upload."""
import io
if file_upload.value:
# For the first file (if multiple=False)
file_info = file_upload.value[0]
content_bytes = file_info["content"].tobytes()
points = pd.read_csv(io.BytesIO(content_bytes))
self.add_points(points)
close_button.observe(on_toggle_change, names="value")
button.on_click(on_button_clicked)
upload_control = ipyleaflet.WidgetControl(widget=hbox, position="topright")
self.add_control(upload_control)
def upload_geojson_file(self, **kwargs):
"""Uploads GeoJSON file to the map."""
from ipywidgets import FileUpload, Button, ToggleButton, HBox
button = Button(
description="Add GeoJSON file",
disabled=False,
button_style="", # 'success', 'info', 'warning', 'danger' or ''
tooltip="Click me",
size=38,
icon="plus", # (FontAwesome names without the `fa-` prefix)
)
file_upload = FileUpload(
accept=".geojson",
multiple=False,
size=38,
description="Upload GeoJSON",
style={"description_width": "initial"},
)
close_button = ToggleButton(icon="file", value=True, tooltip="Add GeoJSON file")
close_button.layout = widgets.Layout(width="36px", height="36px")
hbox = HBox([close_button])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if change["new"]:
hbox.children = (close_button,)
else:
hbox.children = (close_button, file_upload, button)
def on_button_clicked(change):
"""Handles the url upload."""
if file_upload.value and len(file_upload.value) > 0:
import json
from ipyleaflet import GeoJSON
file_info = file_upload.value[0]
# Get bytes from memoryview
content_bytes = file_info["content"].tobytes()
# Decode bytes to string, then load as JSON
geojson_data = json.loads(content_bytes.decode("utf-8"))
# Add the GeoJSON layer to the map
geo_json = GeoJSON(data=geojson_data)
self.add_layer(geo_json)
close_button.observe(on_toggle_change, names="value")
button.on_click(on_button_clicked)
upload_control = ipyleaflet.WidgetControl(widget=hbox, position="topright")
self.add_control(upload_control)
def add_widgets(self):
"""Creates and displays widgets for user interaction."""
from ipywidgets import jslink
from ipyleaflet import WidgetControl
# date_picker = widgets.DatePicker(description="Pick a Date", disabled=False)
opacity_slider = widgets.FloatSlider(
value=1,
min=0,
max=1.0,
step=0.01,
description="Opacity",
continuous_update=False,
orientation="horizontal",
readout=True,
readout_format=".2f",
style={"description_width": "initial"},
)
basemap_layer = self.layers[1]
jslink((opacity_slider, "value"), (basemap_layer, "opacity"))
opacity_control = WidgetControl(widget=opacity_slider, position="topright")
self.add(opacity_control)
# self.add(date_control)
def add_vector(self, data, **kwargs):
"""Adds vector data to the map.
Args:
data (str, geopandas.GeoDataFrame, or dict): The vector data. Can be a file path, GeoDataFrame, or GeoJSON dictionary.
**kwargs: Additional keyword arguments for the GeoJSON layer.
Raises:
ValueError: If the data type is invalid.
"""
import geopandas as gpd
if isinstance(data, str):
gdf = gpd.read_file(data)
self.add_gdf(gdf, **kwargs)
elif isinstance(data, gpd.GeoDataFrame):
self.add_gdf(data, **kwargs)
elif isinstance(data, dict):
self.add_geojson(data, **kwargs)
else:
raise ValueError("Invalid data type")
def change_basemap(self, **kwargs):
"""Changes the basemap of the map using a dropdown selector."""
from ipywidgets import Dropdown, ToggleButton, HBox, Layout
from ipyleaflet import WidgetControl, basemap_to_tiles, basemaps
# Map dropdown names to actual basemap objects
BASEMAP_LOOKUP = {
"OpenStreetMap": basemaps.OpenStreetMap.Mapnik,
"OpenTopoMap": basemaps.OpenTopoMap,
"Esri.WorldImagery": basemaps.Esri.WorldImagery,
"CartoDB.DarkMatter": basemaps.CartoDB.DarkMatter,
"CartoDB.Positron": basemaps.CartoDB.Positron,
"CartoDB.PositronNoLabels": basemaps.CartoDB.PositronNoLabels,
"Esri.WorldTopoMap": basemaps.Esri.WorldTopoMap,
"Esri.WorldStreetMap": basemaps.Esri.WorldStreetMap,
"Esri.OceanBasemap": basemaps.Esri.OceanBasemap,
"Esri.NatGeoWorldMap": basemaps.Esri.NatGeoWorldMap,
"NASAGIBS.ModisTerraTrueColorCR": basemaps.NASAGIBS.ModisTerraTrueColorCR,
"NASAGIBS.ModisAquaTrueColorCR": basemaps.NASAGIBS.ModisAquaTrueColorCR,
"NASAGIBS.ViirsTrueColorCR": basemaps.NASAGIBS.ViirsTrueColorCR,
"NASAGIBS.ViirsEarthAtNight2012": basemaps.NASAGIBS.ViirsEarthAtNight2012,
}
# Create widgets
toggle = ToggleButton(
value=True,
tooltip="Change basemap",
icon="map-o",
layout=Layout(width="36px", height="36px"),
)
dropdown = Dropdown(
options=list(BASEMAP_LOOKUP.keys()),
value="OpenStreetMap",
description="Basemap:",
style={"description_width": "initial"},
layout=Layout(width="250px", height="36px"),
)
# Store reference to current basemap layer
if not hasattr(self, "current_basemap"):
self.current_basemap = self.layers[0]
def on_dropdown_change(change):
"""Handle basemap selection changes"""
if change["new"]:
new_basemap = basemap_to_tiles(BASEMAP_LOOKUP[change["new"]])
self.substitute_layer(self.current_basemap, new_basemap)
self.current_basemap = new_basemap
# def on_toggle_change(change):
# """Toggle visibility of dropdown"""
# if change["new"]:
# dropdown.layout.visibility = 'visible' # Show dropdown
# else:
# dropdown.layout.visibility = 'hidden' # Hide dropdown
# Set up event handlers
dropdown.observe(on_dropdown_change, names="value")
# Assemble and add control
widget_box = HBox([toggle])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if is_open[0] and change["new"]:
widget_box.children = (toggle,)
else:
widget_box.children = (toggle, dropdown)
toggle.observe(on_toggle_change, names="value")
control = WidgetControl(widget=widget_box, position="topright")
self.add_control(control)
def change_built_in_shapefiles(self, **kwargs):
"""Changes the basemap of the map using a dropdown selector."""
from ipywidgets import Dropdown, ToggleButton, HBox, Layout
from ipyleaflet import WidgetControl
# Map dropdown names to actual basemap objects
SHAPEFILE_LOOKUP = {
"None": None,
"U.S. States": "../data/us-states.json",
"National Forests": "https://geohub.oregon.gov/api/download/v1/items/b479e4bd7d70439a87e0230c99bddce5/geojson?layers=0",
}
# Create widgets
toggle = ToggleButton(
value=True,
tooltip="Add built-in shapefile",
icon="square-o",
layout=Layout(width="38px", height="38px"),
)
dropdown = Dropdown(
options=list(SHAPEFILE_LOOKUP.keys()),
value="None",
placeholder="Select a shapefile",
description="Select shapefile:",
style={"description_width": "initial"},
layout=Layout(width="250px", height="38px"),
)
# Store reference to current basemap layer
if not hasattr(self, "current_basemap"):
self.current_basemap = self.layers[0]
def on_dropdown_change(change):
"""Handle shapefile selection changes"""
if change["new"]:
geojson_path = SHAPEFILE_LOOKUP[change["new"]]
if geojson_path is not None:
self.add_geojson(geojson_path, name=change["new"])
else:
# Remove existing shapefile layer if "None" is selected
if (
hasattr(self, "current_geojson_layer")
and self.current_geojson_layer is not None
):
try:
self.remove_layer(self.current_geojson_layer)
except Exception:
pass
# Set up event handlers
dropdown.observe(on_dropdown_change, names="value")
# Assemble and add control
widget_box = HBox([toggle])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if change["new"]:
widget_box.children = (toggle,)
else:
widget_box.children = (toggle, dropdown)
toggle.observe(on_toggle_change, names="value")
control = WidgetControl(widget=widget_box, position="topright")
self.add_control(control)
def add_search_marker(self, **kwargs):
"""Adds a search marker to the map."""
from ipyleaflet import SearchControl, Marker, AwesomeIcon
search_marker = Marker(
icon=AwesomeIcon(name="search", marker_color="blue", icon_color="white")
)
search_control = SearchControl(
position="topleft",
url="https://nominatim.openstreetmap.org/search?format=json&q={s}",
zoom=10,
marker=search_marker,
)
self.add(search_control)
def geojson_button(self, **kwargs):
"""Creates a button to add shapefiles."""
from ipywidgets import Button, Text, ToggleButton, HBox, Layout
from ipyleaflet import WidgetControl
# Create widgets
toggle_close = ToggleButton(
value=True, tooltip="Add GeoJSON from link", icon="link"
)
url = Text(
placeholder="Type something",
description="GeoJSON URL:",
disabled=False,
style={"description_width": "initial"},
)
run_button = Button(
description="Add GeoJSON",
disabled=False,
button_style="", # 'success', 'info', 'warning', 'danger' or ''
tooltip="Click me",
size=38,
icon="plus", # (FontAwesome names without the `fa-` prefix)
)
toggle_close.layout = Layout(width="36px", height="36px")
# Assemble and add control
widget_box = HBox([toggle_close])
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if change["new"]:
widget_box.children = (toggle_close,)
else:
widget_box.children = (toggle_close, url, run_button)
def on_button_clicked(change):
"""Handles the url upload."""
if url.value:
# For the first file (if multiple=False)
geojson_path = url.value
self.add_geojson(geojson_path, name=url.value)
toggle_close.observe(on_toggle_change, names="value")
run_button.on_click(on_button_clicked)
control = WidgetControl(widget=widget_box, position="topright")
self.add_control(control)
__init__(self, center=[37.5, -95], zoom=4, height='600px', **kwargs)
special
¶
Initialize the map with a given center and zoom level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
list |
Center coordinates of the map. Defaults to [37.5, -95]. |
[37.5, -95] |
zoom |
int |
Zoom level of the map. Defaults to 4. |
4 |
height |
str |
Height of the map. Defaults to "600px". |
'600px' |
**kwargs |
Additional keyword arguments for ipyleaflet.Map. |
{} |
Source code in forestspot/interactive.py
def __init__(self, center=[37.5, -95], zoom=4, height="600px", **kwargs):
"""Initialize the map with a given center and zoom level.
Args:
center (list, optional): Center coordinates of the map. Defaults to [37.5, -95].
zoom (int, optional): Zoom level of the map. Defaults to 4.
height (str, optional): Height of the map. Defaults to "600px".
**kwargs: Additional keyword arguments for ipyleaflet.Map.
"""
super().__init__(center=center, zoom=zoom, **kwargs)
self.layout.height = height
self.scroll_wheel_zoom = True
self.add_layer_control()
self.change_basemap()
self.add_search_marker()
self.upload_points()
self.upload_geojson_file()
self.change_built_in_shapefiles()
self.on_interaction(self.handle_click)
self.geojson_button()
add_basemap(self, basemap='Esri.WorldImagery')
¶
Add basemap to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Basemap name. Defaults to "Esri.WorldImagery". |
'Esri.WorldImagery' |
Source code in forestspot/interactive.py
def add_basemap(self, basemap="Esri.WorldImagery"):
"""Add basemap to the map.
Args:
basemap (str, optional): Basemap name. Defaults to "Esri.WorldImagery".
"""
url = eval(f"ipyleaflet.basemaps.{basemap}").build_url()
layer = ipyleaflet.TileLayer(url=url, name=basemap)
self.add(layer)
add_gdf(self, gdf, **kwargs)
¶
Adds a GeoDataFrame to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gdf |
geopandas.GeoDataFrame |
The GeoDataFrame to add. |
required |
**kwargs |
Additional keyword arguments for the GeoJSON layer. |
{} |
Source code in forestspot/interactive.py
def add_gdf(self, gdf, **kwargs):
"""Adds a GeoDataFrame to the map.
Args:
gdf (geopandas.GeoDataFrame): The GeoDataFrame to add.
**kwargs: Additional keyword arguments for the GeoJSON layer.
"""
gdf = gdf.to_crs(epsg=4326)
geojson = gdf.__geo_interface__
self.add_geojson(geojson, **kwargs)
add_geojson(self, data, hover_style=None, **kwargs)
¶
Adds a GeoJSON layer to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
str or dict |
The GeoJSON data. Can be a file path (str) or a dictionary. |
required |
zoom_to_layer |
bool |
Whether to zoom to the layer's bounds. Defaults to True. |
required |
hover_style |
dict |
Style to apply when hovering over features. Defaults to {"color": "yellow", "fillOpacity": 0.2}. |
None |
**kwargs |
Additional keyword arguments for the ipyleaflet.GeoJSON layer. |
{} |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If the data type is invalid. |
Source code in forestspot/interactive.py
def add_geojson(self, data, hover_style=None, **kwargs):
"""Adds a GeoJSON layer to the map.
Args:
data (str or dict): The GeoJSON data. Can be a file path (str) or a dictionary.
zoom_to_layer (bool, optional): Whether to zoom to the layer's bounds. Defaults to True.
hover_style (dict, optional): Style to apply when hovering over features. Defaults to {"color": "yellow", "fillOpacity": 0.2}.
**kwargs: Additional keyword arguments for the ipyleaflet.GeoJSON layer.
Raises:
ValueError: If the data type is invalid.
"""
import geopandas as gpd
if hover_style is None:
hover_style = {"color": "gray", "fillOpacity": 0.2}
if isinstance(data, str):
gdf = gpd.read_file(data)
geojson = gdf.__geo_interface__
elif isinstance(data, dict):
geojson = data
style = {"color": "black", "weight": 1, "opacity": 1}
if (
hasattr(self, "current_geojson_layer")
and self.current_geojson_layer is not None
):
try:
self.remove_layer(self.current_geojson_layer)
except Exception:
pass # Layer might have already been removed
layer = ipyleaflet.GeoJSON(
data=geojson, hover_style=hover_style, style=style, **kwargs
)
self.current_geojson_layer = layer
self.add_layer(layer)
add_google_map(self, map_type='ROADMAP')
¶
Add Google Map to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
map_type |
str |
Map type. Defaults to "ROADMAP". |
'ROADMAP' |
Source code in forestspot/interactive.py
def add_google_map(self, map_type="ROADMAP"):
"""Add Google Map to the map.
Args:
map_type (str, optional): Map type. Defaults to "ROADMAP".
"""
map_types = {
"ROADMAP": "m",
"SATELLITE": "s",
"HYBRID": "y",
"TERRAIN": "p",
}
map_type = map_types[map_type.upper()]
url = (
f"https://mt1.google.com/vt/lyrs={map_type.lower()}&x={{x}}&y={{y}}&z={{z}}"
)
layer = ipyleaflet.TileLayer(url=url, name="Google Map")
self.add(layer)
add_layer_control(self)
¶
Adds a layer control widget to the map.
Source code in forestspot/interactive.py
def add_layer_control(self):
"""Adds a layer control widget to the map."""
control = ipyleaflet.LayersControl(position="topright")
self.add_control(control)
add_points(self, data, **kwargs)
¶
Adds points to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
str or dict |
The GeoJSON data. Can be a file path (str) or a dictionary. |
required |
**kwargs |
Additional keyword arguments for the ipyleaflet.GeoJSON layer. |
{} |
Source code in forestspot/interactive.py
def add_points(self, data, **kwargs):
"""Adds points to the map.
Args:
data (str or dict): The GeoJSON data. Can be a file path (str) or a dictionary.
**kwargs: Additional keyword arguments for the ipyleaflet.GeoJSON layer.
"""
# Load data with safety checks
if isinstance(data, str):
coordinates = pd.read_csv(data)
gdf = gpd.GeoDataFrame(
coordinates,
geometry=gpd.points_from_xy(coordinates.LON, coordinates.LAT),
crs="EPSG:4326", # Directly set CRS during creation
)
elif isinstance(data, pd.DataFrame):
coordinates = data
gdf = gpd.GeoDataFrame(
coordinates,
geometry=gpd.points_from_xy(coordinates.LON, coordinates.LAT),
crs="EPSG:4326", # Directly set CRS during creation
)
else:
gdf = data.to_crs(epsg=4326) # Ensure WGS84
point_style = {
"radius": 2,
"color": "black",
"dashArray": "2",
"fillOpacity": 1,
"fillColor": "red",
"weight": 1,
} # 'color': 'white',
geo_data = ipyleaflet.GeoData(
geo_dataframe=gdf,
point_style=point_style,
)
self.add(geo_data)
add_search_marker(self, **kwargs)
¶
Adds a search marker to the map.
Source code in forestspot/interactive.py
def add_search_marker(self, **kwargs):
"""Adds a search marker to the map."""
from ipyleaflet import SearchControl, Marker, AwesomeIcon
search_marker = Marker(
icon=AwesomeIcon(name="search", marker_color="blue", icon_color="white")
)
search_control = SearchControl(
position="topleft",
url="https://nominatim.openstreetmap.org/search?format=json&q={s}",
zoom=10,
marker=search_marker,
)
self.add(search_control)
add_shp(self, data, **kwargs)
¶
Adds a shapefile to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
str |
The file path to the shapefile. |
required |
**kwargs |
Additional keyword arguments for the GeoJSON layer. |
{} |
Source code in forestspot/interactive.py
def add_shp(self, data, **kwargs):
"""Adds a shapefile to the map.
Args:
data (str): The file path to the shapefile.
**kwargs: Additional keyword arguments for the GeoJSON layer.
"""
gdf = gpd.read_file(data)
gdf = gdf.to_crs(epsg=4326)
geojson = gdf.__geo_interface__
self.add_geojson(geojson, **kwargs)
add_vector(self, data, **kwargs)
¶
Adds vector data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
str, geopandas.GeoDataFrame, or dict |
The vector data. Can be a file path, GeoDataFrame, or GeoJSON dictionary. |
required |
**kwargs |
Additional keyword arguments for the GeoJSON layer. |
{} |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If the data type is invalid. |
Source code in forestspot/interactive.py
def add_vector(self, data, **kwargs):
"""Adds vector data to the map.
Args:
data (str, geopandas.GeoDataFrame, or dict): The vector data. Can be a file path, GeoDataFrame, or GeoJSON dictionary.
**kwargs: Additional keyword arguments for the GeoJSON layer.
Raises:
ValueError: If the data type is invalid.
"""
import geopandas as gpd
if isinstance(data, str):
gdf = gpd.read_file(data)
self.add_gdf(gdf, **kwargs)
elif isinstance(data, gpd.GeoDataFrame):
self.add_gdf(data, **kwargs)
elif isinstance(data, dict):
self.add_geojson(data, **kwargs)
else:
raise ValueError("Invalid data type")
add_widgets(self)
¶
Creates and displays widgets for user interaction.
Source code in forestspot/interactive.py
def add_widgets(self):
"""Creates and displays widgets for user interaction."""
from ipywidgets import jslink
from ipyleaflet import WidgetControl
# date_picker = widgets.DatePicker(description="Pick a Date", disabled=False)
opacity_slider = widgets.FloatSlider(
value=1,
min=0,
max=1.0,
step=0.01,
description="Opacity",
continuous_update=False,
orientation="horizontal",
readout=True,
readout_format=".2f",
style={"description_width": "initial"},
)
basemap_layer = self.layers[1]
jslink((opacity_slider, "value"), (basemap_layer, "opacity"))
opacity_control = WidgetControl(widget=opacity_slider, position="topright")
self.add(opacity_control)
# self.add(date_control)
change_basemap(self, **kwargs)
¶
Changes the basemap of the map using a dropdown selector.
Source code in forestspot/interactive.py
def change_basemap(self, **kwargs):
"""Changes the basemap of the map using a dropdown selector."""
from ipywidgets import Dropdown, ToggleButton, HBox, Layout
from ipyleaflet import WidgetControl, basemap_to_tiles, basemaps
# Map dropdown names to actual basemap objects
BASEMAP_LOOKUP = {
"OpenStreetMap": basemaps.OpenStreetMap.Mapnik,
"OpenTopoMap": basemaps.OpenTopoMap,
"Esri.WorldImagery": basemaps.Esri.WorldImagery,
"CartoDB.DarkMatter": basemaps.CartoDB.DarkMatter,
"CartoDB.Positron": basemaps.CartoDB.Positron,
"CartoDB.PositronNoLabels": basemaps.CartoDB.PositronNoLabels,
"Esri.WorldTopoMap": basemaps.Esri.WorldTopoMap,
"Esri.WorldStreetMap": basemaps.Esri.WorldStreetMap,
"Esri.OceanBasemap": basemaps.Esri.OceanBasemap,
"Esri.NatGeoWorldMap": basemaps.Esri.NatGeoWorldMap,
"NASAGIBS.ModisTerraTrueColorCR": basemaps.NASAGIBS.ModisTerraTrueColorCR,
"NASAGIBS.ModisAquaTrueColorCR": basemaps.NASAGIBS.ModisAquaTrueColorCR,
"NASAGIBS.ViirsTrueColorCR": basemaps.NASAGIBS.ViirsTrueColorCR,
"NASAGIBS.ViirsEarthAtNight2012": basemaps.NASAGIBS.ViirsEarthAtNight2012,
}
# Create widgets
toggle = ToggleButton(
value=True,
tooltip="Change basemap",
icon="map-o",
layout=Layout(width="36px", height="36px"),
)
dropdown = Dropdown(
options=list(BASEMAP_LOOKUP.keys()),
value="OpenStreetMap",
description="Basemap:",
style={"description_width": "initial"},
layout=Layout(width="250px", height="36px"),
)
# Store reference to current basemap layer
if not hasattr(self, "current_basemap"):
self.current_basemap = self.layers[0]
def on_dropdown_change(change):
"""Handle basemap selection changes"""
if change["new"]:
new_basemap = basemap_to_tiles(BASEMAP_LOOKUP[change["new"]])
self.substitute_layer(self.current_basemap, new_basemap)
self.current_basemap = new_basemap
# def on_toggle_change(change):
# """Toggle visibility of dropdown"""
# if change["new"]:
# dropdown.layout.visibility = 'visible' # Show dropdown
# else:
# dropdown.layout.visibility = 'hidden' # Hide dropdown
# Set up event handlers
dropdown.observe(on_dropdown_change, names="value")
# Assemble and add control
widget_box = HBox([toggle])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if is_open[0] and change["new"]:
widget_box.children = (toggle,)
else:
widget_box.children = (toggle, dropdown)
toggle.observe(on_toggle_change, names="value")
control = WidgetControl(widget=widget_box, position="topright")
self.add_control(control)
change_built_in_shapefiles(self, **kwargs)
¶
Changes the basemap of the map using a dropdown selector.
Source code in forestspot/interactive.py
def change_built_in_shapefiles(self, **kwargs):
"""Changes the basemap of the map using a dropdown selector."""
from ipywidgets import Dropdown, ToggleButton, HBox, Layout
from ipyleaflet import WidgetControl
# Map dropdown names to actual basemap objects
SHAPEFILE_LOOKUP = {
"None": None,
"U.S. States": "../data/us-states.json",
"National Forests": "https://geohub.oregon.gov/api/download/v1/items/b479e4bd7d70439a87e0230c99bddce5/geojson?layers=0",
}
# Create widgets
toggle = ToggleButton(
value=True,
tooltip="Add built-in shapefile",
icon="square-o",
layout=Layout(width="38px", height="38px"),
)
dropdown = Dropdown(
options=list(SHAPEFILE_LOOKUP.keys()),
value="None",
placeholder="Select a shapefile",
description="Select shapefile:",
style={"description_width": "initial"},
layout=Layout(width="250px", height="38px"),
)
# Store reference to current basemap layer
if not hasattr(self, "current_basemap"):
self.current_basemap = self.layers[0]
def on_dropdown_change(change):
"""Handle shapefile selection changes"""
if change["new"]:
geojson_path = SHAPEFILE_LOOKUP[change["new"]]
if geojson_path is not None:
self.add_geojson(geojson_path, name=change["new"])
else:
# Remove existing shapefile layer if "None" is selected
if (
hasattr(self, "current_geojson_layer")
and self.current_geojson_layer is not None
):
try:
self.remove_layer(self.current_geojson_layer)
except Exception:
pass
# Set up event handlers
dropdown.observe(on_dropdown_change, names="value")
# Assemble and add control
widget_box = HBox([toggle])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if change["new"]:
widget_box.children = (toggle,)
else:
widget_box.children = (toggle, dropdown)
toggle.observe(on_toggle_change, names="value")
control = WidgetControl(widget=widget_box, position="topright")
self.add_control(control)
geojson_button(self, **kwargs)
¶
Creates a button to add shapefiles.
Source code in forestspot/interactive.py
def geojson_button(self, **kwargs):
"""Creates a button to add shapefiles."""
from ipywidgets import Button, Text, ToggleButton, HBox, Layout
from ipyleaflet import WidgetControl
# Create widgets
toggle_close = ToggleButton(
value=True, tooltip="Add GeoJSON from link", icon="link"
)
url = Text(
placeholder="Type something",
description="GeoJSON URL:",
disabled=False,
style={"description_width": "initial"},
)
run_button = Button(
description="Add GeoJSON",
disabled=False,
button_style="", # 'success', 'info', 'warning', 'danger' or ''
tooltip="Click me",
size=38,
icon="plus", # (FontAwesome names without the `fa-` prefix)
)
toggle_close.layout = Layout(width="36px", height="36px")
# Assemble and add control
widget_box = HBox([toggle_close])
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if change["new"]:
widget_box.children = (toggle_close,)
else:
widget_box.children = (toggle_close, url, run_button)
def on_button_clicked(change):
"""Handles the url upload."""
if url.value:
# For the first file (if multiple=False)
geojson_path = url.value
self.add_geojson(geojson_path, name=url.value)
toggle_close.observe(on_toggle_change, names="value")
run_button.on_click(on_button_clicked)
control = WidgetControl(widget=widget_box, position="topright")
self.add_control(control)
handle_click(self, **kwargs)
¶
Handles click events on the map.
Source code in forestspot/interactive.py
def handle_click(self, **kwargs):
"""Handles click events on the map."""
if kwargs.get("type") == "click":
self.add_layer(ipyleaflet.Marker(location=kwargs.get("coordinates")))
print(f"Clicked at: {kwargs.get('coordinates')}")
upload_geojson_file(self, **kwargs)
¶
Uploads GeoJSON file to the map.
Source code in forestspot/interactive.py
def upload_geojson_file(self, **kwargs):
"""Uploads GeoJSON file to the map."""
from ipywidgets import FileUpload, Button, ToggleButton, HBox
button = Button(
description="Add GeoJSON file",
disabled=False,
button_style="", # 'success', 'info', 'warning', 'danger' or ''
tooltip="Click me",
size=38,
icon="plus", # (FontAwesome names without the `fa-` prefix)
)
file_upload = FileUpload(
accept=".geojson",
multiple=False,
size=38,
description="Upload GeoJSON",
style={"description_width": "initial"},
)
close_button = ToggleButton(icon="file", value=True, tooltip="Add GeoJSON file")
close_button.layout = widgets.Layout(width="36px", height="36px")
hbox = HBox([close_button])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if change["new"]:
hbox.children = (close_button,)
else:
hbox.children = (close_button, file_upload, button)
def on_button_clicked(change):
"""Handles the url upload."""
if file_upload.value and len(file_upload.value) > 0:
import json
from ipyleaflet import GeoJSON
file_info = file_upload.value[0]
# Get bytes from memoryview
content_bytes = file_info["content"].tobytes()
# Decode bytes to string, then load as JSON
geojson_data = json.loads(content_bytes.decode("utf-8"))
# Add the GeoJSON layer to the map
geo_json = GeoJSON(data=geojson_data)
self.add_layer(geo_json)
close_button.observe(on_toggle_change, names="value")
button.on_click(on_button_clicked)
upload_control = ipyleaflet.WidgetControl(widget=hbox, position="topright")
self.add_control(upload_control)
upload_points(self, **kwargs)
¶
Uploads points to the map.
Source code in forestspot/interactive.py
def upload_points(self, **kwargs):
"""Uploads points to the map."""
from ipywidgets import FileUpload, Button, ToggleButton, HBox
button = Button(
description="Add Points",
disabled=False,
button_style="", # 'success', 'info', 'warning', 'danger' or ''
tooltip="Click me",
size=38,
icon="plus", # (FontAwesome names without the `fa-` prefix)
)
file_upload = FileUpload(
accept=".csv",
multiple=False,
size=38,
description="Upload CSV",
style={"description_width": "initial"},
)
close_button = ToggleButton(
icon="map-pin", value=True, tooltip="Upload points from CSV file"
)
close_button.layout = widgets.Layout(width="36px", height="36px")
hbox = HBox([close_button])
is_open = [True]
def on_toggle_change(change):
"""Toggle visibility of dropdown"""
if is_open[0] and change["new"]:
hbox.children = (close_button,)
else:
hbox.children = (close_button, file_upload, button)
def on_button_clicked(change):
"""Handles the file upload."""
import io
if file_upload.value:
# For the first file (if multiple=False)
file_info = file_upload.value[0]
content_bytes = file_info["content"].tobytes()
points = pd.read_csv(io.BytesIO(content_bytes))
self.add_points(points)
close_button.observe(on_toggle_change, names="value")
button.on_click(on_button_clicked)
upload_control = ipyleaflet.WidgetControl(widget=hbox, position="topright")
self.add_control(upload_control)