【问题标题】:Bokeh patches not loading multipolygons with CDS散景补丁不使用 CDS 加载多面体
【发布时间】:2020-02-13 05:56:48
【问题描述】:

我想使用 CDS(ColumnDataSource) 因为我将使用滑块更新地图,我知道我可以使用 GeoJsonDataSource 并且可以使用 geojson 更新它,尽管我会将它嵌入到 webapp尽管我宁愿使用 CDS,但 geojson 会非常重视它,因为我对整个事情有更多的控制权。 还有另一种方法可以在不溶解它的情况下获得“几何”列(涉及大量内存)? 所以当它是一个多多边形时,补丁不会加载它,这只会给我简单的多边形。如果有人可以帮助我。 提前致谢! 数据可在此处获得scroll down and select 2008 Local Electoral Areas

import pandas as pd
import numpy as np
import geopandas as gpd
from bokeh.io import (output_notebook, show, curdoc, output_file)
from bokeh.plotting import figure
from bokeh.models import GeoJSONDataSource, LinearColorMapper, ColorBar, ColumnDataSource
from bokeh.models.glyphs import MultiPolygons
from copy import deepcopy
from bokeh.palettes import Pastel2, viridis, inferno, magma, Paired, Spectral, brewer, Greens, YlGn
import matplotlib.pyplot as plt
output_notebook()

geoIrl = gpd.read_file('Census2011_Local_Electoral_Areas_2008.shp')
irlg = geoIrl[['COUNTY','geometry']]
irl = irlg.dissolve(by='COUNTY', aggfunc='sum')
dfirl = deepcopy(irl)


#extracting the xs and ys
def getPolyCoords(row, geom, coord_type):
    """Returns the coordinates ('x|y') of edges/vertices of a Polygon/others"""

    # Parse the geometries and grab the coordinate
    geometry = row[geom]
    #print(geometry.type)

    if geometry.type=='Polygon':
        if coord_type == 'x':
            # Get the x coordinates of the exterior
            # Interior is more complex: xxx.interiors[0].coords.xy[0]
            return list( geometry.exterior.coords.xy[0] )
        elif coord_type == 'y':
            # Get the y coordinates of the exterior
            return list( geometry.exterior.coords.xy[1] )

    if geometry.type in ['Point', 'LineString']:
        if coord_type == 'x':
            return list( geometry.xy[0] )
        elif coord_type == 'y':
            return list( geometry.xy[1] )

    if geometry.type=='MultiLineString':
        all_xy = []
        for ea in geometry:
            if coord_type == 'x':
                all_xy.append(list( ea.xy[0] ))
            elif coord_type == 'y':
                all_xy.append(list( ea.xy[1] ))
        return all_xy

    if geometry.type=='MultiPolygon':
        all_xy = []
        for ea in geometry:
            if coord_type == 'x':
                all_xy.append(list( ea.exterior.coords.xy[0] ))
            elif coord_type == 'y':
                all_xy.append(list( ea.exterior.coords.xy[1] ))
        return all_xy

    else:
        # Finally, return empty list for unknown geometries
        return []

dfirl['xs'] = dfirl.apply(getPolyCoords, geom='geometry', coord_type='x', axis=1)
dfirl['ys'] = dfirl.apply(getPolyCoords, geom='geometry', coord_type='y', axis=1)

rX = 'CARLOW', 'CAVAN', 'CLARE', 'CORK', 'DONEGAL', 'DUBLIN', 'GALWAY','KERRY', 'KILDARE', 'KILKENNY', 'LAOIS', 'LEITRIM', 'LIMERICK','LONGFORD', 'LOUTH', 'MAYO', 'MEATH', 'MONAGHAN', 'OFFALY', 'ROSCOMMON', 'SLIGO', 'TIPPERARY', 'WATERFORD', 'WESTMEATH', 'WEXFORD', 'WICKLOW'
srcmap = ColumnDataSource(data=dict(x=rX, y02=df_map['2002'], y06=df_map['2006'], y11=df_map['2011'], y16=df_map['2016'], xs=df_map['xs'], ys=df_map['ys']))

pc = figure(title = 'test', tools = '', x_axis_location = None, y_axis_location = None)

pc.patches('xs', 'ys', fill_alpha = 0.7, line_width = 0.5, source = srcmap)
#pc.grid.grid_line_color=None
show(pc)

如果我单独选择一个多面体,我将能够使用补丁来绘制它。

xstest =df_map.loc['DUBLIN']['xs']
ystest = df_map.loc['DUBLIN']['ys']
pc = figure(title = 'test', tools = '', x_axis_location = None, y_axis_location = None)
pc.patches(xs=xstest, ys=ystest, fill_alpha = 0.7, line_width = 0.5)
show(pc)

【问题讨论】:

    标签: python gis bokeh geojson patch


    【解决方案1】:

    我认为这不能完全回答您的问题。但也许它会有所帮助。我编写了以下内容来解构 GeoPandas 数据框并使用 multi_polygons 在 Bokeh 中显示数据。我用来自 GeoPandas 的几何的 MultiPolygon 元素来提供这个函数。

    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
    

    以下是给定来自https://www.naturalearthdata.com/downloads/的数据集的完整示例

    (.html文件会很大,大约20MB,因为它以相对较高的分辨率绘制整个世界,只需将N设置为较小的数字以更快地绘制更少的国家)

    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'))
    

    这是另一个更简单的例子来说明差异:

    from bokeh.io       import show, output_file
    from bokeh.models   import ColumnDataSource, MultiPolygons
    from bokeh.models   import ColorMapper, LinearColorMapper 
    from bokeh.palettes import Turbo256 as palette
    from bokeh.plotting import figure
    from bokeh.layouts  import row, column
    
    output_file("tmp.html")
    
    xs = [[0, 0, 1, 1, 0], [1.5, 1.5, 2.5, 2.5, 1.5], [3, 3, 4, 4, 3]]
    ys = [[0, 1, 1, 0, 0], [0, 1, 1, 0, 0], [0, 1, 1, 0, 0]]
    
    xs_nan = [[*xs[0],float('NaN'),*xs[1],float('NaN'),*xs[2]]]
    ys_nan = [[*ys[0],float('NaN'),*ys[1],float('NaN'),*ys[2]]]
    
    xs_mp =  [[[x] for x in xs]]
    ys_mp =  [[[y] for y in ys]]
    
    # Make data for a patch for each polygon in a Multipolygon, all different colors
    source0 = ColumnDataSource({
        'x': xs,
        'y': ys,
        'color': list(range(0,len(xs))),
        })
    
    # Make data for a patch for each polygon in a Multipolygon, all same color
    source1 = ColumnDataSource({
        'x': xs,
        'y': ys,
        'color':[0]*len(xs),
        })
    
    # Make data for a patch for each Multipolygon, separating polygons with nan 
    source2 = ColumnDataSource({
        'x': xs_nan,
        'y': ys_nan,
        'color': [0],
        })
    # Make data for Multipolygon, using one Multipolygon for all
    source3 = ColumnDataSource({ 
        'x': xs_mp,
        'y': ys_mp,
        'color': [0],
        })
    
    # Initialize our figures
    p0 = figure(match_aspect=True,aspect_ratio=2,x_axis_location=None, y_axis_location=None,)
    p1 = figure(match_aspect=True,aspect_ratio=2,x_axis_location=None, y_axis_location=None,)
    p2 = figure(match_aspect=True,aspect_ratio=2,x_axis_location=None, y_axis_location=None,)
    p3 = figure(match_aspect=True,aspect_ratio=2,x_axis_location=None, y_axis_location=None,)
    
    
    # Plot 
    p0.title.text = "Map has "+str(len(xs))+" polygons, patched using "+str(len(source1.data['x']))+" patches, different colors"
    p0.patches('x', 'y', source=source0,
              fill_color={'field': 'color', 'transform': LinearColorMapper(palette=palette,low=0,high=len(xs))},
              fill_alpha=0.5, line_color="black", line_width=0.5)
    
    p1.title.text = "Map has "+str(len(xs))+" polygons, patched using "+str(len(source1.data['x']))+" patches, same color"
    p1.patches('x', 'y', source=source1,
              fill_color={'field': 'color', 'transform': LinearColorMapper(palette=palette,low=0,high=len(xs))},
              fill_alpha=0.5, line_color="black", line_width=0.5)
    
    p2.title.text = "Map has "+str(len(xs))+" polygons, patched using "+str(len(source2.data['x']))+" patches using NaN separators (too dark, BUG)"
    p2.patches('x', 'y', source=source2,
              fill_color={'field': 'color', 'transform': LinearColorMapper(palette=palette,low=0,high=len(xs))},
              fill_alpha=0.5, line_color="black", line_width=0.5)
    
    p3.title.text = "Map has "+str(len(xs))+" polygons, using "+str(len(source3.data['x']))+" multipolygon, same color"
    p3.multi_polygons(xs='x',ys='y', source=source3,
              fill_color={'field': 'color', 'transform': LinearColorMapper(palette=palette,low=0,high=len(xs))},
              fill_alpha=0.5, line_color="black", line_width=0.5)
    
    show(column(p0,p1,p2,p3,sizing_mode = 'scale_width'))
    

    【讨论】:

    • 嗨,@TheDrDos,欢迎来到社区。我相信经过数小时的研究后,我会找到解决方法,我会花时间尽快完成您的答案。无论如何,非常感谢您的回答和时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-29
    相关资源
    最近更新 更多