{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import geoviews as gv\n", "import cartopy.crs as ccrs\n", "\n", "from geoviews import annotate\n", "\n", "gv.extension('bokeh')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook documents the usage and design of the `annotate` coordinator, which makes it easy to draw, edit, and annotate polygon, polyline, rectangle, and point data on top of a map or other plots. The `annotate` function builds on Bokeh Drawing Tools connected to HoloViews drawing-tools streams, providing convenient access to the drawn data from Python. For a detailed introduction to the ``annotate`` function, see the [HoloViews Annotator user guide](https://holoviews.org/user_guide/Annotators.html). This User Guide will focus on the additional functionality available for these annotators once GeoViews has been imported:\n", "\n", "* When used with GeoViews elements with a `crs`, the editable table will display coordinates as latitude and longitude pairs\n", "* Additional checkpoint, restore, and clear-data tools are added\n", "* The `gv.Path` annotator makes a distinction between feature nodes (at the start and end of a path) and regular nodes\n", "\n", "In this guide we will demonstrate the usage of these annotators on a map tile source to demonstrate how projections between Mercator and lat/lon coordinates are handled." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Annotating Points\n", "\n", "When annotating a GeoViews element it is assumed that the data is displayed in Web Mercator coordinates (as is the default when working with the Bokeh backend) but the coordinates may be supplied in any coordinate system. Regardless of which coordinates are provided, the table used to edit the coordinates for a GeoViews annotator will always display longitudes and latitudes, making it simpler to edit the coordinate values." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tiles = gv.tile_sources.OSM()\n", "\n", "sample_points = dict(\n", " Longitude = [-10131185, -10131943, -10131766, -10131032, -10129766],\n", " Latitude = [ 3805587, 3803182, 3801073, 3799778, 3798878])\n", "\n", "points = gv.Points(sample_points, crs=ccrs.GOOGLE_MERCATOR).opts(\n", " size=10, line_color='black', responsive=True, min_height=600\n", ")\n", "\n", "point_annotate = annotate.instance()\n", "\n", "annotated = point_annotate(points, annotations=['Size'])\n", "\n", "annotate.compose(tiles, annotated)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The annotator will return coordinates in the coordinate system they were originally defined in on the `.annotated` property. The data of this element can be accessed either using the `.dframe` method (or `.array` or `.columns`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "point_annotate.annotated.dframe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively the element can be converted to a shapely geometry:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "point_annotate.annotated.geom(projection=ccrs.PlateCarree())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Annotating Rectangles\n", "\n", "The GeoViews RectangleAnnotator behaves much like the RectangleAnnotator in HoloViews, but also projects the coordinates in the table to be more human-readable. To draw a rectangle, select the RectangleAnnotator tool in the Bokeh toolbar, then double click on one corner and drag to the location of the opposite corner:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rectangles = gv.Rectangles([(0, 0, 10, 10)])\n", "\n", "box_annotate = annotate.instance()\n", "\n", "annotated = box_annotate(rectangles, annotations=['Label'], name='Rectangles')\n", "\n", "annotate.compose(tiles, annotated)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Just like the Points the data can be accessed using the `.annotated` property:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "box_annotate.annotated.dframe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or as a shapely geometry:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "box_annotate.annotated.geom()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Annotating Paths\n", "\n", "The annotator for `gv.Path` objects behaves slightly differently from the one for HoloViews `hv.Path` objects, providing support for specifying the sort of boundary conditions typical in certain types of Earth-science modeling. Specifically, when annotating a GeoViews Path, a distinction is made between feature nodes, which describe the start and end point of a multi-line geometry, and regular nodes, which make up the interior nodes of the geometry. Attaching a new path to a regular node will split the existing path, automatically promoting the regular node to a feature node." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sample_poly=dict(\n", " Longitude = [-10114986, -10123906, -10130333, -10121522, -10129889, -10122959],\n", " Latitude = [ 3806790, 3812413, 3807530, 3805407, 3798394, 3796693])\n", "\n", "\n", "path = gv.Path([sample_poly], crs=ccrs.GOOGLE_MERCATOR).opts(\n", " line_width=2, color='black', responsive=True\n", ")\n", "\n", "path_annotate = annotate.instance()\n", "\n", "annotated = path_annotate(path, vertex_annotations=['Height'])\n", "\n", "annotate.compose(tiles, annotated)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each path on the `annotated` element can be selected using `iloc` or by using `.split` which will return a list of Path elements representing each geometry:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "path_annotate.annotated.iloc[0].dframe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Path` elements also support the `geom` method:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "path_annotate.annotated.geom(projection=ccrs.PlateCarree())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Annotating Polygons\n", "\n", "\n", "The GeoViews Polygons annotator behaves much like the annotator in HoloViews but also projects the coordinates in the table to be more readable:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "poly = gv.Polygons([sample_poly], crs=ccrs.GOOGLE_MERCATOR)\n", "\n", "poly_annotate = annotate.instance()\n", "\n", "annotated = poly_annotate(poly, annotations=['Value'], vertex_annotations=['Height'])\n", "\n", "annotate.compose(tiles, annotated)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Accessing `Polygons` works the same as with `Path` elements:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "poly_annotate.annotated.iloc[0].dframe()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and can also be converted to shapely geometries:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "poly_annotate.annotated.iloc[0].geom(projection=ccrs.PlateCarree())" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }