3

Shapely 和 Jupyter/iPython 之间的互操作性很好。我可以做一些很酷的事情,比如创建一堆几何形状并在笔记本中查看它们:

some_nodes = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]
some_boxes = []
some_boxes.append([some_nodes[0], some_nodes[3], some_nodes[4], some_nodes[1]])
some_boxes.append([some_nodes[1], some_nodes[4], some_nodes[5], some_nodes[2]])

from shapely.geometry import MultiPolygon, Polygon
MultiPolygon([Polygon(box) for box in some_boxes])

...Jupyter 会告诉我这个:

Jupyter_Shapely_output

现在这很酷!它对我来说特别有用,可以快速查看和编辑,例如构成二维有限元网格的多边形。

遗憾的是,生成的图像只是静态 SVG 图形;没有内置交互。能够在 iPython 中使用相同的图形界面选择图像中这些对象的子集会很有帮助。

更具体地说,我希望能够创建一个列表并将一些显示的多边形添加到其中,例如,单击/选择它们,或在它们周围拖动套索/框,也可能在单击时将它们删除第二次。

我已经研究过尝试使用 matplotlib 或 javascript 来做到这一点,尽管我已经取得了一些初步的成功,但在我目前的知识/技能水平上,这种项目可能超出了我的深度。

由于 Jupyter 是一个有点庞大的工具,有很多我可能不知道的功能,我想知道是否已经有现有的解决方案可以在 Jupyter 笔记本的上下文中进行此类交互?


更新#1:看起来我将不得不自己创造一些东西。令人高兴的是,本教程将使这变得更容易。

更新#2:Bokeh 似乎是一个更适合此目的的库。我相信我将放弃创建自定义 Jupyter 小部件的想法,转而使用 Bokeh 小部件和交互来创建应用程序。这样的应用程序可以在 Jupyter notebook 中使用,也可以在其他地方使用。

更新#3:无论如何,我最终还是使用了 jupyter 小部件系统。添加了我自己的答案,显示了概念证明。

4

3 回答 3

2

BokehPlotly是两个支持空间数据的交互式 python 可视化库。您可以查看一些示例(12),看看这是否是您要查找的内容。这个存储库包含一些非常酷的 2D 和 3D 可视化示例,您可以直接在 jupyter notebook 中运行这些示例。您还可以使用 GeoPandas 和 Folium 创建完全交互式的地图(这里有一个很棒的教程)。

于 2017-08-15T16:20:36.027 回答
1

使用 vanilla javascript API 和自定义 IPywidgets 系统解决了这个问题。如果您复制并粘贴此代码,请注意单元格的显示顺序不正确。代码可在此处获得

用法

(细胞#3)

import shapely.geometry as geo

some_nodes = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]
some_boxes = []
some_boxes.append([some_nodes[0], some_nodes[3], some_nodes[4], some_nodes[1]])
some_boxes.append([some_nodes[1], some_nodes[4], some_nodes[5], some_nodes[2]])

m_polygon = geo.MultiPolygon(geo.Polygon(box) for box in some_boxes)
poly_selector = PolygonSelector(m_polygon._repr_svg_())  # PolygonSelector defined below
poly_selector  # display the selector below cell, use the tool

工具看起来像这样:

在此处输入图像描述

使用该工具后,您可以通过复制选择器工具实例的属性来获取当前选中的多边形索引groups_dict,即“live”:

(单元#4)

polygon_indexes = poly_selector.groups_dict.copy()
polygon_indexes

代码

工作仍在进行中,但下面是我最终所做的说明。这里也是nbviewer 上笔记本的链接(该工具在此处不可见)。

我把它放在这里部分是为了我自己的参考,但它是其他人可以学习(和改进)的概念证明。有些事情并没有按我想要的方式工作——例如在选择对象时更改对象的颜色。但是主要功能,选择和保存点击的多边形,是有效的。

下面是逐个单元格的代码,因为我在上面的链接版本中有它。

Python代码

(单元#1)

import ipywidgets.widgets as widgets
from traitlets import Unicode, Dict, List
from random import randint

class PolygonSelector(widgets.DOMWidget):
    _view_name = Unicode('PolygonSelectorView').tag(sync=True)
    _view_module = Unicode('polygonselector').tag(sync=True)
    groups_dict = Dict().tag(sync=True)
    current_list = List().tag(sync=True)
    content = Unicode().tag(sync=True)

    html_template = '''
    <style>
    # polygonGeometry path{{
        fill: 'pink';
    }}
    # polygonGeometry .selectedPolygon {{
        fill: {fill_selected!r};
    }}
    # polygonGeometry path:hover {{
        fill: {fill_hovered!r};
    }}
    {selection_styles}
    </style>
    <button id = "clearBtn"> Clear </button>
    <input placeholder = "Name this collection" id = "name" />
    <button id = "saveBtn"> Save </button>
    <div id = "polygonGeometry">{svg}</div>
    '''

    # provide some default colors; can override if desired
    fill_selected = "plum"
    fill_hovered = "lavender"
    group_colors = ["#{:06X}".format(randint(0,0xFFFFFF)) for _ in range(100)]

    def __init__(self, svg):
        super().__init__()
        self.update_content(svg)

    def update_content(self, svg):
        self.content = self.html_template.format(
            fill_selected = self.fill_selected,
            fill_hovered = self.fill_hovered,
            selection_styles = self.selection_styles,
            svg = svg
        )

    @property
    def selection_styles(self):
        return "".join(f'''
        # polygonGeometry .selection_{group_idx} {{
            fill: {self.group_colors[group_idx]!r};
        }}
        ''' for group_idx in range(len(self.groups_dict)))

Javascript代码

(单元#2)

%%javascript

require.undef('polygonselector');

define('polygonselector', ["@jupyter-widgets/base"], function(widgets) {

    var PolygonSelectorView = widgets.DOMWidgetView.extend({

        initialized: 0,

        init_render: function(){

        },


        // Add item to selection list
        add: function(id) {
          this.current_list.push(id);
          console.log('pushed #', id);
        },

        // Remove item from selection list
        remove: function(id) {
          this.current_list = this.current_list.filter(function(_id) {
            return _id !== id;
          })
          console.log('filtered #', id);
        },

        // Remove all items, closure
        clear: function(thisView) {
                return function() {
                    // `this` is the button element
                    console.log('clear() clicked');
                    thisView.el.querySelector('#name').value = '';
                    thisView.current_list.length = 0;
                    Array.from(thisView.el.querySelectorAll('.selectedPolygon')).forEach(function(path) {
                        console.log("path classList is: ", path.classList)
                        path.classList.remove('selectedPolygon');
                    })
                    console.log('Data cleared');
                    console.log(thisView.current_list)
                };
        },

        // Add current selection to groups_dict, closure
        save: function(thisView) {
                return function() {
                    // `this` is the button element
                    console.log('save() clicked');
                    const newName = thisView.el.querySelector('#name').value;
                    console.log('Current name: ', newName)
                    if (!newName || thisView.current_list.length < 1) {
                        console.log("Can't save, newName: ", newName, " list length: ", thisView.current_list.length)
                        alert('A new selection must have a name and selected polygons');
                    }
                    else {
                        console.log('Attempting to save....')
                        thisView.groups_dict[newName] = thisView.current_list.slice(0)
                        console.log('You saved some data');
                        console.log("Selection Name: ", newName);
                        console.log(thisView.groups_dict[newName]);
                        thisView.model.trigger('change:groups_dict');
                    }
                }
        },

        render: function() {
            PolygonSelectorView.__super__.render.apply(this, arguments);
            this.groups_dict = this.model.get('groups_dict')
            this.current_list = this.model.get('current_list')

            this.content_changed();
            this.el.innerHTML = `${this.model.get('content')}`;

            this.model.on('change:content', this.content_changed, this);
            this.model.on('change:current_list', this.content_changed, this);
            this.model.on('change:groups_dict', this.content_changed, this);

            // Each path element is a polygon
            const polygons = this.el.querySelectorAll('#polygonGeometry path');

            // Add click event to polygons
            console.log('iterating through polygons');
            var thisView = this
            let arr = Array.from(polygons)
            console.log('created array:', arr)
            arr.forEach(function(path, i) {
              console.log("Array item #", i)
              path.addEventListener('click', function() {
                console.log('path object clicked')
                if (thisView.current_list.includes(i)) {
                  path.classList.remove('selectedPolygon')
                  thisView.remove(i);
                  console.log('path #', i, ' removed');
                } else {
                  path.classList.add('selectedPolygon')
                  thisView.add(i);
                  console.log('path #', i, ' added');
                }
                thisView.content_changed();
              });
              console.log('path #', i, ' click set');
            });

            // Attach functions to buttons
            this.el.querySelector('#clearBtn').addEventListener('click', this.clear(this));
            console.log('clearBtn action set to current view context');
            this.el.querySelector('#saveBtn').addEventListener('click', this.save(this));
            console.log('saveBtn action set to current view context');

            console.log('render exit')

        },

        content_changed: function() {
            console.log('content changed');
            this.model.save();
            console.log("Current list: ", this.current_list);
            console.log("Groups dict: ", this.groups_dict);
        },
    });

    return {
        PolygonSelectorView : PolygonSelectorView
    };
});
于 2019-05-13T19:01:16.753 回答
0

另请参阅 jp_doodle 套索工具。

这是独立的Javascript:

https://aaronwatters.github.io/jp_doodle/040_lasso.html

以下是您在笔记本中使用它的方式:

https://github.com/AaronWatters/jp_doodle/blob/a809653b5bca98de70dc9524e703d95dc7c4067b/notebooks/Feature%20demonstrations/Lasso.ipynb

希望你喜欢!

于 2019-05-21T17:13:48.303 回答