Skip to content

Commit

Permalink
Added Segments and Rectangles
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Jan 4, 2020
1 parent 578b0b1 commit cc07c6e
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 44 deletions.
11 changes: 6 additions & 5 deletions geoviews/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
except:
pass

from .element import (_Element, Feature, Tiles, # noqa (API import)
WMTS, LineContours, FilledContours, Text, Image,
Points, Path, Polygons, Shape, Dataset, RGB,
Contours, Graph, TriMesh, Nodes, EdgePaths,
QuadMesh, VectorField, HexTiles, Labels)
from .element import ( # noqa (API import)
_Element, Feature, Tiles, WMTS, LineContours, FilledContours,
Text, Image, Points, Path, Polygons, Shape, Dataset, RGB,
Contours, Graph, TriMesh, Nodes, EdgePaths, QuadMesh, VectorField,
HexTiles, Labels, Rectangles, Segments
)
from .util import load_tiff, from_xarray # noqa (API import)
from .operation import project # noqa (API import)
from . import data # noqa (API import)
Expand Down
16 changes: 12 additions & 4 deletions geoviews/annotators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,37 @@
import cartopy.crs as ccrs

from holoviews.annotators import (
Annotator, AnnotationManager, PathAnnotator, PolyAnnotator, PointAnnotator # noqa
Annotator, PathAnnotator, PolyAnnotator, PointAnnotator, BoxAnnotator # noqa
)
from holoviews.plotting.links import DataLink, VertexTableLink as hvVertexTableLink
from panel.util import param_name

from .models.custom_tools import CheckpointTool, RestoreTool, ClearTool
from .links import VertexTableLink, PointTableLink
from .links import VertexTableLink, PointTableLink, HvRectanglesTableLink, RectanglesTableLink
from .operation import project
from .streams import PolyVertexDraw, PolyVertexEdit

Annotator._tools = [CheckpointTool, RestoreTool, ClearTool]
Annotator.table_transforms.append(project.instance(projection=ccrs.PlateCarree()))

def get_point_table_link(self, source, target):
if hasattr(source, 'crs'):
if hasattr(source.callback.inputs[0], 'crs'):
return PointTableLink(source, target)
else:
return DataLink(source, target)

PointAnnotator._link_type = get_point_table_link

def get_rectangles_table_link(self, source, target):
if hasattr(source.callback.inputs[0], 'crs'):
return RectanglesTableLink(source, target)
else:
return HvRectanglesTableLink(source, target)

BoxAnnotator._link_type = get_rectangles_table_link

def get_vertex_table_link(self, source, target):
if hasattr(source, 'crs'):
if hasattr(source.callback.inputs[0], 'crs'):
return VertexTableLink(source, target)
else:
return hvVertexTableLink(source, target)
Expand Down
2 changes: 1 addition & 1 deletion geoviews/element/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
WMTS, Points, Image, Text, LineContours, RGB,
FilledContours, Path, Polygons, Shape, Dataset,
Contours, TriMesh, Graph, Nodes, EdgePaths, QuadMesh,
VectorField, Labels, HexTiles)
VectorField, Labels, HexTiles, Rectangles, Segments)


class GeoConversion(ElementConversion):
Expand Down
38 changes: 35 additions & 3 deletions geoviews/element/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
RGB as HvRGB, Text as HvText, TriMesh as HvTriMesh,
QuadMesh as HvQuadMesh, Points as HvPoints,
VectorField as HvVectorField, HexTiles as HvHexTiles,
Labels as HvLabels)
Labels as HvLabels, Rectangles as HvRectangles,
Segments as HvSegments
)

from shapely.geometry.base import BaseGeometry

Expand Down Expand Up @@ -50,7 +52,7 @@ def is_geographic(element, kdims=None):
else:
kdims = element.kdims

if len(kdims) != 2 and not isinstance(element, (Graph, Nodes)):
if len(kdims) != 2 and not isinstance(element, (Graph, Nodes, Rectangles, Segments)):
return False
if isinstance(element.data, geographic_types) or isinstance(element, (WMTS, Feature)):
return True
Expand Down Expand Up @@ -586,7 +588,6 @@ def __init__(self, data, kdims=None, vdims=None, **params):
super(TriMesh, self).__init__(data, kdims, vdims, **params)
self.nodes.crs = crs


@property
def edgepaths(self):
"""
Expand Down Expand Up @@ -630,6 +631,37 @@ def geom(self):
return polygon_to_geom(self)


class Rectangles(_Element, HvRectangles):
"""
Rectangles represent a collection of axis-aligned rectangles in 2D space.
"""

group = param.String(default='Rectangles', constant=True)

kdims = param.List(default=[Dimension('lon0'), Dimension('lat0'),
Dimension('lon1'), Dimension('lat1')],
bounds=(4, 4), constant=True, doc="""
The key dimensions of the Rectangles element represent the
bottom-left (lon0, lat0) and top right (lon1, lat1) coordinates
of each box.""")



class Segments(_Element, HvSegments):
"""
Segments represent a collection of lines in 2D space.
"""

group = param.String(default='Segments', constant=True)

kdims = param.List(default=[Dimension('lon0'), Dimension('lat0'),
Dimension('lon1'), Dimension('lat1')],
bounds=(4, 4), constant=True, doc="""
The key dimensions of the Segments element represent the
bottom-left (lon0, lat0) and top-right (lon1, lat1) coordinates
of each segment.""")


class Shape(Dataset):
"""
Shape wraps any shapely geometry type.
Expand Down
71 changes: 69 additions & 2 deletions geoviews/links.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import param

from holoviews.element import Path, Table, Points
from holoviews.plotting.links import Link
from holoviews.plotting.bokeh.callbacks import LinkCallback
from holoviews.plotting.links import Link, RectanglesTableLink as HvRectanglesTableLink
from holoviews.plotting.bokeh.callbacks import (
LinkCallback, RectanglesTableLinkCallback as HvRectanglesTableLinkCallback
)
from holoviews.core.util import dimension_sanitizer


Expand Down Expand Up @@ -40,6 +42,12 @@ def __init__(self, source, target, **params):
super(VertexTableLink, self).__init__(source, target, **params)


class RectanglesTableLink(HvRectanglesTableLink):
"""
Links a Rectangles element to a Table.
"""


class PointTableLinkCallback(LinkCallback):

source_model = 'cds'
Expand Down Expand Up @@ -216,6 +224,65 @@ class VertexTableLinkCallback(LinkCallback):
source_cds.data = source_cds.data
"""


class RectanglesTableLinkCallback(HvRectanglesTableLinkCallback):

source_code = """
var projections = require("core/util/projections");
var xs = source_cds.data[source_glyph.x.field]
var ys = source_cds.data[source_glyph.y.field]
var ws = source_cds.data[source_glyph.width.field]
var hs = source_cds.data[source_glyph.height.field]
var x0 = []
var x1 = []
var y0 = []
var y1 = []
for (i = 0; i < xs.length; i++) {
hw = ws[i]/2.
hh = hs[i]/2.
p1 = projections.wgs84_mercator.inverse([xs[i]-hw, ys[i]-hh])
p2 = projections.wgs84_mercator.inverse([xs[i]+hw, ys[i]+hh])
x0.push(p1[0])
x1.push(p2[0])
y0.push(p1[1])
y1.push(p2[1])
}
target_cds.data[columns[0]] = x0
target_cds.data[columns[1]] = y0
target_cds.data[columns[2]] = x1
target_cds.data[columns[3]] = y1
"""

target_code = """
var projections = require("core/util/projections");
var x0s = target_cds.data[columns[0]]
var y0s = target_cds.data[columns[1]]
var x1s = target_cds.data[columns[2]]
var y1s = target_cds.data[columns[3]]
var xs = []
var ys = []
var ws = []
var hs = []
for (i = 0; i < x0s.length; i++) {
x0 = Math.min(x0s[i], x1s[i])
y0 = Math.min(y0s[i], y1s[i])
x1 = Math.max(x0s[i], x1s[i])
y1 = Math.max(y0s[i], y1s[i])
p1 = projections.wgs84_mercator.forward([x0, y0])
p2 = projections.wgs84_mercator.forward([x1, y1])
xs.push((p1[0]+p2[0])/2.)
ys.push((p1[1]+p2[1])/2.)
ws.push(p2[0]-p1[0])
hs.push(p2[1]-p1[1])
}
source_cds.data['x'] = xs
source_cds.data['y'] = ys
source_cds.data['width'] = ws
source_cds.data['height'] = hs
"""

VertexTableLink.register_callback('bokeh', VertexTableLinkCallback)
PointTableLink.register_callback('bokeh', PointTableLinkCallback)
RectanglesTableLink.register_callback('bokeh', RectanglesTableLinkCallback)
2 changes: 1 addition & 1 deletion geoviews/operation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ..element import _Element
from .projection import ( # noqa (API import)
project_image, project_path, project_shape, project_points,
project_graph, project_quadmesh, project)
project_graph, project_quadmesh, project_geom, project)
from .resample import resample_geometry # noqa (API import)

geo_ops = [contours, bivariate_kde]
Expand Down
46 changes: 38 additions & 8 deletions geoviews/operation/projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ..data import GeoPandasInterface
from ..element import (Image, Shape, Polygons, Path, Points, Contours,
RGB, Graph, Nodes, EdgePaths, QuadMesh, VectorField,
HexTiles, Labels)
HexTiles, Labels, Rectangles, Segments)
from ..util import (
project_extents, geom_to_array, path_to_geom_dicts, polygons_to_geom_dicts,
geom_dict_to_array_dict
Expand Down Expand Up @@ -144,11 +144,10 @@ def _process_element(self, element):
if not len(element):
return element.clone(crs=self.p.projection)
geom = element.geom()
vertices = geom_to_array(geom)
if isinstance(geom, (MultiPolygon, Polygon)):
obj = Polygons([vertices])
obj = Polygons([geom])
else:
obj = Path([vertices])
obj = Path([geom])
geom = project_path(obj, projection=self.p.projection).geom()
return element.clone(geom, crs=self.p.projection)

Expand All @@ -164,10 +163,9 @@ def _process_element(self, element):
xs, ys = (element.dimension_values(i) for i in range(2))
coordinates = self.p.projection.transform_points(element.crs, xs, ys)
mask = np.isfinite(coordinates[:, 0])
new_data = {k: v[mask] for k, v in element.columns().items()}
new_data = {k: v[mask] for k, v in element.columns(element.kdims).items()}
new_data[xdim.name] = coordinates[mask, 0]
new_data[ydim.name] = coordinates[mask, 1]
datatype = [element.interface.datatype]+element.datatype

if len(new_data[xdim.name]) == 0:
self.warning('While projecting a %s element from a %s coordinate '
Expand All @@ -179,7 +177,38 @@ def _process_element(self, element):
type(self.p.projection).__name__))

return element.clone(tuple(new_data[d.name] for d in element.dimensions()),
crs=self.p.projection, datatype=datatype)
crs=self.p.projection)


class project_geom(_project_operation):

supported_types = [Rectangles, Segments]

def _process_element(self, element):
if not len(element):
return element.clone(crs=self.p.projection)
x0d, y0d, x1d, y1d = element.kdims
x0, y0, x1, y1 = (element.dimension_values(i) for i in range(4))
p1 = self.p.projection.transform_points(element.crs, x0, y0)
p2 = self.p.projection.transform_points(element.crs, x1, y1)
mask = np.isfinite(p1[:, 0]) & np.isfinite(p2[:, 0])
new_data = {k: v[mask] for k, v in element.columns(element.vdims).items()}
new_data[x0d.name] = p1[mask, 0]
new_data[y0d.name] = p1[mask, 1]
new_data[x1d.name] = p2[mask, 0]
new_data[y1d.name] = p2[mask, 1]

if len(new_data[x0d.name]) == 0:
self.warning('While projecting a %s element from a %s coordinate '
'reference system (crs) to a %s projection none of '
'the projected paths were contained within the bounds '
'specified by the projection. Ensure you have specified '
'the correct coordinate system for your data.' %
(type(element).__name__, type(element.crs).__name__,
type(self.p.projection).__name__))

return element.clone(tuple(new_data[d.name] for d in element.dimensions()),
crs=self.p.projection)


class project_graph(_project_operation):
Expand Down Expand Up @@ -399,7 +428,8 @@ class project(Operation):
Projection the image type is projected to.""")

_operations = [project_path, project_image, project_shape,
project_graph, project_quadmesh, project_points]
project_graph, project_quadmesh, project_points,
project_geom]

def _process(self, element, key=None):
for op in self._operations:
Expand Down
28 changes: 22 additions & 6 deletions geoviews/plotting/bokeh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@
from holoviews.core.options import SkipRendering, Options, Compositor
from holoviews.plotting.bokeh.annotation import TextPlot, LabelsPlot
from holoviews.plotting.bokeh.chart import PointPlot, VectorFieldPlot
from holoviews.plotting.bokeh.geometry import RectanglesPlot, SegmentPlot
from holoviews.plotting.bokeh.graphs import TriMeshPlot, GraphPlot
from holoviews.plotting.bokeh.hex_tiles import hex_binning, HexTilesPlot
from holoviews.plotting.bokeh.path import PolygonPlot, PathPlot, ContourPlot
from holoviews.plotting.bokeh.raster import RasterPlot, RGBPlot, QuadMeshPlot

from ...element import (WMTS, Points, Polygons, Path, Contours, Shape,
Image, Feature, Text, RGB, Nodes, EdgePaths,
Graph, TriMesh, QuadMesh, VectorField, Labels,
HexTiles, LineContours, FilledContours)
from ...operation import (project_image, project_points, project_path,
project_graph, project_quadmesh)
from ...element import (
WMTS, Points, Polygons, Path, Contours, Shape, Image, Feature,
Text, RGB, Nodes, EdgePaths, Graph, TriMesh, QuadMesh, VectorField,
Labels, HexTiles, LineContours, FilledContours, Rectangles, Segments
)
from ...operation import (
project_image, project_points, project_path, project_graph,
project_quadmesh, project_geom
)
from ...tile_sources import _ATTRIBUTIONS
from ...util import poly_types, line_types
from .plot import GeoPlot, GeoOverlayPlot
Expand Down Expand Up @@ -168,6 +172,16 @@ class GeoTriMeshPlot(GeoPlot, TriMeshPlot):
_project_operation = project_graph


class GeoRectanglesPlot(GeoPlot, RectanglesPlot):

_project_operation = project_geom


class GeoSegmentsPlot(GeoPlot, SegmentPlot):

_project_operation = project_geom


class GeoShapePlot(GeoPolygonPlot):

def get_data(self, element, ranges, style):
Expand Down Expand Up @@ -263,6 +277,8 @@ def _process(self, element, key=None):
VectorField: GeoVectorFieldPlot,
Polygons: GeoPolygonPlot,
Contours: GeoContourPlot,
Rectangles: GeoRectanglesPlot,
Segments: GeoSegmentsPlot,
Path: GeoPathPlot,
Shape: GeoShapePlot,
Image: GeoRasterPlot,
Expand Down
7 changes: 0 additions & 7 deletions geoviews/plotting/bokeh/plot.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""
Module for geographic bokeh plot baseclasses.
"""
from distutils.version import LooseVersion

import param
import holoviews as hv

Expand Down Expand Up @@ -182,8 +180,3 @@ def __init__(self, element, **params):
self.geographic = any(element.traverse(is_geographic, [_Element]))
if self.geographic:
self.show_grid = False
if LooseVersion(hv.__version__) < '1.10.4':
projection = self._get_projection(element)
self.projection = projection
for p in self.subplots.values():
p.projection = projection
Loading

0 comments on commit cc07c6e

Please sign in to comment.