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会告诉我这个:
现在很酷!它对我来说特别有用,可以快速查看和编辑,例如构成2D有限元网格的多边形。
遗憾的是,产生的图像只是静态的SVG图形;没有内置的交互。能够在iPython中使用相同的图形界面选择图像中这些对象的子集是有帮助的。更具体地说,我希望能够创建一个列表并添加一些显示的多边形,例如,点击/选择它们,或者在它们周围拖动套索/框,也可以删除它们第二次点击时。
我已经考虑过使用matplotlib或javascript尝试这样做,虽然我已经取得了一些初步的成功,但这可能是我目前的知识/技能水平之外的那种项目。
由于Jupyter是一个有点庞大的工具,有很多我可能不知道的功能,我想知道在Jupyter笔记本的上下文中是否存在这种交互的现有解决方案?
更新#1:看起来我将不得不自己创造一些东西。令人高兴的是,this tutorial将使这更容易。
更新#2:看来Bokeh是一个更适合这个目的的图书馆。我相信我将放弃创建自定义Jupyter小部件的想法,并使用Bokeh小部件和交互来创建应用程序。这样的应用程序可以在Jupyter笔记本中使用,也可以在其他地方使用。
更新#3:无论如何我最终都使用了jupyter小部件系统。添加了我自己的答案,显示了概念证明。
答案 0 :(得分:2)
Bokeh和Plotly是两个交互式python可视化库,支持空间数据。您可以查看一些示例(1,2),看看这是否是您要查找的内容。 This repository包含一些非常酷的2D和3D可视化示例,您可以在jupyter笔记本中运行它们。您还可以使用GeoPandas和Folium来创建完全交互式的地图(here是一个很棒的教程)。
答案 1 :(得分:1)
使用原始javascript API和custom IPywidgets system解决了该问题。如果您复制并粘贴此代码,请注意,单元格显示不正确。 Code is available here。
(单元格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
属性(“实时”)来获取当前选定的多边形索引:
(单元格4)
polygon_indexes = poly_selector.groups_dict.copy()
polygon_indexes
工作仍在进行中,但以下是我最终所做的工作的说明。这也是link to the notebook on nbviewer(该工具在此处不可见)。
我将此内容部分地放在这里供我参考,但这是其他人可以从中学习(并加以改进)的概念证明。有些事情并没有按照我想要的方式工作-例如在选择对象时更改对象的颜色。但是,选择和保存单击的多边形的主要功能起作用了。
下面是每个单元格的代码,就像上面链接版本中的代码一样。
(单元格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)))
(单元格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
};
});
答案 2 :(得分:0)
另请参阅jp_doodle套索工具。
这里是独立的Javascript:
https://aaronwatters.github.io/jp_doodle/040_lasso.html
这是在笔记本中使用它的方式:
希望你喜欢!