Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spatialpandas interface and polygon rasterization #4120

Merged
merged 59 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
b8edf52
Add spatialpandas interface and polygon rasterization
philippjfr Dec 6, 2019
d7c4029
Fixed flakes
philippjfr Dec 6, 2019
940e5d6
Ensure pipelined dataset retains datatype
philippjfr Dec 10, 2019
3857a1d
Implemented consistent geometry API contract
philippjfr Dec 10, 2019
7957e2a
Dropped spatialpandas priority
philippjfr Dec 10, 2019
0813cbb
Added generalized geometry tests
philippjfr Dec 10, 2019
9d81ccd
Small fix for MultiPath values
philippjfr Dec 10, 2019
bc70af3
Fixed flakes
philippjfr Dec 10, 2019
c7f25b8
Fixed scalar issue
philippjfr Dec 10, 2019
195df71
Add spatialpandas
philippjfr Dec 10, 2019
b9b09af
Fixed Dataset.dataset property
philippjfr Dec 10, 2019
0b58b07
Improved geometry scalar handling
philippjfr Dec 10, 2019
3261920
Fix for handling of holes
philippjfr Dec 10, 2019
e495064
Reraise specific error if single datatype is provided
philippjfr Dec 11, 2019
81543e9
Add spatialpandas as supported subtype
philippjfr Dec 11, 2019
8601ef8
Add isunique wrapper for backward compatibility
philippjfr Dec 11, 2019
558c050
Refactor and fixes for spatialpandas interface
philippjfr Dec 11, 2019
721d99d
Handle nested data
philippjfr Dec 11, 2019
04853dd
Add test fixes and additional tests
philippjfr Dec 11, 2019
46bc069
Fixed flakes
philippjfr Dec 11, 2019
720ffbc
Made spatialpandas install py3 only
philippjfr Dec 11, 2019
722f0c7
Updated tests and fixed minor issues
philippjfr Dec 11, 2019
50f2b6e
Fixed travis
philippjfr Dec 11, 2019
ab33454
Fixed flakes
philippjfr Dec 11, 2019
7ccfe75
Try to fix spatialpandas install
philippjfr Dec 11, 2019
eb81045
Fix flakes
philippjfr Dec 11, 2019
2dd7b1c
Fixed holes validation:
philippjfr Dec 11, 2019
0d61c9b
Fixed flake
philippjfr Dec 11, 2019
8c769b0
Fixed remaining test issues
philippjfr Dec 11, 2019
e672d50
Various fixes tests and refactoring
philippjfr Dec 11, 2019
a9e437d
Flake fixes
philippjfr Dec 11, 2019
876a723
Fixed MultiInterface.select
philippjfr Dec 12, 2019
7075f65
Fixed test
philippjfr Dec 12, 2019
936fdb4
Refactored selection
philippjfr Dec 12, 2019
5ea1d87
Refactoring and bug fixes
philippjfr Dec 13, 2019
dc1df71
Improved isscalar, values and sort for geometry interfaces
philippjfr Dec 15, 2019
eb7643a
Improved geometry plot handling
philippjfr Dec 15, 2019
2b436ca
Improvements to comparisons
philippjfr Dec 15, 2019
108a0e5
Fixed matplotlib Path plot handling
philippjfr Dec 15, 2019
5172f8d
Refactoring and fixes for Polygon/Ring expansion
philippjfr Dec 15, 2019
053b83c
Updated Geometry data docs
philippjfr Dec 15, 2019
aff16ce
Cleanup up interfaces
philippjfr Dec 16, 2019
a0a3889
Fixed and cleaned up plotting tests
philippjfr Dec 16, 2019
6b6edac
Better handling of geometry types
philippjfr Dec 16, 2019
247c7f4
Restore Path.select extents setting
philippjfr Dec 16, 2019
a97ccb8
Fixed flakes
philippjfr Dec 16, 2019
f4fee12
Update matplotlib tests
philippjfr Dec 16, 2019
ea46cd6
Further Ring handling fixes
philippjfr Dec 16, 2019
615f9c3
Add datashader geometry tests
philippjfr Dec 16, 2019
63bd57c
Fixed various test errors
philippjfr Dec 16, 2019
d2ef014
Test fix
philippjfr Dec 16, 2019
95f4f2b
No longer allow tabular interfaces on Path
philippjfr Dec 16, 2019
fe00ddf
Removed superfluous casting code
philippjfr Dec 16, 2019
3e9aae4
Fixed annotator issue
philippjfr Dec 16, 2019
9ad6f8b
Improvements for Dataset comparisons
philippjfr Dec 16, 2019
f9db2a0
Various fixes
philippjfr Dec 17, 2019
ada7a87
Updated datashader tests
philippjfr Dec 17, 2019
0fd9c54
Update Geometry_Data notebook
jlstevens Dec 18, 2019
edc8013
Applied suggested changes
philippjfr Dec 18, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions examples/user_guide/Geometry_Data.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"source": [
"In addition to the two main types of data, namely tabular/columnar and gridded data HoloViews also provide extensible interfaces to represent path geometry data. Specifically it has three main element types used to representing different types of geometries. In this section we will cover the HoloViews data model for representing different kinds of geometries.\n",
"\n",
"There are many different ways of representing path geometries but HoloViews' data model is oriented on GEOS geometry definitions and allows faithfully round-tripping data between its element types and GEOS geometry definitions such as ``LinearString``, ``Polygon``, ``MultiLineString`` and ``MultiPolygon`` geometries (even if this is not implemented in HoloViews itself). Since HoloViews interfaces are extensible many different formats for representing geometries could be supported (see [GeoViews](http://geoviews.org/user_guide/Geometries.html) for other representations) but here we will cover the native formats used by HoloViews to represent this data."
"There are many different ways of representing path geometries but HoloViews' data model is oriented on GEOS geometry definitions and allows faithfully round-tripping data between its element types and GEOS geometry definitions such as ``LinearString``, ``Polygon``, ``MultiLineString`` and ``MultiPolygon`` geometries (even if this is not implemented in HoloViews itself). HoloViews defines a dictionary based format for the geometries but also supports [spatialpandas](https://github.com/holoviz/spatialpandas), which is a highly optimized implementation similar to [geopandas](https://github.com/geopandas/geopandas/) but without the heavy geo-dependencies such as shapely and fiona. [GeoViews](https://geoviews.org/user_guide/Geometries.html) supports both geopandas and raw shapely geometries directly."
]
},
{
Expand Down Expand Up @@ -41,14 +41,16 @@
"metadata": {},
"outputs": [],
"source": [
"hv.Path({'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2]})"
"hv.Path({'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2]}, ['x', 'y'])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here the dictionary of x- and y-coordinates could also be an NumPy array with two columns or a dataframe with 'x' and 'y' columns. To draw multiple paths the data-structures can be wrapped in a list. Additionally, it is also possible to associate a value with each path by declaring it as a value dimension:"
"Here the dictionary of x- and y-coordinates could also be an NumPy array with two columns or a dataframe with 'x' and 'y' columns.\n",
"\n",
"To draw multiple paths the data-structures can be wrapped in a list. Additionally, it is also possible to associate a value with each path by declaring it as a value dimension:"
]
},
{
Expand All @@ -57,8 +59,9 @@
"metadata": {},
"outputs": [],
"source": [
"hv.Path([{'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2], 'value': 0},\n",
" {'x': [5, 4, 3, 2, 1], 'y': [2, 2, 1, 1, 0], 'value': 1}], vdims='value').opts(color='value')"
"p = hv.Path([{'x': [1, 2, 3, 4, 5], 'y': [0, 0, 1, 1, 2], 'value': 0},\n",
" {'x': [5, 4, 3, 2, 1], 'y': [2, 2, 1, 1, 0], 'value': 1}], vdims='value').opts(color='value')\n",
"p"
]
},
{
Expand Down Expand Up @@ -193,7 +196,8 @@
"outputs": [],
"source": [
"hv.Polygons([{'x': xs, 'y': ys, 'holes': holes, 'value': 0},\n",
" {'x': [4, 6, 6], 'y': [0, 2, 1], 'value': 1}, {'x': [-3, -1, -6], 'y': [3, 2, 1], 'value': 3}], vdims='value')"
" {'x': [4, 6, 6], 'y': [0, 2, 1], 'value': 1},\n",
" {'x': [-3, -1, -6], 'y': [3, 2, 1], 'value': 3}], vdims='value')"
]
},
{
Expand Down Expand Up @@ -228,7 +232,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Using the ``datatype`` argument the data may instead be returned in the desired format, e.g. a list of arrays:"
"Using the ``datatype`` argument the data may instead be returned in the desired format, e.g. 'dictionary', 'array' or 'dataframe'. Here we return the 'dictionary' format:"
]
},
{
Expand All @@ -237,7 +241,7 @@
"metadata": {},
"outputs": [],
"source": [
"poly.split(datatype='array')"
"poly.split(datatype='dictionary')"
]
},
{
Expand All @@ -255,5 +259,5 @@
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
2 changes: 1 addition & 1 deletion holoviews/annotators.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ def _update_table(self):
table = self.object
for transform in self.table_transforms:
table = transform(table)
table_data = {a: [d.dimension_values(a, expanded=False)[0] for d in table.split()]
table_data = {a: list(table.dimension_values(a, expanded=False))
for a in annotations}
self._table = Table(table_data, annotations, [], label=name).opts(
show_title=False, **self.table_opts)
Expand Down
8 changes: 5 additions & 3 deletions holoviews/core/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@
from .grid import GridInterface
from .multipath import MultiInterface # noqa (API import)
from .image import ImageInterface # noqa (API import)
from .spatialpandas import SpatialPandasInterface # noqa (API import)

default_datatype = 'dictionary'
datatypes = ['dictionary', 'grid']
datatypes = ['dictionary', 'grid', 'spatialpandas']

try:
import pandas as pd # noqa (Availability import)
from .pandas import PandasInterface
default_datatype = 'dataframe'
datatypes = ['dataframe', 'dictionary', 'grid']
datatypes = ['dataframe', 'dictionary', 'spatialpandas', 'grid']
DFColumns = PandasInterface
except ImportError:
pd = None
Expand Down Expand Up @@ -331,7 +332,8 @@ def dataset(self):
"""
from . import Dataset
if self._dataset is None:
dataset = Dataset(self, _validate_vdims=False)
datatype = list(util.unique_iterator(self.datatype+Dataset.datatype))
dataset = Dataset(self, _validate_vdims=False, datatype=datatype)
if hasattr(self, '_binned'):
dataset._binned = self._binned
return dataset
Expand Down
29 changes: 26 additions & 3 deletions holoviews/core/data/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,18 @@ def groupby(cls, dataset, dimensions, container_type, group_type, **kwargs):
def select(cls, dataset, selection_mask=None, **selection):
if selection_mask is None:
selection_mask = cls.select_mask(dataset, selection)
empty = not selection_mask.sum()
dimensions = dataset.dimensions()
if empty:
return {d.name: np.array([], dtype=cls.dtype(dataset, d))
for d in dimensions}
indexed = cls.indexed(dataset, selection)
data = OrderedDict((k, v if isscalar(v) else v[selection_mask])
for k, v in dataset.data.items())
data = OrderedDict()
for k, v in dataset.data.items():
if k not in dimensions or isscalar(v):
data[k] = v
else:
data[k] = v[selection_mask]
if indexed and len(list(data.values())[0]) == 1 and len(dataset.vdims) == 1:
value = data[dataset.vdims[0].name]
return value if isscalar(value) else value[0]
Expand Down Expand Up @@ -389,6 +398,11 @@ def iloc(cls, dataset, index):
return arr if isscalar(arr) else arr[0]
return new_data


@classmethod
def geom_type(cls, dataset):
return dataset.data.get('geom_type')

@classmethod
def has_holes(cls, dataset):
from holoviews.element import Polygons
Expand All @@ -400,7 +414,16 @@ def holes(cls, dataset):
from holoviews.element import Polygons
key = Polygons._hole_key
if key in dataset.data:
return [[[np.asarray(h) for h in hs] for hs in dataset.data[key]]]
holes = []
for hs in dataset.data[key]:
subholes = []
for h in hs:
hole = np.asarray(h)
if (hole[0, :] != hole[-1, :]).all():
hole = np.concatenate([hole, hole[:1]])
subholes.append(hole)
holes.append(subholes)
return [holes]
else:
return super(DictInterface, cls).holes(dataset)

Expand Down
23 changes: 20 additions & 3 deletions holoviews/core/data/interface.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import absolute_import

import sys
import warnings

import six
import param
import numpy as np

Expand Down Expand Up @@ -229,6 +231,8 @@ def initialize(cls, eltype, data, kdims, vdims, datatype=None):
interface = data.interface
if interface.datatype in datatype and interface.datatype in eltype.datatype:
data = data.data
elif interface.multi and any(cls.interfaces[dt].multi for dt in datatype if dt in cls.interfaces):
data = [d for d in data.interface.split(data, None, None, 'columns')]
elif interface.gridded and any(cls.interfaces[dt].gridded for dt in datatype):
new_data = []
for kd in data.kdims:
Expand All @@ -239,6 +243,8 @@ def initialize(cls, eltype, data, kdims, vdims, datatype=None):
for vd in data.vdims:
new_data.append(interface.values(data, vd, flat=False, compute=False))
data = tuple(new_data)
elif 'dataframe' in datatype and util.pd:
data = data.dframe()
else:
data = tuple(data.columns().values())
elif isinstance(data, Element):
Expand Down Expand Up @@ -269,16 +275,17 @@ def initialize(cls, eltype, data, kdims, vdims, datatype=None):
except DataError:
raise
except Exception as e:
if interface in head:
priority_errors.append((interface, e))
if interface in head or len(prioritized) == 1:
priority_errors.append((interface, e, True))
else:
error = ("None of the available storage backends were able "
"to support the supplied data format.")
if priority_errors:
intfc, e = priority_errors[0]
intfc, e, _ = priority_errors[0]
priority_error = ("%s raised following error:\n\n %s"
% (intfc.__name__, e))
error = ' '.join([error, priority_error])
raise six.reraise(DataError, DataError(error, intfc), sys.exc_info()[2])
raise DataError(error)

return data, interface, dims, extra_kws
Expand All @@ -304,6 +311,16 @@ def expanded(cls, arrays):
def isscalar(cls, dataset, dim):
return len(cls.values(dataset, dim, expanded=False)) == 1

@classmethod
def isunique(cls, dataset, dim, per_geom=False):
"""
Compatibility method introduced for v1.13.0 to smooth
over addition of per_geom kwarg for isscalar method.
"""
try:
return cls.isscalar(dataset, dim, per_geom)
except TypeError:
return cls.isscalar(dataset, dim)

@classmethod
def dtype(cls, dataset, dimension):
Expand Down
Loading