如何使用Bokeh颜色图处理shapefile中的重叠

时间:2019-02-03 16:18:58

标签: bokeh shapefile

我想用Bokeh生成一个chloropleth贴图。 我已经准备好法国部门及其人口的数据集。我还下载了法国部门的shapefile。

第一次试用后,我发现我的货盘被错误地应用于部门(有些比其他人少的地方更暗)。

我发现这很奇怪,并为所有部门设置了相同的人口以进行检查,我发现并非所有部门的颜色都相同!在我的代码下面找到

data = gdf.join(df)
# apply same population per department
data.population = 5678

geo_src = bm.GeoJSONDataSource(geojson=data.to_json())

# set up a log colormap
cmap = bm.LogColorMapper(
    palette=bokeh.palettes.Blues9[::-1], # reverse the palette
)


# define web tools
TOOLS = "pan,wheel_zoom,box_zoom,reset,hover,save"

# set up bokeh figure
p = figure(
    title="Population", 
    tools=TOOLS,
    toolbar_location="below",
    x_axis_location=None, 
    y_axis_location=None, 
    width=800, 
    height=800
)

# remove the grid
p.grid.grid_line_color = None

# core part !
p.patches(
    'xs', 'ys', 
    fill_alpha=0.7, 
    fill_color={'field': 'population', 'transform': cmap},
    line_color='black', 
    line_width=0.5, 
    source=geo_src
)

# show plot
show(p)

查看结果,

enter image description here

我的猜测是,这些较暗的部门具有重叠的形状,而Bokeh施加了两倍的人口,使其变得更暗...

我试图找到一种方法来删除shapefile中的重叠部分(到目前为止还没有成功),但我想知道是否有一种方法可以配置Bokeh以使其不对重叠部分进行汇总?

2 个答案:

答案 0 :(得分:0)

好的,我终于设法了解我做错了什么。

这与重叠或类似内容无关(我使用QGIS确认没有重叠)。 相反,我注意到比其他部门更暗的部门实际上是被分成几部分的部门!

这是东西;在应用Bokeh补丁时,我使用的fill_alpha小于1。我只需要将此参数设置为1,以便无论部门构成什么,颜色都将相同!

p.patches(
    'xs', 'ys', 
    fill_alpha=1, 
    fill_color={'field': 'population', 'transform': cmap},
    line_color='black', 
    line_width=0.5, 
    source=geo_src
)

答案 1 :(得分:0)

我遇到了同样的问题,并且有解决方案。

问题是当补丁使用p.patches边界数据列表中的nan分隔符描述多个多边形时,在使用x的Bokeh中存在一个错误。我将不得不找出一种方法来稍后报告该错误。

解决方案是改用`p.multi_polygons'。每个条目中可以包含多个多边形,而不会出现错误。 https://docs.bokeh.org/en/latest/docs/reference/models/glyphs/multi_polygons.html

我与geojson的合作不多,但与GeoPandas的合作却很多。以下是使用来自https://www.naturalearthdata.com/downloads/的数据的工作示例:

import geopandas as gpd
from shapely.geometry.polygon import Polygon
from shapely.geometry.multipolygon import MultiPolygon
from bokeh.io import show, output_file
from bokeh.models import  MultiPolygons, ColorMapper, LinearColorMapper
from bokeh.palettes import Inferno256 as palette
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.layouts import row, column

# Define the function I gave earlier
def get_MultiPoly(mpoly,coord_type='x'):
    """Returns the coordinates ('x' or 'y') for the exterior and interior of MultiPolygon digestible by multi_polygons in Bokeh"""
    
    if coord_type == 'x':
        i=0
    elif coord_type == 'y':
        i=1
    
    # Get the x or y coordinates
    c = [] 
    if isinstance(mpoly,Polygon):
        mpoly = [mpoly]
    for poly in mpoly: # the polygon objects return arrays, it's important they be lists or Bokeh fails
        exterior_coords = poly.exterior.coords.xy[i].tolist();
        
        interior_coords = []
        for interior in poly.interiors:
            if isinstance(interior.coords.xy[i],list):
                interior_coords += [interior.coords.xy[i]];
            else:
                interior_coords += [interior.coords.xy[i].tolist()];
        c.append([exterior_coords, *interior_coords])
    return c

# Define input/output files and get data, index by name
output_file("tmp.html")

map_world_gpd = gpd.read_file('map_data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp')
map_world_gpd.set_index('NAME_EN',inplace=True)

# Parse all countries into a ColumnDataSource
N=len(map_world_gpd) 
source3 = ColumnDataSource({ 
    'x': [get_MultiPoly(map_world_gpd.iloc[i]['geometry'],'x') for i in range(0,N)],
    'y': [get_MultiPoly(map_world_gpd.iloc[i]['geometry'],'y') for i in range(0,N)],
    'n': [i for i in range(0,N)],
    'name':[map_world_gpd.iloc[i]['NAME'] for i in range(0,N)],
    })

# Plot all the countries
p = figure(title="map_exploration",match_aspect=True,aspect_ratio=2,
            tooltips=[
                ("Name",'@name')
                ],
            )

p.multi_polygons(xs='x',ys='y', source=source3,
          fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=len(source3.data['x']))},
          fill_alpha=0.5, line_color="black", line_width=0.5)

show(row(p,sizing_mode = 'scale_width'))

如果您对该错误感兴趣,那么下面是显示该错误的示例:

map_states_gpd = gpd.read_file('map_data/cb_2019_us_state_20m.shp')
map_states_gpd.set_index('NAME',inplace=True)

map_world_gpd = gpd.read_file('map_data/ne_10m_admin_0_countries/ne_10m_admin_0_countries.shp')
map_world_gpd.set_index('NAME_EN',inplace=True)
map_world_gpd = map_world_gpd.to_crs(epsg=3395)
gsource = map_world_gpd.loc['Spain']['geometry']



output_file("tmp.html")

def get_PolyCoords(poly,coord_type='x'):
    """Returns the coordinates ('x' or 'y') of """
    
    if coord_type == 'x':
        # Get the x coordinates of the exterior
        return list( poly.exterior.coords.xy[0] )
    elif coord_type == 'y':
        # Get the y coordinates of the exterior
        return list( poly.exterior.coords.xy[1] )
    
def get_MultiPoly(mpoly,coord_type='x'):
    """Returns the coordinates ('x' or 'y') for the exterior and interior of MultiPolygon digestible by multi_polygons in Bokeh"""
    
    if coord_type == 'x':
        i=0
    elif coord_type == 'y':
        i=1
    
    # Get the x or y coordinates
    c = [] 
    if isinstance(mpoly,Polygon):
        mpoly = [mpoly]
    for poly in mpoly: # the polygon objects return arrays, it's important they be lists or Bokeh fails
        exterior_coords = poly.exterior.coords.xy[i].tolist();
        
        interior_coords = []
        for interior in poly.interiors:
            if isinstance(interior.coords.xy[i],list):
                interior_coords += [interior.coords.xy[i]];
            else:
                interior_coords += [interior.coords.xy[i].tolist()];
        c.append([exterior_coords, *interior_coords])
    return c
    
    
def get_MultiPolyCords(mpoly,coord_type='x'):
    """Returns the coordinates ('x' or 'y') of edges of a MultiPolygon exterior"""
    if isinstance(mpoly,Polygon):
        mpoly = [mpoly]
    if coord_type == 'x':
        # Get the x coordinates of the exterior
        x = [] 
        for poly in mpoly:
            x.append(get_PolyCoords(poly,coord_type))
        return x
    elif coord_type == 'y':
        # Get the y coordinates of the exterior
        y = [] 
        for poly in mpoly:
            y.append(get_PolyCoords(poly,coord_type))
        return y
    
def concat_MultiPolyCords(mpoly,coord_type='x'):
    """Returns the coordinates ('x' or 'y') of edges of a MultiPolygon exterior"""
    if isinstance(mpoly,Polygon):
        mpoly = [mpoly]
    if coord_type == 'x':
        # Get the x coordinates of the exterior
        x = [] 
        for poly in mpoly:
            x.append(float('NaN'))
            x = x+ get_PolyCoords(poly,coord_type)
        return x
    elif coord_type == 'y':
        # Get the y coordinates of the exterior
        y = [] 
        for poly in mpoly:
            y.append(float('NaN'))
            y = y+ get_PolyCoords(poly,coord_type)
        return y
    
   
def plen(mp):
    if isinstance(mp,MultiPolygon):
        return len(mp)
    else:
        return 1



def plen(mpoly):
    if isinstance(mpoly,Polygon):
        return 1
    else:
        return len(mpoly)

# Plot the patches using the same color 
source1 = ColumnDataSource({
    'x': get_MultiPolyCords(gsource,'x'),
    'y': get_MultiPolyCords(gsource,'y'),
    'n': [3]*plen(gsource),
    })

# Plot the patches by combining them into one patch, separate sub patches with nan, all same color
# Note that the alpha outcome is faulty, it seems to redraw all the patches for each one
ys = [concat_MultiPolyCords(gsource,'y')]
source2 = ColumnDataSource({
    'x': [concat_MultiPolyCords(gsource,'x')],
    'y': [concat_MultiPolyCords(gsource,'y')],
    'n': [3],
    })

# Initialize our figure
p1 = figure(title="map_exploration",match_aspect=True,aspect_ratio=2)

# Plot grid
p1.patches('x', 'y', source=source1,
          fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=plen(gsource))},
          fill_alpha=0.5, line_color="black", line_width=0.5)
p1.title.text = "Map has "+str(plen(gsource))+" polygons, patched using "+str(plen(source1.data['x']))+" patches"

# Initialize our figure
p2 = figure(title="map_exploration",match_aspect=True,aspect_ratio=2)

# Plot grid
p2.patches('x', 'y', source=source2,
          fill_color={'field': 'n', 'transform': LinearColorMapper(palette=palette,low=0,high=plen(gsource))},
          fill_alpha=0.5, line_color="black", line_width=0.5)
p2.title.text = "Map has "+str(plen(gsource))+" polygons, patched using "+str(plen(source2.data['x']))+" patches using NaN separators"

show(row(p1,p2,sizing_mode = 'scale_width',))

所以不要使用第二个代码,而要使用第一个代码以获得所需的结果。