1"""Points and related utilities 2""" 3 4from ctypes import c_double 5import warnings 6 7from shapely.coords import CoordinateSequence 8from shapely.errors import DimensionError, ShapelyDeprecationWarning 9from shapely.geos import lgeos 10from shapely.geometry.base import BaseGeometry, geos_geom_from_py 11from shapely.geometry.proxy import CachingGeometryProxy 12 13__all__ = ['Point', 'asPoint'] 14 15 16class Point(BaseGeometry): 17 """ 18 A zero dimensional feature 19 20 A point has zero length and zero area. 21 22 Attributes 23 ---------- 24 x, y, z : float 25 Coordinate values 26 27 Example 28 ------- 29 >>> p = Point(1.0, -1.0) 30 >>> print(p) 31 POINT (1 -1) 32 >>> p.y 33 -1.0 34 >>> p.x 35 1.0 36 """ 37 38 def __init__(self, *args): 39 """ 40 Parameters 41 ---------- 42 There are 2 cases: 43 44 1) 1 parameter: this must satisfy the numpy array protocol. 45 2) 2 or more parameters: x, y, z : float 46 Easting, northing, and elevation. 47 """ 48 BaseGeometry.__init__(self) 49 if len(args) > 0: 50 if len(args) == 1: 51 geom, n = geos_point_from_py(args[0]) 52 elif len(args) > 3: 53 raise TypeError( 54 "Point() takes at most 3 arguments ({} given)".format(len(args)) 55 ) 56 else: 57 geom, n = geos_point_from_py(tuple(args)) 58 self._set_geom(geom) 59 self._ndim = n 60 61 # Coordinate getters and setters 62 63 @property 64 def x(self): 65 """Return x coordinate.""" 66 return self.coords[0][0] 67 68 @property 69 def y(self): 70 """Return y coordinate.""" 71 return self.coords[0][1] 72 73 @property 74 def z(self): 75 """Return z coordinate.""" 76 if self._ndim != 3: 77 raise DimensionError("This point has no z coordinate.") 78 return self.coords[0][2] 79 80 @property 81 def __geo_interface__(self): 82 return { 83 'type': 'Point', 84 'coordinates': self.coords[0] 85 } 86 87 def svg(self, scale_factor=1., fill_color=None, opacity=None): 88 """Returns SVG circle element for the Point geometry. 89 90 Parameters 91 ========== 92 scale_factor : float 93 Multiplication factor for the SVG circle diameter. Default is 1. 94 fill_color : str, optional 95 Hex string for fill color. Default is to use "#66cc99" if 96 geometry is valid, and "#ff3333" if invalid. 97 opacity : float 98 Float number between 0 and 1 for color opacity. Defaul value is 0.6 99 """ 100 if self.is_empty: 101 return '<g />' 102 if fill_color is None: 103 fill_color = "#66cc99" if self.is_valid else "#ff3333" 104 if opacity is None: 105 opacity = 0.6 106 return ( 107 '<circle cx="{0.x}" cy="{0.y}" r="{1}" ' 108 'stroke="#555555" stroke-width="{2}" fill="{3}" opacity="{4}" />' 109 ).format(self, 3. * scale_factor, 1. * scale_factor, fill_color, opacity) 110 111 @property 112 def _ctypes(self): 113 if not self._ctypes_data: 114 array_type = c_double * self._ndim 115 array = array_type() 116 xy = self.coords[0] 117 array[0] = xy[0] 118 array[1] = xy[1] 119 if self._ndim == 3: 120 array[2] = xy[2] 121 self._ctypes_data = array 122 return self._ctypes_data 123 124 def _array_interface(self): 125 """Provide the Numpy array protocol.""" 126 if self.is_empty: 127 ai = {'version': 3, 'typestr': '<f8', 'shape': (0,), 'data': (c_double * 0)()} 128 else: 129 ai = self._array_interface_base 130 ai.update({'shape': (self._ndim,)}) 131 return ai 132 133 def array_interface(self): 134 """Provide the Numpy array protocol.""" 135 warnings.warn( 136 "The 'array_interface' method is deprecated and will be removed " 137 "in Shapely 2.0.", 138 ShapelyDeprecationWarning, stacklevel=2) 139 return self._array_interface() 140 141 @property 142 def __array_interface__(self): 143 warnings.warn( 144 "The array interface is deprecated and will no longer work in " 145 "Shapely 2.0. Convert the '.coords' to a numpy array instead.", 146 ShapelyDeprecationWarning, stacklevel=3) 147 return self._array_interface() 148 149 @property 150 def bounds(self): 151 """Returns minimum bounding region (minx, miny, maxx, maxy)""" 152 try: 153 xy = self.coords[0] 154 except IndexError: 155 return () 156 return (xy[0], xy[1], xy[0], xy[1]) 157 158 # Coordinate access 159 160 def _get_coords(self): 161 """Access to geometry's coordinates (CoordinateSequence)""" 162 return CoordinateSequence(self) 163 164 def _set_coords(self, *args): 165 warnings.warn( 166 "Setting the 'coords' to mutate a Geometry in place is deprecated," 167 " and will not be possible any more in Shapely 2.0", 168 ShapelyDeprecationWarning, stacklevel=2) 169 self._empty() 170 if len(args) == 1: 171 geom, n = geos_point_from_py(args[0]) 172 elif len(args) > 3: 173 raise TypeError("Point() takes at most 3 arguments ({} given)".format(len(args))) 174 else: 175 geom, n = geos_point_from_py(tuple(args)) 176 self._set_geom(geom) 177 self._ndim = n 178 179 coords = property(_get_coords, _set_coords) 180 181 @property 182 def xy(self): 183 """Separate arrays of X and Y coordinate values 184 185 Example: 186 >>> x, y = Point(0, 0).xy 187 >>> list(x) 188 [0.0] 189 >>> list(y) 190 [0.0] 191 """ 192 return self.coords.xy 193 194 195class PointAdapter(CachingGeometryProxy, Point): 196 197 _other_owned = False 198 199 def __init__(self, context): 200 warnings.warn( 201 "The proxy geometries (through the 'asShape()', 'asPoint()' or " 202 "'PointAdapter()' constructors) are deprecated and will be " 203 "removed in Shapely 2.0. Use the 'shape()' function or the " 204 "standard 'Point()' constructor instead.", 205 ShapelyDeprecationWarning, stacklevel=4) 206 self.context = context 207 self.factory = geos_point_from_py 208 209 @property 210 def _ndim(self): 211 try: 212 # From array protocol 213 array = self.context.__array_interface__ 214 n = array['shape'][0] 215 assert n == 2 or n == 3 216 return n 217 except AttributeError: 218 # Fall back on list 219 return len(self.context) 220 221 @property 222 def __array_interface__(self): 223 """Provide the Numpy array protocol.""" 224 try: 225 return self.context.__array_interface__ 226 except AttributeError: 227 return self.array_interface() 228 229 def _get_coords(self): 230 """Access to geometry's coordinates (CoordinateSequence)""" 231 return CoordinateSequence(self) 232 233 def _set_coords(self, ob): 234 raise NotImplementedError("Adapters can not modify their sources") 235 236 coords = property(_get_coords) 237 238 239def asPoint(context): 240 """Adapt an object to the Point interface""" 241 return PointAdapter(context) 242 243 244def geos_point_from_py(ob, update_geom=None, update_ndim=0): 245 """Create a GEOS geom from an object that is a Point, a coordinate sequence 246 or that provides the array interface. 247 248 Returns the GEOS geometry and the number of its dimensions. 249 """ 250 if isinstance(ob, Point): 251 return geos_geom_from_py(ob) 252 253 # Accept either (x, y) or [(x, y)] 254 if not hasattr(ob, '__getitem__'): # generators 255 ob = list(ob) 256 257 if isinstance(ob[0], tuple): 258 coords = ob[0] 259 else: 260 coords = ob 261 n = len(coords) 262 dx = c_double(coords[0]) 263 dy = c_double(coords[1]) 264 dz = None 265 if n == 3: 266 dz = c_double(coords[2]) 267 268 if update_geom: 269 cs = lgeos.GEOSGeom_getCoordSeq(update_geom) 270 if n != update_ndim: 271 raise ValueError( 272 "Wrong coordinate dimensions; this geometry has dimensions: " 273 "%d" % update_ndim) 274 else: 275 cs = lgeos.GEOSCoordSeq_create(1, n) 276 277 # Because of a bug in the GEOS C API, always set X before Y 278 lgeos.GEOSCoordSeq_setX(cs, 0, dx) 279 lgeos.GEOSCoordSeq_setY(cs, 0, dy) 280 if n == 3: 281 lgeos.GEOSCoordSeq_setZ(cs, 0, dz) 282 283 if update_geom: 284 return None 285 else: 286 return lgeos.GEOSGeom_createPoint(cs), n 287 288 289def update_point_from_py(geom, ob): 290 geos_point_from_py(ob, geom._geom, geom._ndim) 291