Skip to content

split_map module

This module provides a custom Map class that extends folium.Map

Map (Map)

A custom Map class that extends folium.Map.

Source code in beamgis/foliumap.py
class Map(folium.Map):
    """A custom Map class that extends folium.Map."""

    def __init__(self, center=(0, 0), zoom=2, **kwargs):
        """Initializes the Map object.

        Args:
            center (tuple, optional): The initial center of the map as (latitude, longitude). Defaults to (0, 0).
            zoom (int, optional): The initial zoom level of the map. Defaults to 2.
            **kwargs: Additional keyword arguments for the folium.Map class.
        """
        super().__init__(location=center, zoom_start=zoom, **kwargs)

    def add_geojson(
        self,
        data,
        zoom_to_layer=True,
        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 folium.GeoJson layer.

        Raises:
            ValueError: If the data type is invalid.
        """
        import geopandas as gpd

        if hover_style is None:
            hover_style = {"color": "yellow", "fillOpacity": 0.2}

        if isinstance(data, str):
            gdf = gpd.read_file(data)
            geojson = gdf.__geo_interface__
        elif isinstance(data, dict):
            geojson = data

        geojson = folium.GeoJson(data=geojson, **kwargs)
        geojson.add_to(self)

        if zoom_to_layer and gdf is not None:
            bounds = gdf.total_bounds
            self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])

    def add_basemap(self, basemap="OpenStreetMap"):
        """Add basemap to the map using Folium's built-in tiles or a custom TileLayer.

        Args:
            basemap (str or dict, optional): Basemap name (dotted format) or a custom basemap dict.
                Examples:
                    "CartoDB.DarkMatter"
                    {
                        "tiles": "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
                        "name": "OpenTopoMap",
                        "attr": "© OpenTopoMap contributors"
                    }
        """
        # Built-in basemap mapping
        basemap_mapping = {
            "OpenStreetMap": "OpenStreetMap",
            "CartoDB.Positron": "CartoDB positron",
            "CartoDB.DarkMatter": "CartoDB dark_matter",
        }

        if isinstance(basemap, str):
            if basemap not in basemap_mapping:
                raise ValueError(
                    f"Basemap '{basemap}' not supported. Available options: {list(basemap_mapping.keys())}"
                )
            tile_name = basemap_mapping[basemap]
            tile_layer = folium.TileLayer(tiles=tile_name, name=basemap, control=True)
        elif isinstance(basemap, dict):
            required_keys = {"tiles", "name", "attr"}
            if not required_keys.issubset(basemap):
                raise ValueError(
                    "Custom basemap dict must include 'tiles', 'name', and 'attr'"
                )
            tile_layer = folium.TileLayer(
                tiles=basemap["tiles"],
                name=basemap["name"],
                attr=basemap["attr"],
                control=True,
            )
        else:
            raise TypeError(
                "Basemap must be a string or a dictionary with 'tiles', 'name', and 'attr'."
            )

        tile_layer.add_to(self)

    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.
        """
        import geopandas as gpd

        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 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 add_layer_control(self):
        """Adds a layer control widget to the map."""
        folium.LayerControl().add_to(self)

    def add_split_map(
        self,
        left="openstreetmap",
        right="cartodbpositron",
        colormap_left=None,
        colormap_right=None,
        opacity_left=1.0,
        opacity_right=1.0,
        **kwargs,
    ):
        """
        Adds a split map view to the current map, allowing users to compare two different map layers side by side.

        Parameters:
            left (str): The tile layer or path to a raster file for the left side of the map.
            right (str): The tile layer or path to a raster file for the right side of the map.
            colormap_left (callable): Colormap function for the left raster layer (if applicable).
            colormap_right (callable): Colormap function for the right raster layer (if applicable).
            opacity_left (float): Opacity for the left layer.
            opacity_right (float): Opacity for the right layer.
            **kwargs: Additional keyword arguments to customize the tile layers.

        Returns:
            None
        """

        # Handle left layer
        if isinstance(left, str) and left.lower().endswith((".tif", ".tiff")):
            client_left = TileClient(left)
            layer_left = get_folium_tile_layer(
                client_left,
                name="Left Layer",
                colormap=colormap_left,
                opacity=opacity_left,
                **kwargs,
            )
        else:
            layer_left = folium.TileLayer(
                left, name="Left Layer", opacity=opacity_left, **kwargs
            )

        # Handle right layer
        if isinstance(right, str) and right.lower().endswith((".tif", ".tiff")):
            client_right = TileClient(right)
            layer_right = get_folium_tile_layer(
                client_right,
                name="Right Layer",
                colormap=colormap_right,
                opacity=opacity_right,
                **kwargs,
            )
        else:
            layer_right = folium.TileLayer(
                right, name="Right Layer", opacity=opacity_right, **kwargs
            )

        # Add layers to map
        layer_left.add_to(self)
        layer_right.add_to(self)

        # Add split map control
        sbs = folium.plugins.SideBySideLayers(
            layer_left=layer_left, layer_right=layer_right
        )
        sbs.add_to(self)

    def add_heatmap(
        self,
        data: Union[str, List[List[float]], pd.DataFrame],
        latitude: Optional[str] = "latitude",
        longitude: Optional[str] = "longitude",
        value: Optional[str] = "value",
        name: Optional[str] = "Heat map",
        radius: Optional[int] = 25,
        **kwargs,
    ):
        """Adds a heat map to the map. Reference: https://stackoverflow.com/a/54756617

        Args:
            data (str | list | pd.DataFrame): File path or HTTP URL to the input file or a list of data points in the format of [[x1, y1, z1], [x2, y2, z2]]. For example, https://raw.githubusercontent.com/opengeos/leafmap/master/examples/data/world_cities.csv
            latitude (str, optional): The column name of latitude. Defaults to "latitude".
            longitude (str, optional): The column name of longitude. Defaults to "longitude".
            value (str, optional): The column name of values. Defaults to "value".
            name (str, optional): Layer name to use. Defaults to "Heat map".
            radius (int, optional): Radius of each “point” of the heatmap. Defaults to 25.

        Raises:
            ValueError: If data is not a list.
        """
        import pandas as pd

        try:
            if isinstance(data, str):
                df = pd.read_csv(data)
                data = df[[latitude, longitude, value]].values.tolist()
            elif isinstance(data, pd.DataFrame):
                data = data[[latitude, longitude, value]].values.tolist()
            elif isinstance(data, list):
                pass
            else:
                raise ValueError("data must be a list, a DataFrame, or a file path.")

            plugins.HeatMap(data, name=name, radius=radius, **kwargs).add_to(
                folium.FeatureGroup(name=name).add_to(self)
            )
        except Exception as e:
            raise Exception(e)

__init__(self, center=(0, 0), zoom=2, **kwargs) special

Initializes the Map object.

Parameters:

Name Type Description Default
center tuple

The initial center of the map as (latitude, longitude). Defaults to (0, 0).

(0, 0)
zoom int

The initial zoom level of the map. Defaults to 2.

2
**kwargs

Additional keyword arguments for the folium.Map class.

{}
Source code in beamgis/foliumap.py
def __init__(self, center=(0, 0), zoom=2, **kwargs):
    """Initializes the Map object.

    Args:
        center (tuple, optional): The initial center of the map as (latitude, longitude). Defaults to (0, 0).
        zoom (int, optional): The initial zoom level of the map. Defaults to 2.
        **kwargs: Additional keyword arguments for the folium.Map class.
    """
    super().__init__(location=center, zoom_start=zoom, **kwargs)

add_basemap(self, basemap='OpenStreetMap')

Add basemap to the map using Folium's built-in tiles or a custom TileLayer.

Parameters:

Name Type Description Default
basemap str or dict

Basemap name (dotted format) or a custom basemap dict. Examples: "CartoDB.DarkMatter" { "tiles": "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", "name": "OpenTopoMap", "attr": "© OpenTopoMap contributors" }

'OpenStreetMap'
Source code in beamgis/foliumap.py
def add_basemap(self, basemap="OpenStreetMap"):
    """Add basemap to the map using Folium's built-in tiles or a custom TileLayer.

    Args:
        basemap (str or dict, optional): Basemap name (dotted format) or a custom basemap dict.
            Examples:
                "CartoDB.DarkMatter"
                {
                    "tiles": "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
                    "name": "OpenTopoMap",
                    "attr": "© OpenTopoMap contributors"
                }
    """
    # Built-in basemap mapping
    basemap_mapping = {
        "OpenStreetMap": "OpenStreetMap",
        "CartoDB.Positron": "CartoDB positron",
        "CartoDB.DarkMatter": "CartoDB dark_matter",
    }

    if isinstance(basemap, str):
        if basemap not in basemap_mapping:
            raise ValueError(
                f"Basemap '{basemap}' not supported. Available options: {list(basemap_mapping.keys())}"
            )
        tile_name = basemap_mapping[basemap]
        tile_layer = folium.TileLayer(tiles=tile_name, name=basemap, control=True)
    elif isinstance(basemap, dict):
        required_keys = {"tiles", "name", "attr"}
        if not required_keys.issubset(basemap):
            raise ValueError(
                "Custom basemap dict must include 'tiles', 'name', and 'attr'"
            )
        tile_layer = folium.TileLayer(
            tiles=basemap["tiles"],
            name=basemap["name"],
            attr=basemap["attr"],
            control=True,
        )
    else:
        raise TypeError(
            "Basemap must be a string or a dictionary with 'tiles', 'name', and 'attr'."
        )

    tile_layer.add_to(self)

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 beamgis/foliumap.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, zoom_to_layer=True, 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.

True
hover_style dict

Style to apply when hovering over features. Defaults to {"color": "yellow", "fillOpacity": 0.2}.

None
**kwargs

Additional keyword arguments for the folium.GeoJson layer.

{}

Exceptions:

Type Description
ValueError

If the data type is invalid.

Source code in beamgis/foliumap.py
def add_geojson(
    self,
    data,
    zoom_to_layer=True,
    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 folium.GeoJson layer.

    Raises:
        ValueError: If the data type is invalid.
    """
    import geopandas as gpd

    if hover_style is None:
        hover_style = {"color": "yellow", "fillOpacity": 0.2}

    if isinstance(data, str):
        gdf = gpd.read_file(data)
        geojson = gdf.__geo_interface__
    elif isinstance(data, dict):
        geojson = data

    geojson = folium.GeoJson(data=geojson, **kwargs)
    geojson.add_to(self)

    if zoom_to_layer and gdf is not None:
        bounds = gdf.total_bounds
        self.fit_bounds([[bounds[1], bounds[0]], [bounds[3], bounds[2]]])

add_heatmap(self, data, latitude='latitude', longitude='longitude', value='value', name='Heat map', radius=25, **kwargs)

Adds a heat map to the map. Reference: https://stackoverflow.com/a/54756617

Parameters:

Name Type Description Default
data str | list | pd.DataFrame

File path or HTTP URL to the input file or a list of data points in the format of [[x1, y1, z1], [x2, y2, z2]]. For example, https://raw.githubusercontent.com/opengeos/leafmap/master/examples/data/world_cities.csv

required
latitude str

The column name of latitude. Defaults to "latitude".

'latitude'
longitude str

The column name of longitude. Defaults to "longitude".

'longitude'
value str

The column name of values. Defaults to "value".

'value'
name str

Layer name to use. Defaults to "Heat map".

'Heat map'
radius int

Radius of each “point” of the heatmap. Defaults to 25.

25

Exceptions:

Type Description
ValueError

If data is not a list.

Source code in beamgis/foliumap.py
def add_heatmap(
    self,
    data: Union[str, List[List[float]], pd.DataFrame],
    latitude: Optional[str] = "latitude",
    longitude: Optional[str] = "longitude",
    value: Optional[str] = "value",
    name: Optional[str] = "Heat map",
    radius: Optional[int] = 25,
    **kwargs,
):
    """Adds a heat map to the map. Reference: https://stackoverflow.com/a/54756617

    Args:
        data (str | list | pd.DataFrame): File path or HTTP URL to the input file or a list of data points in the format of [[x1, y1, z1], [x2, y2, z2]]. For example, https://raw.githubusercontent.com/opengeos/leafmap/master/examples/data/world_cities.csv
        latitude (str, optional): The column name of latitude. Defaults to "latitude".
        longitude (str, optional): The column name of longitude. Defaults to "longitude".
        value (str, optional): The column name of values. Defaults to "value".
        name (str, optional): Layer name to use. Defaults to "Heat map".
        radius (int, optional): Radius of each “point” of the heatmap. Defaults to 25.

    Raises:
        ValueError: If data is not a list.
    """
    import pandas as pd

    try:
        if isinstance(data, str):
            df = pd.read_csv(data)
            data = df[[latitude, longitude, value]].values.tolist()
        elif isinstance(data, pd.DataFrame):
            data = data[[latitude, longitude, value]].values.tolist()
        elif isinstance(data, list):
            pass
        else:
            raise ValueError("data must be a list, a DataFrame, or a file path.")

        plugins.HeatMap(data, name=name, radius=radius, **kwargs).add_to(
            folium.FeatureGroup(name=name).add_to(self)
        )
    except Exception as e:
        raise Exception(e)

add_layer_control(self)

Adds a layer control widget to the map.

Source code in beamgis/foliumap.py
def add_layer_control(self):
    """Adds a layer control widget to the map."""
    folium.LayerControl().add_to(self)

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 beamgis/foliumap.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.
    """
    import geopandas as gpd

    gdf = gpd.read_file(data)
    gdf = gdf.to_crs(epsg=4326)
    geojson = gdf.__geo_interface__
    self.add_geojson(geojson, **kwargs)

add_split_map(self, left='openstreetmap', right='cartodbpositron', colormap_left=None, colormap_right=None, opacity_left=1.0, opacity_right=1.0, **kwargs)

Adds a split map view to the current map, allowing users to compare two different map layers side by side.

Parameters:

Name Type Description Default
left str

The tile layer or path to a raster file for the left side of the map.

'openstreetmap'
right str

The tile layer or path to a raster file for the right side of the map.

'cartodbpositron'
colormap_left callable

Colormap function for the left raster layer (if applicable).

None
colormap_right callable

Colormap function for the right raster layer (if applicable).

None
opacity_left float

Opacity for the left layer.

1.0
opacity_right float

Opacity for the right layer.

1.0
**kwargs

Additional keyword arguments to customize the tile layers.

{}

Returns:

Type Description

None

Source code in beamgis/foliumap.py
def add_split_map(
    self,
    left="openstreetmap",
    right="cartodbpositron",
    colormap_left=None,
    colormap_right=None,
    opacity_left=1.0,
    opacity_right=1.0,
    **kwargs,
):
    """
    Adds a split map view to the current map, allowing users to compare two different map layers side by side.

    Parameters:
        left (str): The tile layer or path to a raster file for the left side of the map.
        right (str): The tile layer or path to a raster file for the right side of the map.
        colormap_left (callable): Colormap function for the left raster layer (if applicable).
        colormap_right (callable): Colormap function for the right raster layer (if applicable).
        opacity_left (float): Opacity for the left layer.
        opacity_right (float): Opacity for the right layer.
        **kwargs: Additional keyword arguments to customize the tile layers.

    Returns:
        None
    """

    # Handle left layer
    if isinstance(left, str) and left.lower().endswith((".tif", ".tiff")):
        client_left = TileClient(left)
        layer_left = get_folium_tile_layer(
            client_left,
            name="Left Layer",
            colormap=colormap_left,
            opacity=opacity_left,
            **kwargs,
        )
    else:
        layer_left = folium.TileLayer(
            left, name="Left Layer", opacity=opacity_left, **kwargs
        )

    # Handle right layer
    if isinstance(right, str) and right.lower().endswith((".tif", ".tiff")):
        client_right = TileClient(right)
        layer_right = get_folium_tile_layer(
            client_right,
            name="Right Layer",
            colormap=colormap_right,
            opacity=opacity_right,
            **kwargs,
        )
    else:
        layer_right = folium.TileLayer(
            right, name="Right Layer", opacity=opacity_right, **kwargs
        )

    # Add layers to map
    layer_left.add_to(self)
    layer_right.add_to(self)

    # Add split map control
    sbs = folium.plugins.SideBySideLayers(
        layer_left=layer_left, layer_right=layer_right
    )
    sbs.add_to(self)

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 beamgis/foliumap.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")