Folium plot GeoJson 根据自定义值在多边形中填充颜色
Folium plot GeoJson fill color in polygon based on custom values
我有 lat/long 值的多边形与 GeoDataFrame
中的标识符相关联,如下所示。考虑一个有两个标识符 A
和 B
的例子,多边形 A
有三个点, B
有四个点,它们的 lat/long 值如下所示。对应于每个点(lat/long),我也有一个关联的数值,如最后一列所示。
id geometry values
A POLYGON((lat_A_1 long_A_1, lat_A_2 long_A_2, lat_A_3 long_A_3)) 10,12,13
B POLYGON((lat_B_1 long_B_1, lat_B_2 long_B_2, lat_B_3 long_B_3, lat_B_4 long_B_4)) 4,8,16,20
我遍历 GeoDataFrame 并使用此代码在地图上绘制这些多边形
geo_j = folium.GeoJson(data=geo_j,
style_function={
'fillColor': 'blue'
})
有没有一种方法可以根据 GeoDataFrame 中的 values
列使用自定义颜色图填充多边形,例如红色代表 0-5,蓝色代表 6-10,绿色代表 11- 20.这怎么能做到?
- 首先获取一些多边形并定义每个点的值(生成 MWE 示例数据集)
- 这意味着与多边形关联的值与多边形中的点一样多。您请求使用 folium 的解决方案,该解决方案使用自定义颜色图值填充多边形。这意味着您需要一个函数将所有这些值同化为多边形的单个值(一种颜色)。我使用了 mode,最常见的值。这可以是均值、中位数或任何其他函数。
- 解决方案然后变得简单,它是
folium.GeoJson()
使用并适当地构造 style_function
- 扩展答案。您可以将多边形拆分为更小的多边形,并将子多边形的颜色与一个点相关联。 folium 生产没有变化(包括iso_a3)只是为了更容易查看
- shapley 提供了两种分割多边形的方法 https://shapely.readthedocs.io/en/stable/manual.html#shapely.ops.triangulate。发现voronoi效果更好
生成 MWE 数据
# some polygons
gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[lambda d: d["iso_a3"].isin(["BEL", "LUX", "NLD", "DEU", "AUT"]), ["geometry"]]
# comma separated values column... between 0 and 20...
gdf["values"] = gdf.geometry.apply(lambda p: ",".join([str(int(sum(xy)) % 20) for xy in p.exterior.coords]))
# id column
gdf["id"] = list("ABCDEFGHIJ")[0 : len(gdf)]
gdf = gdf.set_index("id", drop=False)
数据
geometry values id
id
A POLYGON ((16.97967 48.12350, 16.9037... 5,4,4,4,3,2,1,1,0,19,19,18,17,17,16,... A
B POLYGON ((14.11969 53.75703, 14.3533... 7,7,7,7,6,6,6,5,5,4,4,3,2,2,2,2,2,1,... B
C POLYGON ((6.04307 50.12805, 6.24275 ... 16,16,15,15,15,15,16 C
D POLYGON ((6.15666 50.80372, 6.04307 ... 16,16,15,15,14,14,13,13,13,13,14,14,... D
E POLYGON ((6.90514 53.48216, 7.09205 ... 0,0,19,18,17,16,16,16,15,14,14,15,17... E
解决方案
import statistics as st
import branca.colormap
import geopandas as gpd
import folium
m = folium.Map(
location=[
sum(gdf.geometry.total_bounds[[1, 3]]) / 2,
sum(gdf.geometry.total_bounds[[0, 2]]) / 2,
],
zoom_start=5,
control_scale=True,
)
# style the polygons based on "values" property
def style_fn(feature):
cm = branca.colormap.LinearColormap(["mistyrose", "tomato", "red"], vmin=0, vmax=20)
most_common = st.mode([int(v) for v in feature["properties"]["values"].split(",")])
ss = {
"fillColor": cm(most_common),
"fillOpacity": 0.8,
"weight": 0.8,
"color": cm(most_common),
}
return ss
folium.GeoJson(
gdf.__geo_interface__,
style_function=style_fn,
tooltip=folium.features.GeoJsonTooltip(["id", "values"]),
).add_to(m)
m
将多边形拆分成多个部分
import statistics as st
import branca.colormap
import geopandas as gpd
import folium
import shapely.geometry
import shapely.ops
import pandas as pd
# some polygons
# fmt: off
gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[lambda d: d["iso_a3"].isin(["BEL", "LUX", "NLD", "DEU", "AUT","POL"]), ["geometry", "iso_a3"]]
# comma separated values column... between 0 and 20...
gdf["values"] = gdf.geometry.apply(lambda p: ",".join([str(int(sum(xy)) % 20) for xy in p.exterior.coords]))
# id column
gdf["id"] = list("ABCDEFGHIJ")[0 : len(gdf)]
gdf = gdf.set_index("id", drop=False)
# fmt: on
def sub_polygons(r, method="voronoi"):
g = r["geometry"]
# split into sub-polygons
if method == "voronoi":
geoms = shapely.ops.voronoi_diagram(g).geoms
elif method == "triangulate":
geoms = [
p
for p in shapely.ops.triangulate(g)
if isinstance(p.intersection(g), shapely.geometry.Polygon)
]
else:
raise "invalid polygon ops method"
# clip sub-geometries
geoms = [p.intersection(g) for p in geoms]
vs = r["values"].split(",")
vr = []
# order or sub-polygons and points are differenct. use value from point
# in sub-polygon
for vg in geoms:
for i, xy in enumerate(g.exterior.coords):
if not shapely.geometry.Point(xy).intersection(vg).is_empty:
break
vr.append(vs[i])
return [{**r.to_dict(), **{"geometry": g, "values": v}} for g, v in zip(geoms, vr)]
gdf2 = gpd.GeoDataFrame(
gdf.apply(sub_polygons, axis=1, method="voronoi").explode().apply(pd.Series)
)
m = folium.Map(
location=[
sum(gdf.geometry.total_bounds[[1, 3]]) / 2,
sum(gdf.geometry.total_bounds[[0, 2]]) / 2,
],
zoom_start=5,
control_scale=True,
)
# style the polygons based on "values" property
def style_fn(feature):
cm = branca.colormap.LinearColormap(["mistyrose", "tomato", "red"], vmin=0, vmax=20)
most_common = st.mode([int(v) for v in feature["properties"]["values"].split(",")])
ss = {
"fillColor": cm(most_common),
"fillOpacity": 0.8,
"weight": 0.8,
"color": cm(most_common),
}
return ss
folium.GeoJson(
gdf2.__geo_interface__,
style_function=style_fn,
tooltip=folium.features.GeoJsonTooltip(["id", "values", "iso_a3"]),
).add_to(m)
m
使用特征组
m = folium.Map(
location=[
sum(gdf.geometry.total_bounds[[1, 3]]) / 2,
sum(gdf.geometry.total_bounds[[0, 2]]) / 2,
],
zoom_start=5,
control_scale=True,
)
for g, d in gdf2.groupby(level=0):
fg = folium.map.FeatureGroup(name=g)
folium.GeoJson(
d.__geo_interface__,
style_function=style_fn,
tooltip=folium.features.GeoJsonTooltip(["id", "values", "iso_a3"]),
).add_to(fg)
fg.add_to(m)
folium.LayerControl().add_to(m)
m
我有 lat/long 值的多边形与 GeoDataFrame
中的标识符相关联,如下所示。考虑一个有两个标识符 A
和 B
的例子,多边形 A
有三个点, B
有四个点,它们的 lat/long 值如下所示。对应于每个点(lat/long),我也有一个关联的数值,如最后一列所示。
id geometry values
A POLYGON((lat_A_1 long_A_1, lat_A_2 long_A_2, lat_A_3 long_A_3)) 10,12,13
B POLYGON((lat_B_1 long_B_1, lat_B_2 long_B_2, lat_B_3 long_B_3, lat_B_4 long_B_4)) 4,8,16,20
我遍历 GeoDataFrame 并使用此代码在地图上绘制这些多边形
geo_j = folium.GeoJson(data=geo_j,
style_function={
'fillColor': 'blue'
})
有没有一种方法可以根据 GeoDataFrame 中的 values
列使用自定义颜色图填充多边形,例如红色代表 0-5,蓝色代表 6-10,绿色代表 11- 20.这怎么能做到?
- 首先获取一些多边形并定义每个点的值(生成 MWE 示例数据集)
- 这意味着与多边形关联的值与多边形中的点一样多。您请求使用 folium 的解决方案,该解决方案使用自定义颜色图值填充多边形。这意味着您需要一个函数将所有这些值同化为多边形的单个值(一种颜色)。我使用了 mode,最常见的值。这可以是均值、中位数或任何其他函数。
- 解决方案然后变得简单,它是
folium.GeoJson()
使用并适当地构造 style_function - 扩展答案。您可以将多边形拆分为更小的多边形,并将子多边形的颜色与一个点相关联。 folium 生产没有变化(包括iso_a3)只是为了更容易查看
- shapley 提供了两种分割多边形的方法 https://shapely.readthedocs.io/en/stable/manual.html#shapely.ops.triangulate。发现voronoi效果更好
生成 MWE 数据
# some polygons
gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[lambda d: d["iso_a3"].isin(["BEL", "LUX", "NLD", "DEU", "AUT"]), ["geometry"]]
# comma separated values column... between 0 and 20...
gdf["values"] = gdf.geometry.apply(lambda p: ",".join([str(int(sum(xy)) % 20) for xy in p.exterior.coords]))
# id column
gdf["id"] = list("ABCDEFGHIJ")[0 : len(gdf)]
gdf = gdf.set_index("id", drop=False)
数据
geometry values id
id
A POLYGON ((16.97967 48.12350, 16.9037... 5,4,4,4,3,2,1,1,0,19,19,18,17,17,16,... A
B POLYGON ((14.11969 53.75703, 14.3533... 7,7,7,7,6,6,6,5,5,4,4,3,2,2,2,2,2,1,... B
C POLYGON ((6.04307 50.12805, 6.24275 ... 16,16,15,15,15,15,16 C
D POLYGON ((6.15666 50.80372, 6.04307 ... 16,16,15,15,14,14,13,13,13,13,14,14,... D
E POLYGON ((6.90514 53.48216, 7.09205 ... 0,0,19,18,17,16,16,16,15,14,14,15,17... E
解决方案
import statistics as st
import branca.colormap
import geopandas as gpd
import folium
m = folium.Map(
location=[
sum(gdf.geometry.total_bounds[[1, 3]]) / 2,
sum(gdf.geometry.total_bounds[[0, 2]]) / 2,
],
zoom_start=5,
control_scale=True,
)
# style the polygons based on "values" property
def style_fn(feature):
cm = branca.colormap.LinearColormap(["mistyrose", "tomato", "red"], vmin=0, vmax=20)
most_common = st.mode([int(v) for v in feature["properties"]["values"].split(",")])
ss = {
"fillColor": cm(most_common),
"fillOpacity": 0.8,
"weight": 0.8,
"color": cm(most_common),
}
return ss
folium.GeoJson(
gdf.__geo_interface__,
style_function=style_fn,
tooltip=folium.features.GeoJsonTooltip(["id", "values"]),
).add_to(m)
m
将多边形拆分成多个部分
import statistics as st
import branca.colormap
import geopandas as gpd
import folium
import shapely.geometry
import shapely.ops
import pandas as pd
# some polygons
# fmt: off
gdf = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[lambda d: d["iso_a3"].isin(["BEL", "LUX", "NLD", "DEU", "AUT","POL"]), ["geometry", "iso_a3"]]
# comma separated values column... between 0 and 20...
gdf["values"] = gdf.geometry.apply(lambda p: ",".join([str(int(sum(xy)) % 20) for xy in p.exterior.coords]))
# id column
gdf["id"] = list("ABCDEFGHIJ")[0 : len(gdf)]
gdf = gdf.set_index("id", drop=False)
# fmt: on
def sub_polygons(r, method="voronoi"):
g = r["geometry"]
# split into sub-polygons
if method == "voronoi":
geoms = shapely.ops.voronoi_diagram(g).geoms
elif method == "triangulate":
geoms = [
p
for p in shapely.ops.triangulate(g)
if isinstance(p.intersection(g), shapely.geometry.Polygon)
]
else:
raise "invalid polygon ops method"
# clip sub-geometries
geoms = [p.intersection(g) for p in geoms]
vs = r["values"].split(",")
vr = []
# order or sub-polygons and points are differenct. use value from point
# in sub-polygon
for vg in geoms:
for i, xy in enumerate(g.exterior.coords):
if not shapely.geometry.Point(xy).intersection(vg).is_empty:
break
vr.append(vs[i])
return [{**r.to_dict(), **{"geometry": g, "values": v}} for g, v in zip(geoms, vr)]
gdf2 = gpd.GeoDataFrame(
gdf.apply(sub_polygons, axis=1, method="voronoi").explode().apply(pd.Series)
)
m = folium.Map(
location=[
sum(gdf.geometry.total_bounds[[1, 3]]) / 2,
sum(gdf.geometry.total_bounds[[0, 2]]) / 2,
],
zoom_start=5,
control_scale=True,
)
# style the polygons based on "values" property
def style_fn(feature):
cm = branca.colormap.LinearColormap(["mistyrose", "tomato", "red"], vmin=0, vmax=20)
most_common = st.mode([int(v) for v in feature["properties"]["values"].split(",")])
ss = {
"fillColor": cm(most_common),
"fillOpacity": 0.8,
"weight": 0.8,
"color": cm(most_common),
}
return ss
folium.GeoJson(
gdf2.__geo_interface__,
style_function=style_fn,
tooltip=folium.features.GeoJsonTooltip(["id", "values", "iso_a3"]),
).add_to(m)
m
使用特征组
m = folium.Map(
location=[
sum(gdf.geometry.total_bounds[[1, 3]]) / 2,
sum(gdf.geometry.total_bounds[[0, 2]]) / 2,
],
zoom_start=5,
control_scale=True,
)
for g, d in gdf2.groupby(level=0):
fg = folium.map.FeatureGroup(name=g)
folium.GeoJson(
d.__geo_interface__,
style_function=style_fn,
tooltip=folium.features.GeoJsonTooltip(["id", "values", "iso_a3"]),
).add_to(fg)
fg.add_to(m)
folium.LayerControl().add_to(m)
m