1import warnings 2from collections.abc import Sized 3 4import numpy as np 5 6from . import Geometry # noqa 7from . import geos_capi_version_string, lib 8from .enum import ParamEnum 9 10# Allowed options for handling WKB/WKT decoding errors 11# Note: cannot use standard constructor since "raise" is a keyword 12DecodingErrorOptions = ParamEnum( 13 "DecodingErrorOptions", {"ignore": 0, "warn": 1, "raise": 2} 14) 15 16 17ShapelyGeometry = None 18ShapelyPreparedGeometry = None 19shapely_lgeos = None 20shapely_geom_factory = None 21shapely_wkb_loads = None 22shapely_compatible = None 23_shapely_checked = False 24 25 26def check_shapely_version(): 27 """ 28 This function will try to import shapely and extracts some necessary classes and functions from the package. 29 It also looks if Shapely and PyGEOS use the same GEOS version, as this means the conversion can be faster. 30 31 This function sets a few global variables: 32 33 - ShapelyGeometry: 34 shapely.geometry.base.BaseGeometry 35 - ShapelyPreparedGeometry: 36 shapely.prepared.PreparedGeometry 37 - shapely_lgeos: 38 shapely.geos.lgeos 39 - shapely_geom_factory: 40 shapely.geometry.base.geom_factory 41 - shapely_wkb_loads: 42 shapely.wkb.loads 43 - shapely_compatible: 44 ``None`` if shapely is not installed, 45 ``True`` if shapely and PyGEOS use the same GEOS version, 46 ``False`` otherwise 47 - _shapely_checked: 48 Mostly internal variable to mark that we already tried to import shapely 49 """ 50 global ShapelyGeometry 51 global ShapelyPreparedGeometry 52 global shapely_lgeos 53 global shapely_geom_factory 54 global shapely_wkb_loads 55 global shapely_compatible 56 global _shapely_checked 57 58 if not _shapely_checked: 59 try: 60 from shapely.geometry.base import BaseGeometry as ShapelyGeometry 61 from shapely.geometry.base import geom_factory as shapely_geom_factory 62 from shapely.geos import geos_version_string 63 from shapely.geos import lgeos as shapely_lgeos 64 from shapely.prepared import PreparedGeometry as ShapelyPreparedGeometry 65 from shapely.wkb import loads as shapely_wkb_loads 66 67 # shapely has something like: "3.6.2-CAPI-1.10.2 4d2925d6" 68 # pygeos has something like: "3.6.2-CAPI-1.10.2" 69 shapely_compatible = True 70 if not geos_version_string.startswith(geos_capi_version_string): 71 shapely_compatible = False 72 warnings.warn( 73 "The shapely GEOS version ({}) is incompatible " 74 "with the PyGEOS GEOS version ({}). " 75 "Conversions between both will be slow".format( 76 geos_version_string, geos_capi_version_string 77 ) 78 ) 79 except ImportError: 80 pass 81 82 _shapely_checked = True 83 84 85__all__ = ["from_shapely", "from_wkb", "from_wkt", "to_shapely", "to_wkb", "to_wkt"] 86 87 88def to_wkt( 89 geometry, 90 rounding_precision=6, 91 trim=True, 92 output_dimension=3, 93 old_3d=False, 94 **kwargs 95): 96 """ 97 Converts to the Well-Known Text (WKT) representation of a Geometry. 98 99 The Well-known Text format is defined in the `OGC Simple Features 100 Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__. 101 102 Parameters 103 ---------- 104 geometry : Geometry or array_like 105 rounding_precision : int, default 6 106 The rounding precision when writing the WKT string. Set to a value of 107 -1 to indicate the full precision. 108 trim : bool, default True 109 If True, trim unnecessary decimals (trailing zeros). 110 output_dimension : int, default 3 111 The output dimension for the WKT string. Supported values are 2 and 3. 112 Specifying 3 means that up to 3 dimensions will be written but 2D 113 geometries will still be represented as 2D in the WKT string. 114 old_3d : bool, default False 115 Enable old style 3D/4D WKT generation. By default, new style 3D/4D WKT 116 (ie. "POINT Z (10 20 30)") is returned, but with ``old_3d=True`` 117 the WKT will be formatted in the style "POINT (10 20 30)". 118 **kwargs 119 For other keyword-only arguments, see the 120 `NumPy ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_. 121 122 Examples 123 -------- 124 >>> to_wkt(Geometry("POINT (0 0)")) 125 'POINT (0 0)' 126 >>> to_wkt(Geometry("POINT (0 0)"), rounding_precision=3, trim=False) 127 'POINT (0.000 0.000)' 128 >>> to_wkt(Geometry("POINT (0 0)"), rounding_precision=-1, trim=False) 129 'POINT (0.0000000000000000 0.0000000000000000)' 130 >>> to_wkt(Geometry("POINT (1 2 3)"), trim=True) 131 'POINT Z (1 2 3)' 132 >>> to_wkt(Geometry("POINT (1 2 3)"), trim=True, output_dimension=2) 133 'POINT (1 2)' 134 >>> to_wkt(Geometry("POINT (1 2 3)"), trim=True, old_3d=True) 135 'POINT (1 2 3)' 136 137 Notes 138 ----- 139 The defaults differ from the default of the GEOS library. To mimic this, 140 use:: 141 142 to_wkt(geometry, rounding_precision=-1, trim=False, output_dimension=2) 143 144 """ 145 if not np.isscalar(rounding_precision): 146 raise TypeError("rounding_precision only accepts scalar values") 147 if not np.isscalar(trim): 148 raise TypeError("trim only accepts scalar values") 149 if not np.isscalar(output_dimension): 150 raise TypeError("output_dimension only accepts scalar values") 151 if not np.isscalar(old_3d): 152 raise TypeError("old_3d only accepts scalar values") 153 154 return lib.to_wkt( 155 geometry, 156 np.intc(rounding_precision), 157 np.bool_(trim), 158 np.intc(output_dimension), 159 np.bool_(old_3d), 160 **kwargs, 161 ) 162 163 164def to_wkb( 165 geometry, hex=False, output_dimension=3, byte_order=-1, include_srid=False, **kwargs 166): 167 r""" 168 Converts to the Well-Known Binary (WKB) representation of a Geometry. 169 170 The Well-Known Binary format is defined in the `OGC Simple Features 171 Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__. 172 173 The following limitations apply to WKB serialization: 174 175 - linearrings will be converted to linestrings 176 - a point with only NaN coordinates is converted to an empty point 177 - empty points are transformed to 3D in GEOS < 3.8 178 - empty points are transformed to 2D in GEOS 3.8 179 180 Parameters 181 ---------- 182 geometry : Geometry or array_like 183 hex : bool, default False 184 If true, export the WKB as a hexidecimal string. The default is to 185 return a binary bytes object. 186 output_dimension : int, default 3 187 The output dimension for the WKB. Supported values are 2 and 3. 188 Specifying 3 means that up to 3 dimensions will be written but 2D 189 geometries will still be represented as 2D in the WKB represenation. 190 byte_order : int, default -1 191 Defaults to native machine byte order (-1). Use 0 to force big endian 192 and 1 for little endian. 193 include_srid : bool, default False 194 If True, the SRID is be included in WKB (this is an extension 195 to the OGC WKB specification). 196 **kwargs 197 For other keyword-only arguments, see the 198 `NumPy ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_. 199 200 Examples 201 -------- 202 >>> to_wkb(Geometry("POINT (1 1)"), byte_order=1) 203 b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?' 204 >>> to_wkb(Geometry("POINT (1 1)"), hex=True, byte_order=1) 205 '0101000000000000000000F03F000000000000F03F' 206 """ 207 if not np.isscalar(hex): 208 raise TypeError("hex only accepts scalar values") 209 if not np.isscalar(output_dimension): 210 raise TypeError("output_dimension only accepts scalar values") 211 if not np.isscalar(byte_order): 212 raise TypeError("byte_order only accepts scalar values") 213 if not np.isscalar(include_srid): 214 raise TypeError("include_srid only accepts scalar values") 215 216 return lib.to_wkb( 217 geometry, 218 np.bool_(hex), 219 np.intc(output_dimension), 220 np.intc(byte_order), 221 np.bool_(include_srid), 222 **kwargs, 223 ) 224 225 226def to_shapely(geometry): 227 """ 228 Converts PyGEOS geometries to Shapely. 229 230 Parameters 231 ---------- 232 geometry : shapely Geometry object or array_like 233 234 Examples 235 -------- 236 >>> to_shapely(Geometry("POINT (1 1)")) # doctest: +SKIP 237 <shapely.geometry.point.Point at 0x7f0c3d737908> 238 239 Notes 240 ----- 241 If PyGEOS and Shapely do not use the same GEOS version, 242 the conversion happens through the WKB format and will thus be slower. 243 """ 244 check_shapely_version() 245 if shapely_compatible is None: 246 raise ImportError("This function requires shapely") 247 248 unpack = geometry is None or isinstance(geometry, Geometry) 249 if unpack: 250 geometry = (geometry,) 251 252 if shapely_compatible: 253 geometry = [ 254 None 255 if g is None 256 else shapely_geom_factory(shapely_lgeos.GEOSGeom_clone(g._ptr)) 257 for g in geometry 258 ] 259 else: 260 geometry = to_wkb(geometry) 261 geometry = [None if g is None else shapely_wkb_loads(g) for g in geometry] 262 263 if unpack: 264 return geometry[0] 265 else: 266 arr = np.empty(len(geometry), dtype=object) 267 arr[:] = geometry 268 return arr 269 270 271def from_wkt(geometry, on_invalid="raise", **kwargs): 272 """ 273 Creates geometries from the Well-Known Text (WKT) representation. 274 275 The Well-known Text format is defined in the `OGC Simple Features 276 Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__. 277 278 Parameters 279 ---------- 280 geometry : str or array_like 281 The WKT string(s) to convert. 282 on_invalid : {"raise", "warn", "ignore"}, default "raise" 283 - raise: an exception will be raised if WKT input geometries are invalid. 284 - warn: a warning will be raised and invalid WKT geometries will be 285 returned as ``None``. 286 - ignore: invalid WKT geometries will be returned as ``None`` without a warning. 287 **kwargs 288 For other keyword-only arguments, see the 289 `NumPy ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_. 290 291 Examples 292 -------- 293 >>> from_wkt('POINT (0 0)') 294 <pygeos.Geometry POINT (0 0)> 295 """ 296 if not np.isscalar(on_invalid): 297 raise TypeError("on_invalid only accepts scalar values") 298 299 invalid_handler = np.uint8(DecodingErrorOptions.get_value(on_invalid)) 300 301 return lib.from_wkt(geometry, invalid_handler, **kwargs) 302 303 304def from_wkb(geometry, on_invalid="raise", **kwargs): 305 r""" 306 Creates geometries from the Well-Known Binary (WKB) representation. 307 308 The Well-Known Binary format is defined in the `OGC Simple Features 309 Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__. 310 311 312 Parameters 313 ---------- 314 geometry : str or array_like 315 The WKB byte object(s) to convert. 316 on_invalid : {"raise", "warn", "ignore"}, default "raise" 317 - raise: an exception will be raised if WKB input geometries are invalid. 318 - warn: a warning will be raised and invalid WKB geometries will be 319 returned as ``None``. 320 - ignore: invalid WKB geometries will be returned as ``None`` without a warning. 321 **kwargs 322 For other keyword-only arguments, see the 323 `NumPy ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_. 324 325 Examples 326 -------- 327 >>> from_wkb(b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?') 328 <pygeos.Geometry POINT (1 1)> 329 """ 330 331 if not np.isscalar(on_invalid): 332 raise TypeError("on_invalid only accepts scalar values") 333 334 invalid_handler = np.uint8(DecodingErrorOptions.get_value(on_invalid)) 335 336 # ensure the input has object dtype, to avoid numpy inferring it as a 337 # fixed-length string dtype (which removes trailing null bytes upon access 338 # of array elements) 339 geometry = np.asarray(geometry, dtype=object) 340 return lib.from_wkb(geometry, invalid_handler, **kwargs) 341 342 343def from_shapely(geometry, **kwargs): 344 """ 345 Creates geometries from shapely Geometry objects. 346 347 Parameters 348 ---------- 349 geometry : shapely Geometry object or array_like 350 **kwargs 351 For other keyword-only arguments, see the 352 `NumPy ufunc docs <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_. 353 354 Examples 355 -------- 356 >>> from shapely.geometry import Point # doctest: +SKIP 357 >>> from_shapely(Point(1, 2)) # doctest: +SKIP 358 <pygeos.Geometry POINT (1 2)> 359 360 Notes 361 ----- 362 If PyGEOS and Shapely do not use the same GEOS version, 363 the conversion happens through the WKB format and will thus be slower. 364 """ 365 check_shapely_version() 366 if shapely_compatible is None: 367 raise ImportError("This function requires shapely") 368 369 if shapely_compatible: 370 if isinstance(geometry, (ShapelyGeometry, ShapelyPreparedGeometry)): 371 # this so that the __array_interface__ of the shapely geometry is not 372 # used, converting the Geometry to its coordinates 373 arr = np.empty(1, dtype=object) 374 arr[0] = geometry 375 arr.shape = () 376 elif not isinstance(geometry, np.ndarray) and isinstance(geometry, Sized): 377 # geometry is a list/array-like 378 arr = np.empty(len(geometry), dtype=object) 379 arr[:] = geometry 380 else: 381 # we already have a numpy array or we are None 382 arr = geometry 383 384 return lib.from_shapely(arr, **kwargs) 385 else: 386 unpack = geometry is None or isinstance( 387 geometry, (ShapelyGeometry, ShapelyPreparedGeometry) 388 ) 389 if unpack: 390 geometry = (geometry,) 391 392 arr = [] 393 for g in geometry: 394 if isinstance(g, ShapelyPreparedGeometry): 395 g = g.context 396 397 if g is None: 398 arr.append(None) 399 elif g.is_empty and g.geom_type == "Point": 400 arr.append( 401 b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f" 402 ) 403 else: 404 arr.append(g.wkb) 405 406 if unpack: 407 arr = arr[0] 408 409 return from_wkb(arr) 410