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

Added a GeohashInterface #373

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion geoviews/data/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import absolute_import

from .geohash_dict import GeohashInterface # noqa (API import)
from .geom_dict import GeomDictInterface # noqa (API import)
from .geopandas import GeoPandasInterface # noqa (API import)
from .iris import CubeInterface # noqa (API import)

44 changes: 44 additions & 0 deletions geoviews/data/geohash_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Implements a data Interface to render Geohashes as polygons.
"""
import sys

from holoviews.core.data import Interface, MultiInterface
from holoviews.core.util import unicode
from holoviews.element import Path

from ..util import geohash_to_polygon
from .geom_dict import GeomDictInterface


class GeohashInterface(GeomDictInterface):

datatype = 'geohash'

_geom_column = 'geohash'

@classmethod
def applies(cls, obj):
if 'shapely' not in sys.modules:
return False
return (isinstance(obj, cls.types) and cls._geom_column in obj
and isinstance(obj[cls._geom_column], (str, unicode)))


@classmethod
def validate(cls, dataset, validate_vdims):
GeomDictInterface.validate(dataset, validate_vdims)
try:
import geohash
except:
raise ImportError("python-geohash library required for "
"geohash support.")

@classmethod
def get_geom(cls, data):
return geohash_to_polygon(data[cls._geom_column])


MultiInterface.subtypes.insert(1, 'geohash')
Interface.register(GeohashInterface)
Path.datatype = ['geohash']+Path.datatype
31 changes: 19 additions & 12 deletions geoviews/data/geom_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ class GeomDictInterface(DictInterface):

datatype = 'geom_dictionary'

_geom_column = 'geometry'

@classmethod
def applies(cls, obj):
if 'shapely' not in sys.modules:
return False
return ((isinstance(obj, cls.types) and 'geometry' in obj
and isinstance(obj['geometry'], geom_types)) or
return ((isinstance(obj, cls.types) and cls._geom_column in obj
and isinstance(obj[cls._geom_column], geom_types)) or
isinstance(obj, geom_types))

@classmethod
def init(cls, eltype, data, kdims, vdims):
name = cls.__name__
odict_types = (OrderedDict, cyODict)
if kdims is None:
kdims = eltype.kdims
Expand All @@ -34,9 +37,9 @@ def init(cls, eltype, data, kdims, vdims):
data = {'geometry': data}

if not cls.applies(data):
raise ValueError("GeomDictInterface only handles dictionary types "
"containing a 'geometry' key and shapely geometry "
"value.")
raise ValueError("%s only handles dictionary types "
"containing a %s key and shapely geometry "
"value." % (name, cls._geom_column))

unpacked = []
for d, vals in data.items():
Expand All @@ -57,11 +60,11 @@ def init(cls, eltype, data, kdims, vdims):
if not isscalar(vals):
vals = np.asarray(vals)
if not vals.ndim == 1 and d in dimensions:
raise ValueError('DictInterface expects data for each column to be flat.')
raise ValueError('%s expects data for each column to be flat.' % name)
unpacked.append((d, vals))

if not cls.expanded([vs for d, vs in unpacked if d in dimensions and not isscalar(vs)]):
raise ValueError('DictInterface expects data to be of uniform shape.')
raise ValueError('%s expects data to be of uniform shape.' % name)
if isinstance(data, odict_types):
data.update(unpacked)
else:
Expand All @@ -74,6 +77,10 @@ def validate(cls, dataset, validate_vdims):
assert len([d for d in dataset.kdims + dataset.vdims
if d.name not in dataset.data]) == 2

@classmethod
def get_geom(cls, data):
return data[cls._geom_column]

@classmethod
def dtype(cls, dataset, dimension):
name = dataset.get_dimension(dimension, strict=True).name
Expand All @@ -84,7 +91,7 @@ def dtype(cls, dataset, dimension):
@classmethod
def has_holes(cls, dataset):
from shapely.geometry import Polygon, MultiPolygon
geom = dataset.data['geometry']
geom = cls.get_geom(dataset.data)
if isinstance(geom, Polygon) and geom.interiors:
return True
elif isinstance(geom, MultiPolygon):
Expand All @@ -96,7 +103,7 @@ def has_holes(cls, dataset):
@classmethod
def holes(cls, dataset):
from shapely.geometry import Polygon, MultiPolygon
geom = dataset.data['geometry']
geom = cls.get_geom(dataset.data)
if isinstance(geom, Polygon):
return [[[geom_to_array(h) for h in geom.interiors]]]
elif isinstance(geom, MultiPolygon):
Expand All @@ -116,7 +123,7 @@ def range(cls, dataset, dim):
dim = dataset.get_dimension(dim)
geom_dims = cls.geom_dims(dataset)
if dim in geom_dims:
bounds = dataset.data['geometry'].bounds
bounds = cls.get_geom(dataset.data).bounds
if not bounds:
return np.nan, np.nan
elif geom_dims.index(dim) == 0:
Expand All @@ -128,7 +135,7 @@ def range(cls, dataset, dim):

@classmethod
def length(cls, dataset):
return geom_length(dataset.data['geometry'])
return geom_length(cls.get_geom(dataset.data))

@classmethod
def geom_dims(cls, dataset):
Expand All @@ -140,7 +147,7 @@ def values(cls, dataset, dim, expanded=True, flat=True, compute=True, keep_index
d = dataset.get_dimension(dim)
geom_dims = cls.geom_dims(dataset)
if d in geom_dims:
g = dataset.data['geometry']
g = cls.get_geom(dataset.data)
if not g:
return np.array([])
array = geom_to_array(g)
Expand Down
18 changes: 18 additions & 0 deletions geoviews/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from cartopy import crs as ccrs
from shapely.geometry import (MultiLineString, LineString, MultiPolygon,
Polygon, LinearRing, Point, MultiPoint)
from shapely.ops import cascaded_union
from holoviews.core.util import basestring

geom_types = (MultiLineString, LineString, MultiPolygon, Polygon,
Expand Down Expand Up @@ -679,3 +680,20 @@ def from_xarray(da, crs=None, apply_transform=False, nan_nodata=False, **kwargs)
if hasattr(el.data, 'attrs'):
el.data.attrs = da.attrs
return el


def geohash_to_polygon(geo):
"""
:param geo: String that represents the geohash.
:return: Returns a Shapely's Polygon instance that represents the geohash.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:return: Returns a Shapely's Polygon instance that represents the geohash.
:return: Returns a Shapely Polygon instance that represents the geohash.

"""
import geohash

lat_centroid, lng_centroid, lat_offset, lng_offset = geohash.decode_exactly(geo)

corner_1 = (lat_centroid - lat_offset, lng_centroid - lng_offset)[::-1]
corner_2 = (lat_centroid - lat_offset, lng_centroid + lng_offset)[::-1]
corner_3 = (lat_centroid + lat_offset, lng_centroid + lng_offset)[::-1]
corner_4 = (lat_centroid + lat_offset, lng_centroid - lng_offset)[::-1]

return sgeom.Polygon([corner_1, corner_2, corner_3, corner_4, corner_1])