Annotators¶
import panel as pn
import holoviews as hv
import geoviews as gv
import cartopy.crs as ccrs
from earthsim.annotators import (PolyAnnotator, PointAnnotator, PolyAndPointAnnotator,
GeoAnnotator, PointWidgetAnnotator)
hv.extension('bokeh')
This notebook documents the usage and design of a set of example GeoAnnotator
classes from the earthsim
module, which make it easy to draw, edit, and annotate polygon, polyline, and point data on top of a map. Each of these GeoAnnotator
classes builds on Bokeh Drawing Tools connected to HoloViews drawing-tools streams, providing convenient access to the drawn data from Python.
Important caveat: These classes provide complex functionality that can be useful as is, but because each specific use case for annotations is likely to have different requirements, it is best to think of the classes documented here as templates or starting points for whatever specific functionality you need in your own applications.
GeoAnnotator¶
A GeoAnnotator
allows drawing polygons and points on top of a tile source and syncing the drawn data back to Python. It does this by attaching PointDraw
, PolyDraw
, and VertexEdit
streams to the points and polygons, which in turn add the corresponding Bokeh tools.
For use when this notebook is a static web page, we'll supply an initial sample polygon and set of points, but the polys=
and points=
arguments can be omitted if you want to start with a blank map. Note that if you are viewing this notebook as a static web page, the various drawing tools should work as usual but anything involving executing Python code will not update, since there is no running Python process in that case.
hv.opts.defaults(hv.opts.Path(line_width=5))
sample_poly=dict(
Longitude = [-10114986, -10123906, -10130333, -10121522, -10129889, -10122959],
Latitude = [ 3806790, 3812413, 3807530, 3805407, 3798394, 3796693])
sample_points = dict(
Longitude = [-10131185, -10131943, -10131766, -10131032],
Latitude = [ 3805587, 3803182, 3801073, 3799778])
annot = GeoAnnotator(polys=[sample_poly], points=sample_points, path_type=gv.Path)
annot.pprint()
annot.panel()
In the map above, you can use each of the drawing tools from the toolbar to edit the existing objects, delete them, or add new ones, as described in the Drawing tools guide.
Accessing the stream data¶
The data drawn in the above plot is automatically synced to Python (as long as the Python kernel is running), and we can easily access it on the two stream classes. We'll first look at the polygon data, which can be accessed in a dynamically updated form if we want (updating automatically whenever the polygons change in the above map, as long as Python is running):
annot.poly_stream.dynamic
You can use .element
instead of .dynamic
above if you want a static version that is updated only when that cell is executed.
In most cases, however, you will probably want to get direct access to the data, either in a format matching what is accepted by a Bokeh ColumnDataSource
(in Web Mercator coordinates):
annot.poly_stream.data
or via .element
, which makes it easy to project the data into a more natural coordinate system:
gv.project(annot.poly_stream.element, projection=ccrs.PlateCarree()).dframe()
The same functionality is also available for the point_stream
:
gv.project(annot.point_stream.element, projection=ccrs.PlateCarree()).dframe()
PointAnnotator¶
PointAnnotator
is an extension of GeoAnnotator
that adds support for annotating the points with the help of a table. Whenever a point is added by tapping on the plot, an entry will appear in the table below the plot allowing you to edit the specified point_columns
(which we specify here as a Size to be associated with each point).
After selecting the Point Draw Tool you can tap anywhere to draw points, drag the points around, and delete them with backspace. Whenever a point is added it will appear in the table, and by tapping on the empty 'Size' cells you can enter a value, which will also be synced back to Python. Selecting one or more rows in the table will highlight the corresponding points.
Note that points=sample_points
is only needed here because we wanted some initial points to show, e.g. on a static web page; it can be omitted if you want to start with a blank map.
import pandas as pd
sample_points = pd.DataFrame({
'Longitude': [-10131185, -10131943, -10131766, -10131032],
'Latitude': [ 3805587, 3803182, 3801073, 3799778],
'Size': [ 5, 50, 500, 5000]})
annot = PointAnnotator(point_columns=['Size'], points=sample_points)
annot.pprint()
annot.panel()
Once again we can access the annotated points in Python by looking at the point_stream
:
gv.project(annot.point_stream.element, projection=ccrs.PlateCarree()).dframe()
Additionally we can access the currently selected points using the selected_points
property:
annot.selected_points
PolyAnnotator¶
The PolyAnnotator
works much the same as the PointAnnotator
except that it allows us to annotate polygons. As before, whenever a polygon is added (now using the Polygon Draw tool) it will appear in the table below, and selecting a row will highlight the corresponding polygon. You can edit the table to associate a 'Group' value, to add multiple attributes to a polygon you can declare any number ofpoly_columns
. The PolyAnnotator
also allows annotating the vertices in each polygon by defining vertex_columns
. When a polygon is selected using the draw tool the vertices will be shown in the second table and can be edited by tapping on a cell and pressing enter:
annot = PolyAnnotator(poly_columns=['Group'], polys=[sample_poly], vertex_columns=['Weight'])
annot.pprint()
annot.panel()
When we inspect the stream data we can see that three columns are made available, the 'xs' and 'ys' containing the vertices of each polygon/path and the Group annotation column. The vertex_columns
containing the annotations for each vertex will appear in the data once an edit has been made in the table.
annot.poly_stream.data
Just as with the PointAnnotator
we can access the currently selected polygons or paths using the selected_polygons
property:
annot.selected_polygons
PolyAndPointAnnotator¶
The PolyAndPointAnnotator
combines the PointAnnotator
and PolyAnnotator
, showing three tables to add annotations to both the points, the polygons and the polygon vertices.
annot = PolyAndPointAnnotator(
poly_columns =['Group'], polys=[sample_poly], vertex_columns=['Weight'],
point_columns=['Size'], points=sample_points
)
annot.pprint()
annot.panel()
annot.poly_stream.data
WidgetAnnotator¶
The WidgetAnnotator
takes a different approach to annotating points. Instead of annotating points by editing the Table directly, it allows adding points to a number of predefined groups. To use it,
- Add some points
- Select a subset of the points by tapping on them or using the box_select tool
- Select a group to assign to the points from the dropdown menu
- Click the add button
The indexes of the points assigned to each group can be seen in the table.
annotator = PointWidgetAnnotator(['A', 'B', 'C'], points=sample_points)
annotator.panel()
We can also view the annotated points separately, if we are running a live Python process:
points = annotator.annotated_points()
(points + points.table()).opts(shared_datasource=True)
These values can then be used in any subsequent Python code.
As you can see, the GeoAnnotator classes make it straightforward to collect user inputs specifying point and polygon data on maps, including associated values, which makes it possible to design convenient user interfaces to simulators and other code that needs inputs situated in geographic coordinates. The specific GeoAnnotator
classes used here are already useful for these tasks, but in practice it is very likely that specific applications will need new annotator classes, which you can create by copying and modifying the code for the example classes above (in earthsim/annotators.py
), or by subclassing from one of the existing classes and adding specific behavior you need for a particular application.
The Specifying Meshes user guide shows one such application, for collecting specifications for generating an irregular mesh to cover an area of the map with varying levels of detail for different regions.