diff --git a/earthsim/annotators.py b/earthsim/annotators.py index 82fb7d1..5e59715 100644 --- a/earthsim/annotators.py +++ b/earthsim/annotators.py @@ -17,6 +17,8 @@ from geoviews.data.geopandas import GeoPandasInterface from geoviews import Polygons, Points, WMTS, TriMesh +from .custom_tools import CheckpointTool, RestoreTool + def poly_to_geopandas(polys, columns): """ @@ -40,6 +42,19 @@ def poly_to_geopandas(polys, columns): return gpd.GeoDataFrame(rows, columns=columns+['geometry']) +def initialize_tools(plot, element): + """ + Initializes the Checkpoint and Restore tools. + """ + cds = plot.handles['source'] + checkpoint = plot.state.select(type=CheckpointTool) + restore = plot.state.select(type=RestoreTool) + if checkpoint: + checkpoint[0].sources.append(cds) + if restore: + restore[0].sources.append(cds) + + class GeoAnnotator(param.Parameterized): """ Provides support for drawing polygons and points on top of a map. @@ -68,10 +83,12 @@ def __init__(self, polys=None, points=None, crs=None, **params): polys = [] if polys is None else polys points = [] if points is None else points crs = ccrs.GOOGLE_MERCATOR if crs is None else crs - self.polys = self.path_type(polys, crs=crs) + tools = [CheckpointTool(), RestoreTool()] + opts = dict(tools=tools, finalize_hooks=[initialize_tools]) + self.polys = self.path_type(polys, crs=crs).options(**opts) self.poly_stream = PolyDraw(source=self.polys, data={}) self.vertex_stream = PolyEdit(source=self.polys) - self.points = Points(points, self.polys.kdims, crs=crs) + self.points = Points(points, self.polys.kdims, crs=crs).options(**opts) self.point_stream = PointDraw(source=self.points, data={}) def pprint(self): diff --git a/earthsim/custom_tools.py b/earthsim/custom_tools.py new file mode 100644 index 0000000..937cc5e --- /dev/null +++ b/earthsim/custom_tools.py @@ -0,0 +1,29 @@ +import os + +from bokeh.core.properties import Instance, List +from bokeh.core.enums import Dimensions +from bokeh.models import Tool, ColumnDataSource + +fpath = os.path.dirname(__file__) + + +class CheckpointTool(Tool): + """ + Checkpoints the data on the supplied ColumnDataSources, allowing + the RestoreTool to restore the data to a previous state. + """ + + __implementation__ = os.path.join(fpath, 'custom_tools.ts') + + sources = List(Instance(ColumnDataSource)) + + +class RestoreTool(Tool): + """ + Restores the data on the supplied ColumnDataSources to a previous + checkpoint created by the CheckpointTool + """ + + __implementation__ = os.path.join(fpath, 'custom_tools.ts') + + sources = List(Instance(ColumnDataSource)) diff --git a/earthsim/custom_tools.ts b/earthsim/custom_tools.ts new file mode 100644 index 0000000..b3cd299 --- /dev/null +++ b/earthsim/custom_tools.ts @@ -0,0 +1,102 @@ +import * as p from "core/properties" +import {ActionTool, ActionToolView} from "models/tools/actions/action_tool" +import {ColumnDataSource} from "models/sources/column_data_source" +import {copy} from "core/util/array" + +export class CheckpointToolView extends ActionToolView { + model: CheckpointTool + + doit(): void { + for (var source of this.model.sources) { + if (source.buffer == undefined) { source.buffer = [] } + let data_copy = {}; + for (const key in source.data) { + const column = source.data[key]; + if (Array.isArray(column) || (ArrayBuffer.isView(column))) { + const new_column = [] + for (const arr of column) { + new_column.push(copy(arr)) + } + data_copy[key] = new_column; + } else { + data_copy[key] = copy(column); + } + } + source.buffer.push(data_copy) + } + } +} + +export namespace CheckpointTool { + export interface Attrs extends ActionTool.Attrs {} + + export interface Props extends ActionTool.Props {} +} + +export interface CheckpointTool extends CheckpointTool.Attrs {} + +export class CheckpointTool extends ActionTool { + properties: CheckpointTool.Props + sources: ColumnDataSource[] + + constructor(attrs?: Partial) { + super(attrs) + } + + static initClass(): void { + this.prototype.type = "CheckpointTool" + this.prototype.default_view = CheckpointToolView + + this.define({ + sources: [ p.Array, [] ], + }) + } + + tool_name = "Checkpoint" + icon = "bk-tool-icon-save" +} +CheckpointTool.initClass() + +export class RestoreToolView extends ActionToolView { + model: RestoreTool + + doit(): void { + for (var source of this.model.sources) { + if ((source.buffer == undefined) || (source.buffer.length == 0)) { continue; } + source.data = source.buffer.pop(); + source.change.emit(); + source.properties.data.change.emit(); + } + } +} + +export namespace RestoreTool { + export interface Attrs extends ActionTool.Attrs {} + + export interface Props extends ActionTool.Props {} +} + +export interface RestoreTool extends RestoreTool.Attrs {} + +export class RestoreTool extends ActionTool { + properties: RestoreTool.Props + sources: ColumnDataSource[] + + constructor(attrs?: Partial) { + super(attrs) + } + + static initClass(): void { + this.prototype.type = "RestoreTool" + this.prototype.default_view = RestoreToolView + + this.define({ + sources: [ p.Array, [] ], + }) + } + + tool_name = "Restore" + icon = "bk-tool-icon-undo" +} + +RestoreTool.initClass()