"""Points and related utilities
"""
from ctypes import c_double
from shapely.errors import DimensionError
from shapely.geos import lgeos
from shapely.geometry.base import BaseGeometry, geos_geom_from_py
from shapely.geometry.proxy import CachingGeometryProxy
__all__ = ['Point', 'asPoint']
class Point(BaseGeometry):
"""
A zero dimensional feature
A point has zero length and zero area.
Attributes
----------
x, y, z : float
Coordinate values
Example
-------
>>> p = Point(1.0, -1.0)
>>> print(p)
POINT (1.0000000000000000 -1.0000000000000000)
>>> p.y
-1.0
>>> p.x
1.0
"""
def __init__(self, *args):
"""
Parameters
----------
There are 2 cases:
1) 1 parameter: this must satisfy the numpy array protocol.
2) 2 or more parameters: x, y, z : float
Easting, northing, and elevation.
"""
BaseGeometry.__init__(self)
if len(args) > 0:
self._set_coords(*args)
# Coordinate getters and setters
@property
def x(self):
"""Return x coordinate."""
return self.coords[0][0]
@property
def y(self):
"""Return y coordinate."""
return self.coords[0][1]
@property
def z(self):
"""Return z coordinate."""
if self._ndim != 3:
raise DimensionError("This point has no z coordinate.")
return self.coords[0][2]
@property
def __geo_interface__(self):
return {
'type': 'Point',
'coordinates': self.coords[0]
}
def svg(self, scale_factor=1., fill_color=None):
"""Returns SVG circle element for the Point geometry.
Parameters
==========
scale_factor : float
Multiplication factor for the SVG circle diameter. Default is 1.
fill_color : str, optional
Hex string for fill color. Default is to use "#66cc99" if
geometry is valid, and "#ff3333" if invalid.
"""
if self.is_empty:
return ''
if fill_color is None:
fill_color = "#66cc99" if self.is_valid else "#ff3333"
return (
''
).format(self, 3. * scale_factor, 1. * scale_factor, fill_color)
@property
def ctypes(self):
if not self._ctypes_data:
array_type = c_double * self._ndim
array = array_type()
xy = self.coords[0]
array[0] = xy[0]
array[1] = xy[1]
if self._ndim == 3:
array[2] = xy[2]
self._ctypes_data = array
return self._ctypes_data
def array_interface(self):
"""Provide the Numpy array protocol."""
if self.is_empty:
ai = {'version': 3, 'typestr': ' 3:
raise TypeError("Point() takes at most 3 arguments ({} given)".format(len(args)))
else:
self._geom, self._ndim = geos_point_from_py(tuple(args))
coords = property(BaseGeometry._get_coords, _set_coords)
@property
def xy(self):
"""Separate arrays of X and Y coordinate values
Example:
>>> x, y = Point(0, 0).xy
>>> list(x)
[0.0]
>>> list(y)
[0.0]
"""
return self.coords.xy
class PointAdapter(CachingGeometryProxy, Point):
_other_owned = False
def __init__(self, context):
self.context = context
self.factory = geos_point_from_py
@property
def _ndim(self):
try:
# From array protocol
array = self.context.__array_interface__
n = array['shape'][0]
assert n == 2 or n == 3
return n
except AttributeError:
# Fall back on list
return len(self.context)
@property
def __array_interface__(self):
"""Provide the Numpy array protocol."""
try:
return self.context.__array_interface__
except AttributeError:
return self.array_interface()
_get_coords = BaseGeometry._get_coords
def _set_coords(self, ob):
raise NotImplementedError("Adapters can not modify their sources")
coords = property(_get_coords)
def asPoint(context):
"""Adapt an object to the Point interface"""
return PointAdapter(context)
def geos_point_from_py(ob, update_geom=None, update_ndim=0):
"""Create a GEOS geom from an object that is a Point, a coordinate sequence
or that provides the array interface.
Returns the GEOS geometry and the number of its dimensions.
"""
if isinstance(ob, Point):
return geos_geom_from_py(ob)
# Accept either (x, y) or [(x, y)]
if not hasattr(ob, '__getitem__'): # Iterators, e.g. Python 3 zip
ob = list(ob)
if isinstance(ob[0], tuple):
coords = ob[0]
else:
coords = ob
n = len(coords)
dx = c_double(coords[0])
dy = c_double(coords[1])
dz = None
if n == 3:
dz = c_double(coords[2])
if update_geom:
cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
if n != update_ndim:
raise ValueError(
"Wrong coordinate dimensions; this geometry has dimensions: "
"%d" % update_ndim)
else:
cs = lgeos.GEOSCoordSeq_create(1, n)
# Because of a bug in the GEOS C API, always set X before Y
lgeos.GEOSCoordSeq_setX(cs, 0, dx)
lgeos.GEOSCoordSeq_setY(cs, 0, dy)
if n == 3:
lgeos.GEOSCoordSeq_setZ(cs, 0, dz)
if update_geom:
return None
else:
return lgeos.GEOSGeom_createPoint(cs), n
def update_point_from_py(geom, ob):
geos_point_from_py(ob, geom._geom, geom._ndim)