获取GeoJSONDataSource中所选项的属性

Rut*_*ies 6 python bokeh

我想将包含补丁(来自a GeoJSONDataSource)的图表与折线图链接,但我无法获取所选补丁的属性.

它基本上是一个显示多边形的图,当选择多边形时,我想用该多边形的数据时间序列更新折线图.折线图由法线驱动ColumnDataSource.

我可以通过添加一个回调结合得到所选补丁的索引geo_source.selected['1d']['indices'].但是,我如何获得与该索引相对应的数据/属性?我需要的属性,我可以再使用更新线图一个"关键".

GeoJSONDataSource没有data属性的,我可以查找数据本身.Bokeh可以使用像着色/工具提示等东西的属性,所以我认为必须有一种方法来解决这些问题GeoJSONDataSource,我很遗憾地发现它.

编辑:

这是一个工作的玩具示例,展示了迄今为止我所拥有的.

import pandas as pd
import numpy as np

from bokeh import events
from bokeh.models import (Select, Column, Row, ColumnDataSource, HoverTool, 
                          Range1d, LinearAxis, GeoJSONDataSource)
from bokeh.plotting import figure
from bokeh.io import curdoc

import os
import datetime
from collections import OrderedDict

def make_plot(src):
    # function to create the line chart

    p = figure(width=500, height=200, x_axis_type='datetime', title='Some parameter',
               tools=['xwheel_zoom', 'xpan'], logo=None, toolbar_location='below', toolbar_sticky=False)

    p.circle('index', 'var1', color='black', fill_alpha=0.2, size=10, source=src)

    return p

def make_geo_plot(src):
    # function to create the spatial plot with polygons

    p = figure(width=300, height=300, title="Select area", tools=['tap', 'pan', 'box_zoom', 'wheel_zoom','reset'], logo=None)

    p.patches('xs', 'ys', fill_alpha=0.2, fill_color='black',
              line_color='black', line_width=0.5, source=src)

    p.on_event(events.SelectionGeometry, update_plot_from_geo)

    return p

def update_plot_from_geo(event):
    # update the line chart based on the selected polygon

    selected = geo_source.selected['1d']['indices']

    if (len(selected) > 0):
        first = selected[0]
        print(geo_source.selected['1d']['indices'])


def update_plot(attrname, old, new):
    # Callback for the dropdown menu which updates the line chart
    new_src = get_source(df, area_select.value)    
    src.data.update(new_src.data)

def get_source(df, fieldid):
    # function to get a subset of the multi-hierarchical DataFrame

    # slice 'out' the selected area
    dfsub = df.xs(fieldid, axis=1, level=0)
    src = ColumnDataSource(dfsub)

    return src

# example timeseries
n_points = 100
df = pd.DataFrame({('area_a','var1'): np.sin(np.linspace(0,5,n_points)) + np.random.rand(100)*0.1,
                   ('area_b','var1'): np.sin(np.linspace(0,2,n_points)) + np.random.rand(100)*0.1,
                   ('area_c','var1'): np.sin(np.linspace(0,3,n_points)) + np.random.rand(100)*0.1,
                   ('area_d','var1'): np.sin(np.linspace(0,4,n_points)) + np.random.rand(100)*0.1},
                  index=pd.DatetimeIndex(start='2017-01-01', freq='D', periods=100))

# example polygons
geojson = """{
"type":"FeatureCollection",
"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}},
"features":[
{"type":"Feature","properties":{"key":"area_a"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-108.8,42.7],[-104.5,42.0],[-108.3,39.3],[-108.8,42.7]]]]}},
{"type":"Feature","properties":{"key":"area_b"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-106.3,44.0],[-106.2,42.6],[-103.3,42.6],[-103.4,44.0],[-106.3,44.0]]]]}},
{"type":"Feature","properties":{"key":"area_d"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-104.3,41.0],[-101.5,41.0],[-102.9,37.8],[-104.3,41.0]]]]}},
{"type":"Feature","properties":{"key":"area_c"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-105.8,40.3],[-108.3,37.7],[-104.0,37.4],[-105.8,40.3]]]]}}
]
}"""

geo_source = GeoJSONDataSource(geojson=geojson)

# populate a drop down menu with the area's 
area_ids = sorted(df.columns.get_level_values(0).unique().values.tolist())
area_ids = [str(x) for x in area_ids]
area_select = Select(value=area_ids[0], title='Select area', options=area_ids)
area_select.on_change('value', update_plot)

src = get_source(df, area_select.value)

p = make_plot(src)
pgeo = make_geo_plot(geo_source)

# add to document
curdoc().add_root(Row(Column(area_select, p), pgeo))
Run Code Online (Sandbox Code Playgroud)

将代码保存在.py文件中并加载bokeh serve example.py --show

在此输入图像描述

Seb*_*Seb 3

您应该为 GeoJSONDataSource 编写自定义扩展

这是 GeoJSONDataSource 的咖啡脚本https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/models/sources/geojson_data_source.coffee

我不太擅长自定义扩展。所以我完全复制了 GeoJSONDataSource 并将其命名为 CustomGeo。我刚刚将“数据”从@internal 移至@define。然后宾果,你得到了一个带有“data”属性的 GeoJSONDataSource。

在下面的示例中,我使用“key”列表进行回调,但由于您现在拥有这样的数据,如果您担心洗牌,您可以编写一些内容来仔细检查它是否对应于适当的多边形

import pandas as pd
import numpy as np

from bokeh.core.properties import Instance, Dict, JSON, Any

from bokeh import events
from bokeh.models import (Select, Column, Row, ColumnDataSource, HoverTool, 
                          Range1d, LinearAxis, GeoJSONDataSource, ColumnarDataSource)
from bokeh.plotting import figure
from bokeh.io import curdoc

import os
import datetime
from collections import OrderedDict

def make_plot(src):
    # function to create the line chart

    p = figure(width=500, height=200, x_axis_type='datetime', title='Some parameter',
               tools=['xwheel_zoom', 'xpan'], logo=None, toolbar_location='below', toolbar_sticky=False)

    p.circle('index', 'var1', color='black', fill_alpha=0.2, size=10, source=src)

    return p

def make_geo_plot(src):
    # function to create the spatial plot with polygons

    p = figure(width=300, height=300, title="Select area", tools=['tap', 'pan', 'box_zoom', 'wheel_zoom','reset'], logo=None)

    a=p.patches('xs', 'ys', fill_alpha=0.2, fill_color='black',
              line_color='black', line_width=0.5, source=src,name='poly')

    p.on_event(events.SelectionGeometry, update_plot_from_geo)

    return p

def update_plot_from_geo(event):
    # update the line chart based on the selected polygon

    try:
      selected = geo_source.selected['1d']['indices'][0]
    except IndexError:
      return

    print geo_source.data
    print geo_source.data['key'][selected]

    new_src = get_source(df,geo_source.data['key'][selected])
    src.data.update(new_src.data)

def update_plot(attrname, old, new):
    # Callback for the dropdown menu which updates the line chart
    print area_select.value
    new_src = get_source(df, area_select.value)    
    src.data.update(new_src.data)

def get_source(df, fieldid):
    # function to get a subset of the multi-hierarchical DataFrame

    # slice 'out' the selected area
    dfsub = df.xs(fieldid, axis=1, level=0)
    src = ColumnDataSource(dfsub)

    return src

# example timeseries
n_points = 100
df = pd.DataFrame({('area_a','var1'): np.sin(np.linspace(0,5,n_points)) + np.random.rand(100)*0.1,
                   ('area_b','var1'): np.sin(np.linspace(0,2,n_points)) + np.random.rand(100)*0.1,
                   ('area_c','var1'): np.sin(np.linspace(0,3,n_points)) + np.random.rand(100)*0.1,
                   ('area_d','var1'): np.sin(np.linspace(0,4,n_points)) + np.random.rand(100)*0.1},
                  index=pd.DatetimeIndex(start='2017-01-01', freq='D', periods=100))

# example polygons
geojson = """{
"type":"FeatureCollection",
"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}},
"features":[
{"type":"Feature","properties":{"key":"area_a"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-108.8,42.7],[-104.5,42.0],[-108.3,39.3],[-108.8,42.7]]]]}},
{"type":"Feature","properties":{"key":"area_b"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-106.3,44.0],[-106.2,42.6],[-103.3,42.6],[-103.4,44.0],[-106.3,44.0]]]]}},
{"type":"Feature","properties":{"key":"area_d"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-104.3,41.0],[-101.5,41.0],[-102.9,37.8],[-104.3,41.0]]]]}},
{"type":"Feature","properties":{"key":"area_c"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-105.8,40.3],[-108.3,37.7],[-104.0,37.4],[-105.8,40.3]]]]}}
]
}"""

implementation = """
import {ColumnarDataSource} from "models/sources/columnar_data_source"
import {logger} from "core/logging"
import * as p from "core/properties"

export class CustomGeo extends ColumnarDataSource
  type: 'CustomGeo'

  @define {
    geojson: [ p.Any     ] # TODO (bev)
    data:    [ p.Any,   {} ]
  }

  initialize: (options) ->
    super(options)
    @_update_data()
    @connect(@properties.geojson.change, () => @_update_data())

  _update_data: () -> @data = @geojson_to_column_data()

  _get_new_list_array: (length) -> ([] for i in [0...length])

  _get_new_nan_array: (length) -> (NaN for i in [0...length])

  _flatten_function: (accumulator, currentItem) ->
    return accumulator.concat([[NaN, NaN, NaN]]).concat(currentItem)

  _add_properties: (item, data, i, item_count) ->
    for property of item.properties
      if !data.hasOwnProperty(property)
        data[property] = @_get_new_nan_array(item_count)
      data[property][i] = item.properties[property]

  _add_geometry: (geometry, data, i) ->

    switch geometry.type

      when "Point"
        coords = geometry.coordinates
        data.x[i] = coords[0]
        data.y[i] = coords[1]
        data.z[i] = coords[2] ? NaN

      when "LineString"
        coord_list = geometry.coordinates
        for coords, j in coord_list
          data.xs[i][j] = coords[0]
          data.ys[i][j] = coords[1]
          data.zs[i][j] = coords[2] ? NaN

      when "Polygon"
        if geometry.coordinates.length > 1
          logger.warn('Bokeh does not support Polygons with holes in, only exterior ring used.')
        exterior_ring = geometry.coordinates[0]
        for coords, j in exterior_ring
          data.xs[i][j] = coords[0]
          data.ys[i][j] = coords[1]
          data.zs[i][j] = coords[2] ? NaN

      when "MultiPoint"
        logger.warn('MultiPoint not supported in Bokeh')

      when "MultiLineString"
        flattened_coord_list = geometry.coordinates.reduce(@_flatten_function)
        for coords, j in flattened_coord_list
          data.xs[i][j] = coords[0]
          data.ys[i][j] = coords[1]
          data.zs[i][j] = coords[2] ? NaN

      when "MultiPolygon"
        exterior_rings = []
        for polygon in geometry.coordinates
          if polygon.length > 1
            logger.warn('Bokeh does not support Polygons with holes in, only exterior ring used.')
          exterior_rings.push(polygon[0])

        flattened_coord_list = exterior_rings.reduce(@_flatten_function)
        for coords, j in flattened_coord_list
          data.xs[i][j] = coords[0]
          data.ys[i][j] = coords[1]
          data.zs[i][j] = coords[2] ? NaN

      else
        throw new Error('Invalid type ' + geometry.type)

  _get_items_length: (items) ->
    count = 0
    for item, i in items
      geometry = if item.type == 'Feature' then item.geometry else item
      if geometry.type == 'GeometryCollection'
        for g, j in geometry.geometries
          count += 1
      else
        count += 1
    return count

  geojson_to_column_data: () ->
    geojson = JSON.parse(@geojson)

    if geojson.type not in ['GeometryCollection', 'FeatureCollection']
      throw new Error('Bokeh only supports type GeometryCollection and FeatureCollection at top level')

    if geojson.type == 'GeometryCollection'
      if not geojson.geometries?
        throw new Error('No geometries found in GeometryCollection')
      if geojson.geometries.length == 0
        throw new Error('geojson.geometries must have one or more items')
      items = geojson.geometries

    if geojson.type == 'FeatureCollection'
      if not geojson.features?
        throw new Error('No features found in FeaturesCollection')
      if geojson.features.length == 0
        throw new Error('geojson.features must have one or more items')
      items = geojson.features

    item_count = @_get_items_length(items)

    data = {
      'x': @_get_new_nan_array(item_count),
      'y': @_get_new_nan_array(item_count),
      'z': @_get_new_nan_array(item_count),
      'xs': @_get_new_list_array(item_count),
      'ys': @_get_new_list_array(item_count),
      'zs': @_get_new_list_array(item_count)
    }

    arr_index = 0
    for item, i in items
      geometry = if item.type == 'Feature' then item.geometry else item

      if geometry.type == 'GeometryCollection'
        for g, j in geometry.geometries
          @_add_geometry(g, data, arr_index)
          if item.type == 'Feature'
            @_add_properties(item, data, arr_index, item_count)
          arr_index += 1
      else
        # Now populate based on Geometry type
        @_add_geometry(geometry, data, arr_index)
        if item.type == 'Feature'
          @_add_properties(item, data, arr_index, item_count)

        arr_index += 1

    return data

"""

class CustomGeo(ColumnarDataSource):
  __implementation__ = implementation

  geojson = JSON(help="""
  GeoJSON that contains features for plotting. Currently GeoJSONDataSource can
  only process a FeatureCollection or GeometryCollection.
  """)

  data = Dict(Any,Any,default={},help="wooo")

geo_source = CustomGeo(geojson=geojson)

# populate a drop down menu with the area's 
area_ids = sorted(df.columns.get_level_values(0).unique().values.tolist())
area_ids = [str(x) for x in area_ids]
area_select = Select(value=area_ids[0], title='Select area', options=area_ids)
area_select.on_change('value', update_plot)

src = get_source(df, area_select.value)

p = make_plot(src)
pgeo = make_geo_plot(geo_source)

# add to document
curdoc().add_root(Row(Column(area_select, p), pgeo))
Run Code Online (Sandbox Code Playgroud)