将 folium.plugins.HeatMap() 数据点的颜色梯度调整为提供的 branca.colormap

Adapt color gradient of folium.plugins.HeatMap() datapoints to provided branca.colormap

问题描述

当创建一个关于 folium 地图绘制函数中颜色的字典 gradient 时,lon-lat - 数据点不显示在输出 graphic/map 上 folium.plugins.HeatMap(). 可以找到函数文档 here。 这是因为我的 gradient-dict 是通过 RGBA 颜色值而不是粗体颜色字符串(例如 "blue"、"green" 等)传递的吗? 如果我没有根据实际地图绘制的颜色图调整颜色范围,我的数据点将始终以默认方式显示(类似于从蓝色到黄色再到红色)。

顺便说一句,我还想知道如何更改 branca.colormap.caption 和索引标签的 字体大小和颜色 。 它始终以黑色显示,并具有默认字体大小。有时,这在当前地图背景下并不明显。

预期输出

我的 folium.plugins.HeatMap() 的数据点是根据我通过 branca.colormaps 创建的相同颜色图绘制的。我认为这可以通过 folium.plugins.HeatMap().

的梯度参数实现

此外,我想更改 branca.colormap.caption 的字体大小和颜色,以更好地适应实际的卫星地图背景。默认颜色"black"部分看得懂。

打印输出(folium.__version__):

'0.10.1'

Python 带有 folium 和 branca 实现的代码 - 带有 colorscale

的热图
# Package importing
import folium
import folium.plugins as fol_plugins


# Instantiate folium base map to plot on
folium_map = folium.Map(location=coord_center_point,
                        zoom_start=zoom_level,
                        max_zoom=max_zoom,
                        tiles=mapbox_tile_URL,
                        attr='Mapbox')

## Add BRANCA colormap ##
import branca.colormap as branca_folium_cm
colormap = branca_folium_cm.linear.Blues_05.scale(z_min, z_max)
colormap.caption = "Bla bla"  # how do I change fontsize and color here?
folium_map.add_child(colormap)

# Prepare gradient dictionary according to the example like {0.4: ‘blue’, 0.65: ‘lime’, 1: ‘red’}
gradient_dict = {}
# Get the index values and colors from the just created branca-colormap
# NOTE: colors are RGBA-vectors, like "(0.9372549019607843, 0.9529411764705882, 1.0, 1.0)":
for ind_val, c in zip(colormap.index, colormap.colors):
    # Create gradient dictionary for heatmap on the fly
    gradient_dict[ind_val] = c

# Resulting gradient dict in my case:
# {1.4117859851611496e-05: (0.9372549019607843, 0.9529411764705882, 1.0, 1.0), 0.00247235752568163: (0.7411764705882353, 0.8431372549019608, 0.9058823529411765, 1.0), 0.004930597191511649: (0.4196078431372549, 0.6823529411764706, 0.8392156862745098, 1.0), 0.007388836857341667: (0.19215686274509805, 0.5098039215686274, 0.7411764705882353, 1.0), 0.009847076523171685: (0.03137254901960784, 0.3176470588235294, 0.611764705882353, 1.0)}

# Overlay the heatmap data on top of the previously instantiated folium basemap
fol_plugins.HeatMap(data=zip(y, x, z),
                    name=titlestr,
                    min_opacity=min_alpha_opacity,
                    max_zoom=max_zoom,
                    radius=radius,
                    gradient=gradient_dict,  # insert gradient dict
                    blur=blur).add_to(folium_map)

我自己找到了一个解决方案,也在 GitHub 的以下讨论中找到了答案: https://github.com/python-visualization/folium/issues/1303#issuecomment-675368594

1) 自己的解决方案

## * Create a gradient dictionary adapted to the colormap plotted previously * ##
# Instantiate empty dictionary
perc_color_gradient_dict = {}
color_gradient_dict = {}
size_gradient_dict = {}
max_size_val = 12

# Loop over index-values and according colors in RGBA-vector-space
for ind_val, c in zip(colormap.index, colormap.colors):
    # Calculate the percentile of the current index value as the gradient dictionary takes
    # only percentages of the entire value range, not the absolute values themselves
    perc_val = (ind_val - min_z) / ((max_z - min_z))

    # * Get the hex-color, if desired -> PREFERRED
    # NOTE on scope of choosing hex-colors over actual color-names:
    # The colorname space is very small for built-in folium plugins, wherefore only standard names can be passed
    # to the employed functions, e.g. via the color_gradient_dict for adaptation to the colormap
    # -> Here, hex-colors come in handy as they can be interpreted by folium.plugins
    # NOTE on former approach:
    # Get the actual or closest colorname fitting to the current color
    # actual_c_name, closest_c_name = aux_plot.get_exact_or_closest_colorname_to_RGB_color(c)
    hex_color = aux_plot.convert_given_color_to_RGB_RGBA_hex_or_HSV(
        c, return_type="hex", keep_alpha=keep_alpha)
    # Add it to the color_gradient_dicts
    # NOTE on scope: this is only for information purposes, it'll be printed out below
    perc_color_gradient_dict[perc_val] = hex_color
    color_gradient_dict[ind_val] = hex_color
    # Finally, create an size-gradient dictionary alongside with the color dicts
    size_gradient_dict[ind_val] = perc_val * max_size_val


def value_filter_from_dict(val, gradient_dict=None):

    range_limit_keys = np.array(sorted(list(gradient_dict.keys())))

    filtered_array = range_limit_keys[val < range_limit_keys]
    if len(filtered_array) > 0:
        left_border_key = min(filtered_array)
    else:
        left_border_key = max(range_limit_keys)

    return gradient_dict[left_border_key]


# NOTE on implementation syntax:
# If avoiding lambda is a concern, .apply(value_filter_from_dict, gradient_dict=color_gradient_dict) also does the job
# 
# -> use key
df['color'] = df[z_varname_new].swifter.apply(
    lambda val: value_filter_from_dict(val, gradient_dict=color_gradient_dict))
df['size'] = df[z_varname_new].swifter.apply(
    lambda val: value_filter_from_dict(val, gradient_dict=size_gradient_dict))

下面显示了每个 color-gradient 词典的打印输出。

## * Printout for user * ##
# In:
print(
    f"\nThe final percentage color gradient dictionary is:\n{perc_color_gradient_dict}"
)
print(f"\nThe final color gradient dictionary is:\n{color_gradient_dict}")
print(f"\nThe final size gradient dictionary is:\n{size_gradient_dict}")

# Out:
The final percentage color gradient dictionary is:
{0.0: '#ffffb2', 0.05: '#fff7a4', 0.1: '#ffef97', 0.15000000000000002: '#ffe789', 0.2: '#fedf7c', 0.25: '#fed76e', 0.30000000000000004: '#fecf61', 0.35000000000000003: '#fec559', 0.4: '#febb54', 0.45: '#feb14f', 0.5: '#fda849', 0.55: '#fd9e44', 0.6000000000000001: '#fd943f', 0.65: '#fc873a', 0.7000000000000001: '#f87535', 0.75: '#f36330', 0.8: '#ef502b', 0.85: '#eb3e26', 0.9: '#e72c21', 0.95: '#e31a1c'}

The final color gradient dictionary is:
{1.4304179517249468e-05: '#ffffb2', 0.000263588970541387: '#fff7a4', 0.0005128737615655246: '#ffef97', 0.0007621585525896622: '#ffe789', 0.0010114433436137996: '#fedf7c', 0.001260728134637937: '#fed76e', 0.0015100129256620748: '#fecf61', 0.0017592977166862123: '#fec559', 0.0020085825077103495: '#febb54', 0.002257867298734487: '#feb14f', 0.0025071520897586245: '#fda849', 0.002756436880782762: '#fd9e44', 0.0030057216718069: '#fd943f', 0.0032550064628310373: '#fc873a', 0.003504291253855175: '#f87535', 0.0037535760448793123: '#f36330', 0.00400286083590345: '#ef502b', 0.004252145626927588: '#eb3e26', 0.004501430417951725: '#e72c21', 0.004750715208975863: '#e31a1c'}

The final size gradient dictionary is:
{1.4304179517249468e-05: 0.0, 0.000263588970541387: 0.6000000000000001, 0.0005128737615655246: 1.2000000000000002, 0.0007621585525896622: 1.8000000000000003, 0.0010114433436137996: 2.4000000000000004, 0.001260728134637937: 3.0, 0.0015100129256620748: 3.6000000000000005, 0.0017592977166862123: 4.2, 0.0020085825077103495: 4.800000000000001, 0.002257867298734487: 5.4, 0.0025071520897586245: 6.0, 0.002756436880782762: 6.6000000000000005, 0.0030057216718069: 7.200000000000001, 0.0032550064628310373: 7.800000000000001, 0.003504291253855175: 8.4, 0.0037535760448793123: 9.0, 0.00400286083590345: 9.600000000000001, 0.004252145626927588: 10.2, 0.004501430417951725: 10.8, 0.004750715208975863: 11.399999999999999}

最后,为了完整起见,我将post之前使用的函数的细节convert_given_color_to_RGB_RGBA_hex_or_HSV():

def convert_given_color_to_RGB_RGBA_hex_or_HSV(color,
                                               return_type="rgb",
                                               rgb_n_rgba_as_int=False,
                                               keep_alpha=True,
                                               alpha=None):
    """Function documentation:\n
    Accepts the following input and output color formats:
        - HSV (see: https://en.wikipedia.org/wiki/HSL_and_HSV)
        - RGB
        - RGBA
        - hex
        - color-name (string)

    # NOTE on return type preference:
    Prefer matplotlib- over webcolors-return-type within the realm of python

    # NOTE on hex-type as the choice for in-between conversion step:
    -> This is the link between both worlds of matplotlib and webcolors which works for both,
    i.e. which is compatible between both libraries
    """

    # Check whether a correct value has been passed as return-type
    if return_type.lower() not in ["rgb", "rgba", "hsv", "hex"]:
        raise Exception(
            f"The given return type '{return_type}' has not been implemented.")
    else:
        return_type = return_type.lower()  # saves coding later

    # Save initial color for later error-message, if necessary
    original_color = color

    # * i) String
    if isinstance(color, str):
        ## CONVERT to hex-type
        # I) Indicates the case of a hex-color, such as '#08519c'
        if "#" in color:
            pass  # is already of hex-type (compatible with everything)

        # II) Considers percent-string-triplets, such as (u'100%', u'100%', u'100%')
        elif "%" in color:
            # NOTE: use the hex-type as connective link between both libraries
            color = webcolors.rgb_percent_to_hex(color)  # convert to hex-type

        # III) Any other name provided like "black"
        else:
            # NOTE: use the hex-type as connective link between both libraries
            color = webcolors.name_to_hex(color)  # convert to hex-type
            if alpha is not None and keep_alpha:
                color = mpl.colors.to_hex(mpl.colors.to_rgba(color,
                                                             alpha=alpha),
                                          keep_alpha=keep_alpha)

    # * ii) HSV, RGB or RGBA
    else:
        ## CONVERT to hex-type
        # NOTE on keep_alpha: refers to the alpha information of a passed color, like the 4D-RGBA-vectors
        color = mpl.colors.to_hex(color, keep_alpha=keep_alpha)

    ## * 0) Special previous check for hex-type
    if return_type == "hex":
        return color  # is already hex

    ## * 1) Matplotlib
    try:
        # Distinguish between the different return types
        if return_type == "rgb":
            if not rgb_n_rgba_as_int:  # as percent
                return mpl.colors.to_rgb(color)
            else:
                # NOTE: use webcolors for integer RGB-colors without alpha
                # CAUTION: as webcolors cannot handle alpha values, convert possible alpha-hex to normal hex prior to passing them to webcolors-functions
                rgb_as_int = webcolors.hex_to_rgb(
                    mpl.colors.to_hex(mpl.colors.to_rgb(color)))
                return (rgb_as_int.red, rgb_as_int.green, rgb_as_int.blue)

        elif return_type == "rgba":
            if not rgb_n_rgba_as_int:  # as percent
                return mpl.colors.to_rgba(color, alpha=alpha)
            else:
                # NOTE: use webcolors for integer RGB-colors without alpha
                # CAUTION: as webcolors cannot handle alpha values, convert possible alpha-hex to normal hex prior to passing them to webcolors-functions
                rgb_as_int = webcolors.hex_to_rgb(
                    mpl.colors.to_hex(mpl.colors.to_rgb(color)))
                return (rgb_as_int.red, rgb_as_int.green, rgb_as_int.blue,
                        alpha)

        elif return_type == "hsv":
            return mpl.colors.rgb_to_hsv(mpl.colors.to_rgb(color))

    ## * 2) Webcolors
    # NOTE on possible return values: webcolors doesn't have RGBA- and HSV-support
    except:
        # Distinguish between the different return types
        if return_type == "rgb":
            # NOTE: use webcolors for integer RGB-colors without alpha
            # CAUTION: as webcolors cannot handle alpha values, convert possible alpha-hex to normal hex prior to passing them to webcolors-functions
            return webcolors.hex_to_rgb(
                mpl.colors.to_hex(mpl.colors.to_rgb(color)))

    # If this part of the code is reached, something went wrong and it would return None
    raise Exception(
        f"No proper color could be returned for the given color '{original_color}'."
    )

2) 来自 GitHub

的回答

从那里开始,我将提及以下重要部分:

# Get the index values and colors from the just created branca-colormap
# NOTE: colors are RGBA-vectors, like "(0.9372549019607843, 0.9529411764705882, 1.0, 1.0)":
for ind_val, c in zip(colormap.index, colormap.colors):
    # Create gradient dictionary for heatmap on the fly
    r, g, b, a = c
    gradient_dict[ind_val] = f"rgba({r},{g},{b},{a})"

与我最初方法的不同之处在于,我将 color-tuples 作为长度为 4 的元组直接传递,例如(0.9372549019607843, 0.9529411764705882, 1.0, 1.0)。 正确的形式就像上面提到的由 RGBA 构造的循环中的 f-string - values:

f"rgba({r},{g},{b},{a})"