Skip to content

Commit

Permalink
Added custom PolyVertexEditTool (#276)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Jul 5, 2019
1 parent 08e4b19 commit d8d675a
Show file tree
Hide file tree
Showing 9 changed files with 657 additions and 18 deletions.
6 changes: 4 additions & 2 deletions .travis.yml
Expand Up @@ -6,6 +6,7 @@ language: generic
env:
global:
- PYENV_VERSION=3.6
- PYTHON_VERSION=3.6
- PKG_TEST_PYTHON="--test-python=py36"
- CHANS_DEV="-c pyviz/label/earthsim -c pyviz/label/dev -c bokeh -c defaults -c erdc -c conda-forge -c aquaveo"
- CHANS_REL="-c pyviz -c bokeh -c defaults -c erdc -c conda-forge -c aquaveo"
Expand All @@ -32,7 +33,7 @@ stages:
_osx_config: &_osx_config
os: osx
osx_image: xcode10.1
env: PYENV_VERSION=3.6.4 CHANS_DEV="-c pyviz/label/earthsim -c pyviz/label/dev -c erdc -c conda-forge -c aquaveo"
env: PYENV_VERSION=3.7.1 CHANS_DEV="-c pyviz/label/earthsim -c pyviz/label/dev -c erdc -c conda-forge -c aquaveo"
before_install:
# set up python
- eval "$(pyenv init -)"
Expand All @@ -45,6 +46,7 @@ _osx_config: &_osx_config
- conda config --set always_yes True
- conda install -c pyviz "pyctdev>=0.5" && doit ecosystem_setup
- conda config --set path_conflict warn
- conda config --set restore_free_channel true

jobs:
include:
Expand All @@ -58,7 +60,7 @@ jobs:
- conda install -c pyviz "pyctdev>=0.5" && doit ecosystem_setup
- conda config --set path_conflict warn
install:
- travis_wait 20 doit env_create $CHANS_DEV --name=earthsim --python=$PYENV_VERSION
- travis_wait 20 doit env_create $CHANS_DEV --name=earthsim --python=$PYTHON_VERSION
- source activate earthsim
- doit develop_install $CHANS_DEV -o tests
- doit env_capture
Expand Down
39 changes: 35 additions & 4 deletions earthsim/annotators.py
Expand Up @@ -17,14 +17,15 @@
from holoviews import DynamicMap, Path, Table, NdOverlay, Store, Options
from holoviews.core.util import disable_constant
from holoviews.plotting.links import DataLink
from holoviews.streams import Selection1D, Stream, PolyDraw, PolyEdit, PointDraw, CDSStream
from holoviews.streams import Selection1D, Stream, PointDraw, CDSStream, PolyEdit, PolyDraw
from geoviews.data.geopandas import GeoPandasInterface
from geoviews import Polygons, Points, WMTS, TriMesh, Path as GeoPath
from geoviews.util import path_to_geom_dicts
from shapely.geometry import Polygon, LinearRing, MultiPolygon

from .models.custom_tools import CheckpointTool, RestoreTool, ClearTool
from .links import VertexTableLink, PointTableLink
from .streams import PolyVertexDraw, PolyVertexEdit


def paths_to_polys(path):
Expand Down Expand Up @@ -158,6 +159,12 @@ class GeoAnnotator(param.Parameterized):
num_polys = param.Integer(default=None, doc="""
Maximum number of polygons to allow drawing (unlimited by default).""")

node_style = param.Dict(default={'fill_color': 'indianred', 'size': 6}, doc="""
Styling to apply to the node vertices.""")

feature_style = param.Dict(default={'fill_color': 'blue', 'size': 10}, doc="""
Styling to apply to the feature vertices.""")

height = param.Integer(default=500, doc="Height of the plot",
precedence=-1)

Expand Down Expand Up @@ -186,9 +193,20 @@ def _init_polys(self, polys=None):
opts = dict(tools=self._tools, finalize_hooks=[initialize_tools], color_index=None)
polys = self.polys if polys is None else polys
self.polys = polys.options(**opts)
self.poly_stream = PolyDraw(source=self.polys, data={}, show_vertices=True, num_objects=self.num_polys)
self.vertex_stream = PolyEdit(source=self.polys, vertex_style={'nonselection_alpha': 0.5})

if isinstance(self.polys, Polygons):
poly_draw, poly_edit = PolyDraw, PolyEdit
style_kwargs = {}
else:
poly_draw, poly_edit = PolyVertexDraw, PolyVertexEdit
style_kwargs = dict(node_style=self.node_style, feature_style=self.feature_style)
self.poly_stream = poly_draw(
source=self.polys, data={}, show_vertices=True,
num_objects=self.num_polys, **style_kwargs)
self.vertex_stream = poly_edit(
source=self.polys, vertex_style={'nonselection_alpha': 0.5},
**style_kwargs)
self._poly_selection = Selection1D(source=self.polys)

@param.depends('points', watch=True)
@preprocess
def _init_points(self, points=None):
Expand Down Expand Up @@ -316,6 +334,7 @@ def _link_polys(self):
self.polys = self.polys.add_dimension(col, 0, '', True)
self.poly_stream.source = self.polys
self.vertex_stream.source = self.polys
self._poly_selection.source = self.polys

if len(self.polys):
poly_data = gv.project(self.polys).split()
Expand Down Expand Up @@ -344,6 +363,13 @@ def table_view(self):
def panel(self):
return pn.Row(self.map_view, self.table_view)

@property
def selected_polygons(self):
index = self._poly_selection.index
if not index:
return []
return [p for i, p in enumerate(self.poly_stream.element.split()) if i in index]

@param.output(path=hv.Path)
def path_output(self):
return self.poly_stream.element
Expand Down Expand Up @@ -380,11 +406,16 @@ def _link_points(self):
projected = gv.project(self.points, projection=ccrs.PlateCarree())
self.point_table = Table(projected).opts(plot=plot, style=style)
self.point_link = PointTableLink(source=self.points, target=self.point_table)
self._point_selection = Selection1D(source=self.points)

@param.depends('points')
def table_view(self):
return self.point_table

@property
def selected_points(self):
return self.point_stream.element.iloc[self._point_selection.index]

def panel(self):
return pn.Row(self.map_view, self.table_view)

Expand Down
Binary file added earthsim/icons/PolyBreak.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 26 additions & 2 deletions earthsim/links.py
Expand Up @@ -165,16 +165,18 @@ class VertexTableLinkCallback(LinkCallback):
ys_column = target_cds.data[y]
var projected_xs = []
var projected_ys = []
var points = []
for (i = 0; i < xs_column.length; i++) {
var xv = xs_column[i]
var yv = ys_column[i]
p = projections.wgs84_mercator.forward([xv, yv])
projected_xs.push(p[0])
projected_ys.push(p[1])
points.push(i)
}
index = source_cds.selected.indices[0]
source_cds.data['xs'][index] = projected_xs;
source_cds.data['ys'][index] = projected_ys;
const xpaths = source_cds.data['xs']
const ypaths = source_cds.data['ys']
var length = source_cds.data['xs'].length
for (var col in target_cds.data) {
if ((col == x) || (col == y)) { continue; }
Expand All @@ -185,7 +187,29 @@ class VertexTableLinkCallback(LinkCallback):
source_cds.data[col] = empty
}
source_cds.data[col][index] = target_cds.data[col]
for (const p of points) {
for (let pindex = 0; pindex < xpaths.length; pindex++) {
if (pindex == index) { continue }
const xs = xpaths[pindex]
const ys = ypaths[pindex]
const column = source_cds.data[col][pindex]
if (column.length != xs.length) {
for (let ind = 0; ind < xs.length; ind++) {
column.push(null)
}
}
for (let ind = 0; ind < xs.length; ind++) {
if ((xs[ind] == xpaths[index][p]) && (ys[ind] == ypaths[index][p])) {
column[ind] = target_cds.data[col][p]
xs[ind] = projected_xs[p];
ys[ind] = projected_ys[p];
}
}
}
}
}
xpaths[index] = projected_xs;
ypaths[index] = projected_ys;
source_cds.change.emit()
source_cds.properties.data.change.emit();
source_cds.data = source_cds.data
Expand Down
40 changes: 32 additions & 8 deletions earthsim/models/custom_tools.py
@@ -1,12 +1,12 @@
import os

from bokeh.core.properties import Instance, List
from bokeh.core.properties import Instance, List, Dict, String, Any
from bokeh.core.enums import Dimensions
from bokeh.models import Tool, ColumnDataSource
from bokeh.models import Tool, ColumnDataSource, PolyEditTool, PolyDrawTool

from . import _CUSTOM_MODELS

fpath = os.path.dirname(__file__)
fpath = os.path.abspath(os.path.dirname(__file__))


class CheckpointTool(Tool):
Expand All @@ -15,32 +15,56 @@ class CheckpointTool(Tool):
the RestoreTool to restore the data to a previous state.
"""

__implementation__ = os.path.join(fpath, 'checkpoint_tool.ts')

sources = List(Instance(ColumnDataSource))

__implementation__ = os.path.join(fpath, 'checkpoint_tool.ts')


class RestoreTool(Tool):
"""
Restores the data on the supplied ColumnDataSources to a previous
checkpoint created by the CheckpointTool
"""

__implementation__ = os.path.join(fpath, 'restore_tool.ts')

sources = List(Instance(ColumnDataSource))

__implementation__ = os.path.join(fpath, 'restore_tool.ts')


class ClearTool(Tool):
"""
Clears the data on the supplied ColumnDataSources.
"""

sources = List(Instance(ColumnDataSource))

__implementation__ = os.path.join(fpath, 'clear_tool.ts')

sources = List(Instance(ColumnDataSource))

class PolyVertexEditTool(PolyEditTool):

node_style = Dict(String, Any, help="""
Custom styling to apply to the intermediate nodes of a patch or line glyph.""")

end_style = Dict(String, Any, help="""
Custom styling to apply to the start and nodes of a patch or line glyph.""")

__implementation__ = os.path.join(fpath, 'poly_edit.ts')


class PolyVertexDrawTool(PolyDrawTool):

node_style = Dict(String, Any, help="""
Custom styling to apply to the intermediate nodes of a patch or line glyph.""")

end_style = Dict(String, Any, help="""
Custom styling to apply to the start and nodes of a patch or line glyph.""")

__implementation__ = os.path.join(fpath, 'poly_draw.ts')


_CUSTOM_MODELS['earthsim.models.custom_tools.CheckPointTool'] = CheckpointTool
_CUSTOM_MODELS['earthsim.models.custom_tools.RestoreTool'] = RestoreTool
_CUSTOM_MODELS['earthsim.models.custom_tools.ClearTool'] = ClearTool
_CUSTOM_MODELS['earthsim.models.custom_tools.PolyVertexEditTool'] = PolyVertexEditTool
_CUSTOM_MODELS['earthsim.models.custom_tools.PolyVertexDrawTool'] = PolyVertexDrawTool

0 comments on commit d8d675a

Please sign in to comment.