update
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2007-2009 Justin Bronn
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of GEOSGeometry nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
The GeoDjango GEOS module. Please consult the GeoDjango documentation
|
||||
for more details: https://docs.djangoproject.com/en/dev/ref/contrib/gis/geos/
|
||||
"""
|
||||
|
||||
from .collections import ( # NOQA
|
||||
GeometryCollection,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
)
|
||||
from .error import GEOSException # NOQA
|
||||
from .factory import fromfile, fromstr # NOQA
|
||||
from .geometry import GEOSGeometry, hex_regex, wkt_regex # NOQA
|
||||
from .io import WKBReader, WKBWriter, WKTReader, WKTWriter # NOQA
|
||||
from .libgeos import geos_version # NOQA
|
||||
from .linestring import LinearRing, LineString # NOQA
|
||||
from .point import Point # NOQA
|
||||
from .polygon import Polygon # NOQA
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.ptr import CPointerBase
|
||||
|
||||
|
||||
class GEOSBase(CPointerBase):
|
||||
null_ptr_exception_class = GEOSException
|
||||
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
This module houses the Geometry Collection objects:
|
||||
GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
|
||||
"""
|
||||
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR
|
||||
from django.contrib.gis.geos.linestring import LinearRing, LineString
|
||||
from django.contrib.gis.geos.point import Point
|
||||
from django.contrib.gis.geos.polygon import Polygon
|
||||
|
||||
|
||||
class GeometryCollection(GEOSGeometry):
|
||||
_typeid = 7
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"Initialize a Geometry Collection from a sequence of Geometry objects."
|
||||
# Checking the arguments
|
||||
if len(args) == 1:
|
||||
# If only one geometry provided or a list of geometries is provided
|
||||
# in the first argument.
|
||||
if isinstance(args[0], (tuple, list)):
|
||||
init_geoms = args[0]
|
||||
else:
|
||||
init_geoms = args
|
||||
else:
|
||||
init_geoms = args
|
||||
|
||||
# Ensuring that only the permitted geometries are allowed in this collection
|
||||
# this is moved to list mixin super class
|
||||
self._check_allowed(init_geoms)
|
||||
|
||||
# Creating the geometry pointer array.
|
||||
collection = self._create_collection(len(init_geoms), init_geoms)
|
||||
super().__init__(collection, **kwargs)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterate over each Geometry in the Collection."
|
||||
for i in range(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of geometries in this Collection."
|
||||
return self.num_geom
|
||||
|
||||
# ### Methods for compatibility with ListMixin ###
|
||||
def _create_collection(self, length, items):
|
||||
# Creating the geometry pointer array.
|
||||
geoms = (GEOM_PTR * length)(
|
||||
*[
|
||||
# this is a little sloppy, but makes life easier
|
||||
# allow GEOSGeometry types (python wrappers) or pointer types
|
||||
capi.geom_clone(getattr(g, "ptr", g))
|
||||
for g in items
|
||||
]
|
||||
)
|
||||
return capi.create_collection(self._typeid, geoms, length)
|
||||
|
||||
def _get_single_internal(self, index):
|
||||
return capi.get_geomn(self.ptr, index)
|
||||
|
||||
def _get_single_external(self, index):
|
||||
"Return the Geometry from this Collection at the given index (0-based)."
|
||||
# Checking the index and returning the corresponding GEOS geometry.
|
||||
return GEOSGeometry(
|
||||
capi.geom_clone(self._get_single_internal(index)), srid=self.srid
|
||||
)
|
||||
|
||||
def _set_list(self, length, items):
|
||||
"Create a new collection, and destroy the contents of the previous pointer."
|
||||
prev_ptr = self.ptr
|
||||
srid = self.srid
|
||||
self.ptr = self._create_collection(length, items)
|
||||
if srid:
|
||||
self.srid = srid
|
||||
capi.destroy_geom(prev_ptr)
|
||||
|
||||
_set_single = GEOSGeometry._set_single_rebuild
|
||||
_assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Return the KML for this Geometry Collection."
|
||||
return "<MultiGeometry>%s</MultiGeometry>" % "".join(g.kml for g in self)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Return a tuple of all the coordinates in this Geometry Collection"
|
||||
return tuple(g.tuple for g in self)
|
||||
|
||||
coords = tuple
|
||||
|
||||
|
||||
# MultiPoint, MultiLineString, and MultiPolygon class definitions.
|
||||
class MultiPoint(GeometryCollection):
|
||||
_allowed = Point
|
||||
_typeid = 4
|
||||
|
||||
|
||||
class MultiLineString(LinearGeometryMixin, GeometryCollection):
|
||||
_allowed = (LineString, LinearRing)
|
||||
_typeid = 5
|
||||
|
||||
|
||||
class MultiPolygon(GeometryCollection):
|
||||
_allowed = Polygon
|
||||
_typeid = 6
|
||||
|
||||
|
||||
# Setting the allowed types here since GeometryCollection is defined before
|
||||
# its subclasses.
|
||||
GeometryCollection._allowed = (
|
||||
Point,
|
||||
LineString,
|
||||
LinearRing,
|
||||
Polygon,
|
||||
MultiPoint,
|
||||
MultiLineString,
|
||||
MultiPolygon,
|
||||
)
|
||||
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
This module houses the GEOSCoordSeq object, which is used internally
|
||||
by GEOSGeometry to house the actual coordinates of the Point,
|
||||
LineString, and LinearRing geometries.
|
||||
"""
|
||||
|
||||
from ctypes import byref, c_byte, c_double, c_uint
|
||||
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import CS_PTR
|
||||
from django.contrib.gis.shortcuts import numpy
|
||||
|
||||
|
||||
class GEOSCoordSeq(GEOSBase):
|
||||
"The internal representation of a list of coordinates inside a Geometry."
|
||||
|
||||
ptr_type = CS_PTR
|
||||
|
||||
def __init__(self, ptr, z=False):
|
||||
"Initialize from a GEOS pointer."
|
||||
if not isinstance(ptr, CS_PTR):
|
||||
raise TypeError("Coordinate sequence should initialize with a CS_PTR.")
|
||||
self._ptr = ptr
|
||||
self._z = z
|
||||
|
||||
def __iter__(self):
|
||||
"Iterate over each point in the coordinate sequence."
|
||||
for i in range(self.size):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of points in the coordinate sequence."
|
||||
return self.size
|
||||
|
||||
def __str__(self):
|
||||
"Return the string representation of the coordinate sequence."
|
||||
return str(self.tuple)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Return the coordinate sequence value at the given index."
|
||||
self._checkindex(index)
|
||||
return self._point_getter(index)
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"Set the coordinate sequence value at the given index."
|
||||
# Checking the input value
|
||||
if isinstance(value, (list, tuple)):
|
||||
pass
|
||||
elif numpy and isinstance(value, numpy.ndarray):
|
||||
pass
|
||||
else:
|
||||
raise TypeError(
|
||||
"Must set coordinate with a sequence (list, tuple, or numpy array)."
|
||||
)
|
||||
# Checking the dims of the input
|
||||
if self.dims == 3 and self._z:
|
||||
n_args = 3
|
||||
point_setter = self._set_point_3d
|
||||
else:
|
||||
n_args = 2
|
||||
point_setter = self._set_point_2d
|
||||
if len(value) != n_args:
|
||||
raise TypeError("Dimension of value does not match.")
|
||||
self._checkindex(index)
|
||||
point_setter(index, value)
|
||||
|
||||
# #### Internal Routines ####
|
||||
def _checkindex(self, index):
|
||||
"Check the given index."
|
||||
if not (0 <= index < self.size):
|
||||
raise IndexError("invalid GEOS Geometry index: %s" % index)
|
||||
|
||||
def _checkdim(self, dim):
|
||||
"Check the given dimension."
|
||||
if dim < 0 or dim > 2:
|
||||
raise GEOSException('invalid ordinate dimension "%d"' % dim)
|
||||
|
||||
def _get_x(self, index):
|
||||
return capi.cs_getx(self.ptr, index, byref(c_double()))
|
||||
|
||||
def _get_y(self, index):
|
||||
return capi.cs_gety(self.ptr, index, byref(c_double()))
|
||||
|
||||
def _get_z(self, index):
|
||||
return capi.cs_getz(self.ptr, index, byref(c_double()))
|
||||
|
||||
def _set_x(self, index, value):
|
||||
capi.cs_setx(self.ptr, index, value)
|
||||
|
||||
def _set_y(self, index, value):
|
||||
capi.cs_sety(self.ptr, index, value)
|
||||
|
||||
def _set_z(self, index, value):
|
||||
capi.cs_setz(self.ptr, index, value)
|
||||
|
||||
@property
|
||||
def _point_getter(self):
|
||||
return self._get_point_3d if self.dims == 3 and self._z else self._get_point_2d
|
||||
|
||||
def _get_point_2d(self, index):
|
||||
return (self._get_x(index), self._get_y(index))
|
||||
|
||||
def _get_point_3d(self, index):
|
||||
return (self._get_x(index), self._get_y(index), self._get_z(index))
|
||||
|
||||
def _set_point_2d(self, index, value):
|
||||
x, y = value
|
||||
self._set_x(index, x)
|
||||
self._set_y(index, y)
|
||||
|
||||
def _set_point_3d(self, index, value):
|
||||
x, y, z = value
|
||||
self._set_x(index, x)
|
||||
self._set_y(index, y)
|
||||
self._set_z(index, z)
|
||||
|
||||
# #### Ordinate getting and setting routines ####
|
||||
def getOrdinate(self, dimension, index):
|
||||
"Return the value for the given dimension and index."
|
||||
self._checkindex(index)
|
||||
self._checkdim(dimension)
|
||||
return capi.cs_getordinate(self.ptr, index, dimension, byref(c_double()))
|
||||
|
||||
def setOrdinate(self, dimension, index, value):
|
||||
"Set the value for the given dimension and index."
|
||||
self._checkindex(index)
|
||||
self._checkdim(dimension)
|
||||
capi.cs_setordinate(self.ptr, index, dimension, value)
|
||||
|
||||
def getX(self, index):
|
||||
"Get the X value at the index."
|
||||
return self.getOrdinate(0, index)
|
||||
|
||||
def setX(self, index, value):
|
||||
"Set X with the value at the given index."
|
||||
self.setOrdinate(0, index, value)
|
||||
|
||||
def getY(self, index):
|
||||
"Get the Y value at the given index."
|
||||
return self.getOrdinate(1, index)
|
||||
|
||||
def setY(self, index, value):
|
||||
"Set Y with the value at the given index."
|
||||
self.setOrdinate(1, index, value)
|
||||
|
||||
def getZ(self, index):
|
||||
"Get Z with the value at the given index."
|
||||
return self.getOrdinate(2, index)
|
||||
|
||||
def setZ(self, index, value):
|
||||
"Set Z with the value at the given index."
|
||||
self.setOrdinate(2, index, value)
|
||||
|
||||
# ### Dimensions ###
|
||||
@property
|
||||
def size(self):
|
||||
"Return the size of this coordinate sequence."
|
||||
return capi.cs_getsize(self.ptr, byref(c_uint()))
|
||||
|
||||
@property
|
||||
def dims(self):
|
||||
"Return the dimensions of this coordinate sequence."
|
||||
return capi.cs_getdims(self.ptr, byref(c_uint()))
|
||||
|
||||
@property
|
||||
def hasz(self):
|
||||
"""
|
||||
Return whether this coordinate sequence is 3D. This property value is
|
||||
inherited from the parent Geometry.
|
||||
"""
|
||||
return self._z
|
||||
|
||||
# ### Other Methods ###
|
||||
def clone(self):
|
||||
"Clone this coordinate sequence."
|
||||
return GEOSCoordSeq(capi.cs_clone(self.ptr), self.hasz)
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Return the KML representation for the coordinates."
|
||||
# Getting the substitution string depending on whether the coordinates have
|
||||
# a Z dimension.
|
||||
if self.hasz:
|
||||
substr = "%s,%s,%s "
|
||||
else:
|
||||
substr = "%s,%s,0 "
|
||||
return (
|
||||
"<coordinates>%s</coordinates>"
|
||||
% "".join(substr % self[i] for i in range(len(self))).strip()
|
||||
)
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Return a tuple version of this coordinate sequence."
|
||||
n = self.size
|
||||
get_point = self._point_getter
|
||||
if n == 1:
|
||||
return get_point(0)
|
||||
return tuple(get_point(i) for i in range(n))
|
||||
|
||||
@property
|
||||
def is_counterclockwise(self):
|
||||
"""Return whether this coordinate sequence is counterclockwise."""
|
||||
ret = c_byte()
|
||||
if not capi.cs_is_ccw(self.ptr, byref(ret)):
|
||||
raise GEOSException(
|
||||
'Error encountered in GEOS C function "%s".' % capi.cs_is_ccw.func_name
|
||||
)
|
||||
return ret.value == 1
|
||||
@@ -0,0 +1,4 @@
|
||||
class GEOSException(Exception):
|
||||
"The base GEOS exception, indicates a GEOS-related error."
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,33 @@
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry, hex_regex, wkt_regex
|
||||
|
||||
|
||||
def fromfile(file_h):
|
||||
"""
|
||||
Given a string file name, returns a GEOSGeometry. The file may contain WKB,
|
||||
WKT, or HEX.
|
||||
"""
|
||||
# If given a file name, get a real handle.
|
||||
if isinstance(file_h, str):
|
||||
with open(file_h, "rb") as file_h:
|
||||
buf = file_h.read()
|
||||
else:
|
||||
buf = file_h.read()
|
||||
|
||||
# If we get WKB need to wrap in memoryview(), so run through regexes.
|
||||
if isinstance(buf, bytes):
|
||||
try:
|
||||
decoded = buf.decode()
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
else:
|
||||
if wkt_regex.match(decoded) or hex_regex.match(decoded):
|
||||
return GEOSGeometry(decoded)
|
||||
else:
|
||||
return GEOSGeometry(buf)
|
||||
|
||||
return GEOSGeometry(memoryview(buf))
|
||||
|
||||
|
||||
def fromstr(string, **kwargs):
|
||||
"Given a string value, return a GEOSGeometry object."
|
||||
return GEOSGeometry(string, **kwargs)
|
||||
@@ -0,0 +1,780 @@
|
||||
"""
|
||||
This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
|
||||
inherit from this object.
|
||||
"""
|
||||
|
||||
import re
|
||||
from ctypes import addressof, byref, c_double
|
||||
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.geometry import hex_regex, json_regex, wkt_regex
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR, geos_version_tuple
|
||||
from django.contrib.gis.geos.mutable_list import ListMixin
|
||||
from django.contrib.gis.geos.prepared import PreparedGeometry
|
||||
from django.contrib.gis.geos.prototypes.io import ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
|
||||
|
||||
class GEOSGeometryBase(GEOSBase):
|
||||
_GEOS_CLASSES = None
|
||||
|
||||
ptr_type = GEOM_PTR
|
||||
destructor = capi.destroy_geom
|
||||
has_cs = False # Only Point, LineString, LinearRing have coordinate sequences
|
||||
|
||||
def __init__(self, ptr, cls):
|
||||
self._ptr = ptr
|
||||
|
||||
# Setting the class type (e.g., Point, Polygon, etc.)
|
||||
if type(self) in (GEOSGeometryBase, GEOSGeometry):
|
||||
if cls is None:
|
||||
if GEOSGeometryBase._GEOS_CLASSES is None:
|
||||
# Inner imports avoid import conflicts with GEOSGeometry.
|
||||
from .collections import (
|
||||
GeometryCollection,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
)
|
||||
from .linestring import LinearRing, LineString
|
||||
from .point import Point
|
||||
from .polygon import Polygon
|
||||
|
||||
GEOSGeometryBase._GEOS_CLASSES = {
|
||||
0: Point,
|
||||
1: LineString,
|
||||
2: LinearRing,
|
||||
3: Polygon,
|
||||
4: MultiPoint,
|
||||
5: MultiLineString,
|
||||
6: MultiPolygon,
|
||||
7: GeometryCollection,
|
||||
}
|
||||
cls = GEOSGeometryBase._GEOS_CLASSES[self.geom_typeid]
|
||||
self.__class__ = cls
|
||||
self._post_init()
|
||||
|
||||
def _post_init(self):
|
||||
"Perform post-initialization setup."
|
||||
# Setting the coordinate sequence for the geometry (will be None on
|
||||
# geometries that do not have coordinate sequences)
|
||||
self._cs = (
|
||||
GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz) if self.has_cs else None
|
||||
)
|
||||
|
||||
def __copy__(self):
|
||||
"""
|
||||
Return a clone because the copy of a GEOSGeometry may contain an
|
||||
invalid pointer location if the original is garbage collected.
|
||||
"""
|
||||
return self.clone()
|
||||
|
||||
def __deepcopy__(self, memodict):
|
||||
"""
|
||||
The `deepcopy` routine is used by the `Node` class of django.utils.tree;
|
||||
thus, the protocol routine needs to be implemented to return correct
|
||||
copies (clones) of these GEOS objects, which use C pointers.
|
||||
"""
|
||||
return self.clone()
|
||||
|
||||
def __str__(self):
|
||||
"EWKT is used for the string representation."
|
||||
return self.ewkt
|
||||
|
||||
def __repr__(self):
|
||||
"Short-hand representation because WKT may be very large."
|
||||
return "<%s object at %s>" % (self.geom_type, hex(addressof(self.ptr)))
|
||||
|
||||
# Pickling support
|
||||
def _to_pickle_wkb(self):
|
||||
return bytes(self.wkb)
|
||||
|
||||
def _from_pickle_wkb(self, wkb):
|
||||
return wkb_r().read(memoryview(wkb))
|
||||
|
||||
def __getstate__(self):
|
||||
# The pickled state is simply a tuple of the WKB (in string form)
|
||||
# and the SRID.
|
||||
return self._to_pickle_wkb(), self.srid
|
||||
|
||||
def __setstate__(self, state):
|
||||
# Instantiating from the tuple state that was pickled.
|
||||
wkb, srid = state
|
||||
ptr = self._from_pickle_wkb(wkb)
|
||||
if not ptr:
|
||||
raise GEOSException("Invalid Geometry loaded from pickled state.")
|
||||
self.ptr = ptr
|
||||
self._post_init()
|
||||
self.srid = srid
|
||||
|
||||
@classmethod
|
||||
def _from_wkb(cls, wkb):
|
||||
return wkb_r().read(wkb)
|
||||
|
||||
@staticmethod
|
||||
def from_ewkt(ewkt):
|
||||
ewkt = force_bytes(ewkt)
|
||||
srid = None
|
||||
parts = ewkt.split(b";", 1)
|
||||
if len(parts) == 2:
|
||||
srid_part, wkt = parts
|
||||
match = re.match(rb"SRID=(?P<srid>\-?\d+)", srid_part)
|
||||
if not match:
|
||||
raise ValueError("EWKT has invalid SRID part.")
|
||||
srid = int(match["srid"])
|
||||
else:
|
||||
wkt = ewkt
|
||||
if not wkt:
|
||||
raise ValueError("Expected WKT but got an empty string.")
|
||||
return GEOSGeometry(GEOSGeometry._from_wkt(wkt), srid=srid)
|
||||
|
||||
@staticmethod
|
||||
def _from_wkt(wkt):
|
||||
return wkt_r().read(wkt)
|
||||
|
||||
@classmethod
|
||||
def from_gml(cls, gml_string):
|
||||
return gdal.OGRGeometry.from_gml(gml_string).geos
|
||||
|
||||
# Comparison operators
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Equivalence testing, a Geometry may be compared with another Geometry
|
||||
or an EWKT representation.
|
||||
"""
|
||||
if isinstance(other, str):
|
||||
try:
|
||||
other = GEOSGeometry.from_ewkt(other)
|
||||
except (ValueError, GEOSException):
|
||||
return False
|
||||
return (
|
||||
isinstance(other, GEOSGeometry)
|
||||
and self.srid == other.srid
|
||||
and self.equals_exact(other)
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.srid, self.wkt))
|
||||
|
||||
# ### Geometry set-like operations ###
|
||||
# Thanks to Sean Gillies for inspiration:
|
||||
# http://lists.gispython.org/pipermail/community/2007-July/001034.html
|
||||
# g = g1 | g2
|
||||
def __or__(self, other):
|
||||
"Return the union of this Geometry and the other."
|
||||
return self.union(other)
|
||||
|
||||
# g = g1 & g2
|
||||
def __and__(self, other):
|
||||
"Return the intersection of this Geometry and the other."
|
||||
return self.intersection(other)
|
||||
|
||||
# g = g1 - g2
|
||||
def __sub__(self, other):
|
||||
"Return the difference this Geometry and the other."
|
||||
return self.difference(other)
|
||||
|
||||
# g = g1 ^ g2
|
||||
def __xor__(self, other):
|
||||
"Return the symmetric difference of this Geometry and the other."
|
||||
return self.sym_difference(other)
|
||||
|
||||
# #### Coordinate Sequence Routines ####
|
||||
@property
|
||||
def coord_seq(self):
|
||||
"Return a clone of the coordinate sequence for this Geometry."
|
||||
if self.has_cs:
|
||||
return self._cs.clone()
|
||||
|
||||
# #### Geometry Info ####
|
||||
@property
|
||||
def geom_type(self):
|
||||
"Return a string representing the Geometry type, e.g. 'Polygon'"
|
||||
return capi.geos_type(self.ptr).decode()
|
||||
|
||||
@property
|
||||
def geom_typeid(self):
|
||||
"Return an integer representing the Geometry type."
|
||||
return capi.geos_typeid(self.ptr)
|
||||
|
||||
@property
|
||||
def num_geom(self):
|
||||
"Return the number of geometries in the Geometry."
|
||||
return capi.get_num_geoms(self.ptr)
|
||||
|
||||
@property
|
||||
def num_coords(self):
|
||||
"Return the number of coordinates in the Geometry."
|
||||
return capi.get_num_coords(self.ptr)
|
||||
|
||||
@property
|
||||
def num_points(self):
|
||||
"Return the number points, or coordinates, in the Geometry."
|
||||
return self.num_coords
|
||||
|
||||
@property
|
||||
def dims(self):
|
||||
"Return the dimension of this Geometry (0=point, 1=line, 2=surface)."
|
||||
return capi.get_dims(self.ptr)
|
||||
|
||||
def normalize(self, clone=False):
|
||||
"""
|
||||
Convert this Geometry to normal form (or canonical form).
|
||||
If the `clone` keyword is set, then the geometry is not modified and a
|
||||
normalized clone of the geometry is returned instead.
|
||||
"""
|
||||
if clone:
|
||||
clone = self.clone()
|
||||
capi.geos_normalize(clone.ptr)
|
||||
return clone
|
||||
capi.geos_normalize(self.ptr)
|
||||
|
||||
def make_valid(self):
|
||||
"""
|
||||
Attempt to create a valid representation of a given invalid geometry
|
||||
without losing any of the input vertices.
|
||||
"""
|
||||
return GEOSGeometry(capi.geos_makevalid(self.ptr), srid=self.srid)
|
||||
|
||||
# #### Unary predicates ####
|
||||
@property
|
||||
def empty(self):
|
||||
"""
|
||||
Return a boolean indicating whether the set of points in this Geometry
|
||||
are empty.
|
||||
"""
|
||||
return capi.geos_isempty(self.ptr)
|
||||
|
||||
@property
|
||||
def hasz(self):
|
||||
"Return whether the geometry has a 3D dimension."
|
||||
return capi.geos_hasz(self.ptr)
|
||||
|
||||
@property
|
||||
def ring(self):
|
||||
"Return whether or not the geometry is a ring."
|
||||
return capi.geos_isring(self.ptr)
|
||||
|
||||
@property
|
||||
def simple(self):
|
||||
"Return false if the Geometry isn't simple."
|
||||
return capi.geos_issimple(self.ptr)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"Test the validity of this Geometry."
|
||||
return capi.geos_isvalid(self.ptr)
|
||||
|
||||
@property
|
||||
def valid_reason(self):
|
||||
"""
|
||||
Return a string containing the reason for any invalidity.
|
||||
"""
|
||||
return capi.geos_isvalidreason(self.ptr).decode()
|
||||
|
||||
# #### Binary predicates. ####
|
||||
def contains(self, other):
|
||||
"Return true if other.within(this) returns true."
|
||||
return capi.geos_contains(self.ptr, other.ptr)
|
||||
|
||||
def covers(self, other):
|
||||
"""
|
||||
Return True if the DE-9IM Intersection Matrix for the two geometries is
|
||||
T*****FF*, *T****FF*, ***T**FF*, or ****T*FF*. If either geometry is
|
||||
empty, return False.
|
||||
"""
|
||||
return capi.geos_covers(self.ptr, other.ptr)
|
||||
|
||||
def crosses(self, other):
|
||||
"""
|
||||
Return true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*T****** (for a point and a curve,a point and an area or a line and
|
||||
an area) 0******** (for two curves).
|
||||
"""
|
||||
return capi.geos_crosses(self.ptr, other.ptr)
|
||||
|
||||
def disjoint(self, other):
|
||||
"""
|
||||
Return true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FF*FF****.
|
||||
"""
|
||||
return capi.geos_disjoint(self.ptr, other.ptr)
|
||||
|
||||
def equals(self, other):
|
||||
"""
|
||||
Return true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**FFF*.
|
||||
"""
|
||||
return capi.geos_equals(self.ptr, other.ptr)
|
||||
|
||||
def equals_exact(self, other, tolerance=0):
|
||||
"""
|
||||
Return true if the two Geometries are exactly equal, up to a
|
||||
specified tolerance.
|
||||
"""
|
||||
return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))
|
||||
|
||||
def equals_identical(self, other):
|
||||
"""
|
||||
Return true if the two Geometries are point-wise equivalent.
|
||||
"""
|
||||
if geos_version_tuple() < (3, 12):
|
||||
raise GEOSException(
|
||||
"GEOSGeometry.equals_identical() requires GEOS >= 3.12.0."
|
||||
)
|
||||
return capi.geos_equalsidentical(self.ptr, other.ptr)
|
||||
|
||||
def intersects(self, other):
|
||||
"Return true if disjoint return false."
|
||||
return capi.geos_intersects(self.ptr, other.ptr)
|
||||
|
||||
def overlaps(self, other):
|
||||
"""
|
||||
Return true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
|
||||
"""
|
||||
return capi.geos_overlaps(self.ptr, other.ptr)
|
||||
|
||||
def relate_pattern(self, other, pattern):
|
||||
"""
|
||||
Return true if the elements in the DE-9IM intersection matrix for the
|
||||
two Geometries match the elements in pattern.
|
||||
"""
|
||||
if not isinstance(pattern, str) or len(pattern) > 9:
|
||||
raise GEOSException("invalid intersection matrix pattern")
|
||||
return capi.geos_relatepattern(self.ptr, other.ptr, force_bytes(pattern))
|
||||
|
||||
def touches(self, other):
|
||||
"""
|
||||
Return true if the DE-9IM intersection matrix for the two Geometries
|
||||
is FT*******, F**T***** or F***T****.
|
||||
"""
|
||||
return capi.geos_touches(self.ptr, other.ptr)
|
||||
|
||||
def within(self, other):
|
||||
"""
|
||||
Return true if the DE-9IM intersection matrix for the two Geometries
|
||||
is T*F**F***.
|
||||
"""
|
||||
return capi.geos_within(self.ptr, other.ptr)
|
||||
|
||||
# #### SRID Routines ####
|
||||
@property
|
||||
def srid(self):
|
||||
"Get the SRID for the geometry. Return None if no SRID is set."
|
||||
s = capi.geos_get_srid(self.ptr)
|
||||
if s == 0:
|
||||
return None
|
||||
else:
|
||||
return s
|
||||
|
||||
@srid.setter
|
||||
def srid(self, srid):
|
||||
"Set the SRID for the geometry."
|
||||
capi.geos_set_srid(self.ptr, 0 if srid is None else srid)
|
||||
|
||||
# #### Output Routines ####
|
||||
@property
|
||||
def ewkt(self):
|
||||
"""
|
||||
Return the EWKT (SRID + WKT) of the Geometry.
|
||||
"""
|
||||
srid = self.srid
|
||||
return "SRID=%s;%s" % (srid, self.wkt) if srid else self.wkt
|
||||
|
||||
@property
|
||||
def wkt(self):
|
||||
"Return the WKT (Well-Known Text) representation of this Geometry."
|
||||
return wkt_w(dim=3 if self.hasz else 2, trim=True).write(self).decode()
|
||||
|
||||
@property
|
||||
def hex(self):
|
||||
"""
|
||||
Return the WKB of this Geometry in hexadecimal form. Please note
|
||||
that the SRID is not included in this representation because it is not
|
||||
a part of the OGC specification (use the `hexewkb` property instead).
|
||||
"""
|
||||
# A possible faster, all-python, implementation:
|
||||
# str(self.wkb).encode('hex')
|
||||
return wkb_w(dim=3 if self.hasz else 2).write_hex(self)
|
||||
|
||||
@property
|
||||
def hexewkb(self):
|
||||
"""
|
||||
Return the EWKB of this Geometry in hexadecimal form. This is an
|
||||
extension of the WKB specification that includes SRID value that are
|
||||
a part of this geometry.
|
||||
"""
|
||||
return ewkb_w(dim=3 if self.hasz else 2).write_hex(self)
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""
|
||||
Return GeoJSON representation of this Geometry.
|
||||
"""
|
||||
return self.ogr.json
|
||||
|
||||
geojson = json
|
||||
|
||||
@property
|
||||
def wkb(self):
|
||||
"""
|
||||
Return the WKB (Well-Known Binary) representation of this Geometry
|
||||
as a Python memoryview. SRID and Z values are not included, use the
|
||||
`ewkb` property instead.
|
||||
"""
|
||||
return wkb_w(3 if self.hasz else 2).write(self)
|
||||
|
||||
@property
|
||||
def ewkb(self):
|
||||
"""
|
||||
Return the EWKB representation of this Geometry as a Python memoryview.
|
||||
This is an extension of the WKB specification that includes any SRID
|
||||
value that are a part of this geometry.
|
||||
"""
|
||||
return ewkb_w(3 if self.hasz else 2).write(self)
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Return the KML representation of this Geometry."
|
||||
gtype = self.geom_type
|
||||
return "<%s>%s</%s>" % (gtype, self.coord_seq.kml, gtype)
|
||||
|
||||
@property
|
||||
def prepared(self):
|
||||
"""
|
||||
Return a PreparedGeometry corresponding to this geometry -- it is
|
||||
optimized for the contains, intersects, and covers operations.
|
||||
"""
|
||||
return PreparedGeometry(self)
|
||||
|
||||
# #### GDAL-specific output routines ####
|
||||
def _ogr_ptr(self):
|
||||
return gdal.OGRGeometry._from_wkb(self.wkb)
|
||||
|
||||
@property
|
||||
def ogr(self):
|
||||
"Return the OGR Geometry for this Geometry."
|
||||
return gdal.OGRGeometry(self._ogr_ptr(), self.srs)
|
||||
|
||||
@property
|
||||
def srs(self):
|
||||
"Return the OSR SpatialReference for SRID of this Geometry."
|
||||
if self.srid:
|
||||
try:
|
||||
return gdal.SpatialReference(self.srid)
|
||||
except (gdal.GDALException, gdal.SRSException):
|
||||
pass
|
||||
return None
|
||||
|
||||
@property
|
||||
def crs(self):
|
||||
"Alias for `srs` property."
|
||||
return self.srs
|
||||
|
||||
def transform(self, ct, clone=False):
|
||||
"""
|
||||
Requires GDAL. Transform the geometry according to the given
|
||||
transformation object, which may be an integer SRID, and WKT or
|
||||
PROJ string. By default, transform the geometry in-place and return
|
||||
nothing. However if the `clone` keyword is set, don't modify the
|
||||
geometry and return a transformed clone instead.
|
||||
"""
|
||||
srid = self.srid
|
||||
|
||||
if ct == srid:
|
||||
# short-circuit where source & dest SRIDs match
|
||||
if clone:
|
||||
return self.clone()
|
||||
else:
|
||||
return
|
||||
|
||||
if isinstance(ct, gdal.CoordTransform):
|
||||
# We don't care about SRID because CoordTransform presupposes
|
||||
# source SRS.
|
||||
srid = None
|
||||
elif srid is None or srid < 0:
|
||||
raise GEOSException("Calling transform() with no SRID set is not supported")
|
||||
|
||||
# Creating an OGR Geometry, which is then transformed.
|
||||
g = gdal.OGRGeometry(self._ogr_ptr(), srid)
|
||||
g.transform(ct)
|
||||
# Getting a new GEOS pointer
|
||||
ptr = g._geos_ptr()
|
||||
if clone:
|
||||
# User wants a cloned transformed geometry returned.
|
||||
return GEOSGeometry(ptr, srid=g.srid)
|
||||
if ptr:
|
||||
# Reassigning pointer, and performing post-initialization setup
|
||||
# again due to the reassignment.
|
||||
capi.destroy_geom(self.ptr)
|
||||
self.ptr = ptr
|
||||
self._post_init()
|
||||
self.srid = g.srid
|
||||
else:
|
||||
raise GEOSException("Transformed WKB was invalid.")
|
||||
|
||||
# #### Topology Routines ####
|
||||
def _topology(self, gptr):
|
||||
"Return Geometry from the given pointer."
|
||||
return GEOSGeometry(gptr, srid=self.srid)
|
||||
|
||||
@property
|
||||
def boundary(self):
|
||||
"Return the boundary as a newly allocated Geometry object."
|
||||
return self._topology(capi.geos_boundary(self.ptr))
|
||||
|
||||
def buffer(self, width, quadsegs=8):
|
||||
"""
|
||||
Return a geometry that represents all points whose distance from this
|
||||
Geometry is less than or equal to distance. Calculations are in the
|
||||
Spatial Reference System of this Geometry. The optional third parameter sets
|
||||
the number of segment used to approximate a quarter circle (defaults to 8).
|
||||
(Text from PostGIS documentation at ch. 6.1.3)
|
||||
"""
|
||||
return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))
|
||||
|
||||
def buffer_with_style(
|
||||
self, width, quadsegs=8, end_cap_style=1, join_style=1, mitre_limit=5.0
|
||||
):
|
||||
"""
|
||||
Same as buffer() but allows customizing the style of the memoryview.
|
||||
|
||||
End cap style can be round (1), flat (2), or square (3).
|
||||
Join style can be round (1), mitre (2), or bevel (3).
|
||||
Mitre ratio limit only affects mitered join style.
|
||||
"""
|
||||
return self._topology(
|
||||
capi.geos_bufferwithstyle(
|
||||
self.ptr, width, quadsegs, end_cap_style, join_style, mitre_limit
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def centroid(self):
|
||||
"""
|
||||
The centroid is equal to the centroid of the set of component Geometries
|
||||
of highest dimension (since the lower-dimension geometries contribute zero
|
||||
"weight" to the centroid).
|
||||
"""
|
||||
return self._topology(capi.geos_centroid(self.ptr))
|
||||
|
||||
@property
|
||||
def convex_hull(self):
|
||||
"""
|
||||
Return the smallest convex Polygon that contains all the points
|
||||
in the Geometry.
|
||||
"""
|
||||
return self._topology(capi.geos_convexhull(self.ptr))
|
||||
|
||||
def difference(self, other):
|
||||
"""
|
||||
Return a Geometry representing the points making up this Geometry
|
||||
that do not make up other.
|
||||
"""
|
||||
return self._topology(capi.geos_difference(self.ptr, other.ptr))
|
||||
|
||||
@property
|
||||
def envelope(self):
|
||||
"Return the envelope for this geometry (a polygon)."
|
||||
return self._topology(capi.geos_envelope(self.ptr))
|
||||
|
||||
def intersection(self, other):
|
||||
"Return a Geometry representing the points shared by this Geometry and other."
|
||||
return self._topology(capi.geos_intersection(self.ptr, other.ptr))
|
||||
|
||||
@property
|
||||
def point_on_surface(self):
|
||||
"Compute an interior point of this Geometry."
|
||||
return self._topology(capi.geos_pointonsurface(self.ptr))
|
||||
|
||||
def relate(self, other):
|
||||
"Return the DE-9IM intersection matrix for this Geometry and the other."
|
||||
return capi.geos_relate(self.ptr, other.ptr).decode()
|
||||
|
||||
def simplify(self, tolerance=0.0, preserve_topology=False):
|
||||
"""
|
||||
Return the Geometry, simplified using the Douglas-Peucker algorithm
|
||||
to the specified tolerance (higher tolerance => less points). If no
|
||||
tolerance provided, defaults to 0.
|
||||
|
||||
By default, don't preserve topology - e.g. polygons can be split,
|
||||
collapse to lines or disappear holes can be created or disappear, and
|
||||
lines can cross. By specifying preserve_topology=True, the result will
|
||||
have the same dimension and number of components as the input. This is
|
||||
significantly slower.
|
||||
"""
|
||||
if preserve_topology:
|
||||
return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
|
||||
else:
|
||||
return self._topology(capi.geos_simplify(self.ptr, tolerance))
|
||||
|
||||
def sym_difference(self, other):
|
||||
"""
|
||||
Return a set combining the points in this Geometry not in other,
|
||||
and the points in other not in this Geometry.
|
||||
"""
|
||||
return self._topology(capi.geos_symdifference(self.ptr, other.ptr))
|
||||
|
||||
@property
|
||||
def unary_union(self):
|
||||
"Return the union of all the elements of this geometry."
|
||||
return self._topology(capi.geos_unary_union(self.ptr))
|
||||
|
||||
def union(self, other):
|
||||
"Return a Geometry representing all the points in this Geometry and other."
|
||||
return self._topology(capi.geos_union(self.ptr, other.ptr))
|
||||
|
||||
# #### Other Routines ####
|
||||
@property
|
||||
def area(self):
|
||||
"Return the area of the Geometry."
|
||||
return capi.geos_area(self.ptr, byref(c_double()))
|
||||
|
||||
def distance(self, other):
|
||||
"""
|
||||
Return the distance between the closest points on this Geometry
|
||||
and the other. Units will be in those of the coordinate system of
|
||||
the Geometry.
|
||||
"""
|
||||
if not isinstance(other, GEOSGeometry):
|
||||
raise TypeError("distance() works only on other GEOS Geometries.")
|
||||
return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))
|
||||
|
||||
@property
|
||||
def extent(self):
|
||||
"""
|
||||
Return the extent of this geometry as a 4-tuple, consisting of
|
||||
(xmin, ymin, xmax, ymax).
|
||||
"""
|
||||
from .point import Point
|
||||
|
||||
env = self.envelope
|
||||
if isinstance(env, Point):
|
||||
xmin, ymin = env.tuple
|
||||
xmax, ymax = xmin, ymin
|
||||
else:
|
||||
xmin, ymin = env[0][0]
|
||||
xmax, ymax = env[0][2]
|
||||
return (xmin, ymin, xmax, ymax)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""
|
||||
Return the length of this Geometry (e.g., 0 for point, or the
|
||||
circumference of a Polygon).
|
||||
"""
|
||||
return capi.geos_length(self.ptr, byref(c_double()))
|
||||
|
||||
def clone(self):
|
||||
"Clone this Geometry."
|
||||
return GEOSGeometry(capi.geom_clone(self.ptr))
|
||||
|
||||
|
||||
class LinearGeometryMixin:
|
||||
"""
|
||||
Used for LineString and MultiLineString.
|
||||
"""
|
||||
|
||||
def interpolate(self, distance):
|
||||
return self._topology(capi.geos_interpolate(self.ptr, distance))
|
||||
|
||||
def interpolate_normalized(self, distance):
|
||||
return self._topology(capi.geos_interpolate_normalized(self.ptr, distance))
|
||||
|
||||
def project(self, point):
|
||||
from .point import Point
|
||||
|
||||
if not isinstance(point, Point):
|
||||
raise TypeError("locate_point argument must be a Point")
|
||||
return capi.geos_project(self.ptr, point.ptr)
|
||||
|
||||
def project_normalized(self, point):
|
||||
from .point import Point
|
||||
|
||||
if not isinstance(point, Point):
|
||||
raise TypeError("locate_point argument must be a Point")
|
||||
return capi.geos_project_normalized(self.ptr, point.ptr)
|
||||
|
||||
@property
|
||||
def merged(self):
|
||||
"""
|
||||
Return the line merge of this Geometry.
|
||||
"""
|
||||
return self._topology(capi.geos_linemerge(self.ptr))
|
||||
|
||||
@property
|
||||
def closed(self):
|
||||
"""
|
||||
Return whether or not this Geometry is closed.
|
||||
"""
|
||||
return capi.geos_isclosed(self.ptr)
|
||||
|
||||
|
||||
@deconstructible
|
||||
class GEOSGeometry(GEOSGeometryBase, ListMixin):
|
||||
"A class that, generally, encapsulates a GEOS geometry."
|
||||
|
||||
def __init__(self, geo_input, srid=None):
|
||||
"""
|
||||
The base constructor for GEOS geometry objects. It may take the
|
||||
following inputs:
|
||||
|
||||
* strings:
|
||||
- WKT
|
||||
- HEXEWKB (a PostGIS-specific canonical form)
|
||||
- GeoJSON (requires GDAL)
|
||||
* memoryview:
|
||||
- WKB
|
||||
|
||||
The `srid` keyword specifies the Source Reference Identifier (SRID)
|
||||
number for this Geometry. If not provided, it defaults to None.
|
||||
"""
|
||||
input_srid = None
|
||||
if isinstance(geo_input, bytes):
|
||||
geo_input = force_str(geo_input)
|
||||
if isinstance(geo_input, str):
|
||||
wkt_m = wkt_regex.match(geo_input)
|
||||
if wkt_m:
|
||||
# Handle WKT input.
|
||||
if wkt_m["srid"]:
|
||||
input_srid = int(wkt_m["srid"])
|
||||
g = self._from_wkt(force_bytes(wkt_m["wkt"]))
|
||||
elif hex_regex.match(geo_input):
|
||||
# Handle HEXEWKB input.
|
||||
g = wkb_r().read(force_bytes(geo_input))
|
||||
elif json_regex.match(geo_input):
|
||||
# Handle GeoJSON input.
|
||||
ogr = gdal.OGRGeometry.from_json(geo_input)
|
||||
g = ogr._geos_ptr()
|
||||
input_srid = ogr.srid
|
||||
else:
|
||||
raise ValueError("String input unrecognized as WKT EWKT, and HEXEWKB.")
|
||||
elif isinstance(geo_input, GEOM_PTR):
|
||||
# When the input is a pointer to a geometry (GEOM_PTR).
|
||||
g = geo_input
|
||||
elif isinstance(geo_input, memoryview):
|
||||
# When the input is a memoryview (WKB).
|
||||
g = wkb_r().read(geo_input)
|
||||
elif isinstance(geo_input, GEOSGeometry):
|
||||
g = capi.geom_clone(geo_input.ptr)
|
||||
else:
|
||||
raise TypeError("Improper geometry input type: %s" % type(geo_input))
|
||||
|
||||
if not g:
|
||||
raise GEOSException("Could not initialize GEOS Geometry with given input.")
|
||||
|
||||
input_srid = input_srid or capi.geos_get_srid(g) or None
|
||||
if input_srid and srid and input_srid != srid:
|
||||
raise ValueError("Input geometry already has SRID: %d." % input_srid)
|
||||
|
||||
super().__init__(g, None)
|
||||
# Set the SRID, if given.
|
||||
srid = input_srid or srid
|
||||
if srid and isinstance(srid, int):
|
||||
self.srid = srid
|
||||
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Module that holds classes for performing I/O operations on GEOS geometry
|
||||
objects. Specifically, this has Python implementations of WKB/WKT
|
||||
reader and writer classes.
|
||||
"""
|
||||
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos.prototypes.io import (
|
||||
WKBWriter,
|
||||
WKTWriter,
|
||||
_WKBReader,
|
||||
_WKTReader,
|
||||
)
|
||||
|
||||
__all__ = ["WKBWriter", "WKTWriter", "WKBReader", "WKTReader"]
|
||||
|
||||
|
||||
# Public classes for (WKB|WKT)Reader, which return GEOSGeometry
|
||||
class WKBReader(_WKBReader):
|
||||
def read(self, wkb):
|
||||
"Return a GEOSGeometry for the given WKB buffer."
|
||||
return GEOSGeometry(super().read(wkb))
|
||||
|
||||
|
||||
class WKTReader(_WKTReader):
|
||||
def read(self, wkt):
|
||||
"Return a GEOSGeometry for the given WKT string."
|
||||
return GEOSGeometry(super().read(wkt))
|
||||
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
This module houses the ctypes initialization procedures, as well
|
||||
as the notice and error handler function callbacks (get called
|
||||
when an error occurs in GEOS).
|
||||
|
||||
This module also houses GEOS Pointer utilities, including
|
||||
get_pointer_arr(), and GEOM_PTR.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from ctypes import CDLL, CFUNCTYPE, POINTER, Structure, c_char_p
|
||||
from ctypes.util import find_library
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.functional import SimpleLazyObject, cached_property
|
||||
from django.utils.version import get_version_tuple
|
||||
|
||||
logger = logging.getLogger("django.contrib.gis")
|
||||
|
||||
|
||||
def load_geos():
|
||||
# Custom library path set?
|
||||
try:
|
||||
from django.conf import settings
|
||||
|
||||
lib_path = settings.GEOS_LIBRARY_PATH
|
||||
except (AttributeError, ImportError, ImproperlyConfigured, OSError):
|
||||
lib_path = None
|
||||
|
||||
# Setting the appropriate names for the GEOS-C library.
|
||||
if lib_path:
|
||||
lib_names = None
|
||||
elif os.name == "nt":
|
||||
# Windows NT libraries
|
||||
lib_names = ["geos_c", "libgeos_c-1"]
|
||||
elif os.name == "posix":
|
||||
# *NIX libraries
|
||||
lib_names = ["geos_c", "GEOS"]
|
||||
else:
|
||||
raise ImportError('Unsupported OS "%s"' % os.name)
|
||||
|
||||
# Using the ctypes `find_library` utility to find the path to the GEOS
|
||||
# shared library. This is better than manually specifying each library name
|
||||
# and extension (e.g., libgeos_c.[so|so.1|dylib].).
|
||||
if lib_names:
|
||||
for lib_name in lib_names:
|
||||
lib_path = find_library(lib_name)
|
||||
if lib_path is not None:
|
||||
break
|
||||
|
||||
# No GEOS library could be found.
|
||||
if lib_path is None:
|
||||
raise ImportError(
|
||||
'Could not find the GEOS library (tried "%s"). '
|
||||
"Try setting GEOS_LIBRARY_PATH in your settings." % '", "'.join(lib_names)
|
||||
)
|
||||
# Getting the GEOS C library. The C interface (CDLL) is used for
|
||||
# both *NIX and Windows.
|
||||
# See the GEOS C API source code for more details on the library function calls:
|
||||
# https://libgeos.org/doxygen/geos__c_8h_source.html
|
||||
_lgeos = CDLL(lib_path)
|
||||
# Here we set up the prototypes for the initGEOS_r and finishGEOS_r
|
||||
# routines. These functions aren't actually called until they are
|
||||
# attached to a GEOS context handle -- this actually occurs in
|
||||
# geos/prototypes/threadsafe.py.
|
||||
_lgeos.initGEOS_r.restype = CONTEXT_PTR
|
||||
_lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
|
||||
# Set restype for compatibility across 32 and 64-bit platforms.
|
||||
_lgeos.GEOSversion.restype = c_char_p
|
||||
return _lgeos
|
||||
|
||||
|
||||
# The notice and error handler C function callback definitions.
|
||||
# Supposed to mimic the GEOS message handler (C below):
|
||||
# typedef void (*GEOSMessageHandler)(const char *fmt, ...);
|
||||
NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
|
||||
|
||||
|
||||
def notice_h(fmt, lst):
|
||||
fmt, lst = fmt.decode(), lst.decode()
|
||||
try:
|
||||
warn_msg = fmt % lst
|
||||
except TypeError:
|
||||
warn_msg = fmt
|
||||
logger.warning("GEOS_NOTICE: %s\n", warn_msg)
|
||||
|
||||
|
||||
notice_h = NOTICEFUNC(notice_h)
|
||||
|
||||
ERRORFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
|
||||
|
||||
|
||||
def error_h(fmt, lst):
|
||||
fmt, lst = fmt.decode(), lst.decode()
|
||||
try:
|
||||
err_msg = fmt % lst
|
||||
except TypeError:
|
||||
err_msg = fmt
|
||||
logger.error("GEOS_ERROR: %s\n", err_msg)
|
||||
|
||||
|
||||
error_h = ERRORFUNC(error_h)
|
||||
|
||||
# #### GEOS Geometry C data structures, and utility functions. ####
|
||||
|
||||
|
||||
# Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
|
||||
class GEOSGeom_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class GEOSPrepGeom_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class GEOSCoordSeq_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class GEOSContextHandle_t(Structure):
|
||||
pass
|
||||
|
||||
|
||||
# Pointers to opaque GEOS geometry structures.
|
||||
GEOM_PTR = POINTER(GEOSGeom_t)
|
||||
PREPGEOM_PTR = POINTER(GEOSPrepGeom_t)
|
||||
CS_PTR = POINTER(GEOSCoordSeq_t)
|
||||
CONTEXT_PTR = POINTER(GEOSContextHandle_t)
|
||||
|
||||
|
||||
lgeos = SimpleLazyObject(load_geos)
|
||||
|
||||
|
||||
class GEOSFuncFactory:
|
||||
"""
|
||||
Lazy loading of GEOS functions.
|
||||
"""
|
||||
|
||||
argtypes = None
|
||||
restype = None
|
||||
errcheck = None
|
||||
|
||||
def __init__(self, func_name, *, restype=None, errcheck=None, argtypes=None):
|
||||
self.func_name = func_name
|
||||
if restype is not None:
|
||||
self.restype = restype
|
||||
if errcheck is not None:
|
||||
self.errcheck = errcheck
|
||||
if argtypes is not None:
|
||||
self.argtypes = argtypes
|
||||
|
||||
def __call__(self, *args):
|
||||
return self.func(*args)
|
||||
|
||||
@cached_property
|
||||
def func(self):
|
||||
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
|
||||
|
||||
func = GEOSFunc(self.func_name)
|
||||
func.argtypes = self.argtypes or []
|
||||
func.restype = self.restype
|
||||
if self.errcheck:
|
||||
func.errcheck = self.errcheck
|
||||
return func
|
||||
|
||||
|
||||
def geos_version():
|
||||
"""Return the string version of the GEOS library."""
|
||||
return lgeos.GEOSversion()
|
||||
|
||||
|
||||
def geos_version_tuple():
|
||||
"""Return the GEOS version as a tuple (major, minor, subminor)."""
|
||||
return get_version_tuple(geos_version().decode())
|
||||
@@ -0,0 +1,193 @@
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin
|
||||
from django.contrib.gis.geos.point import Point
|
||||
from django.contrib.gis.shortcuts import numpy
|
||||
|
||||
|
||||
class LineString(LinearGeometryMixin, GEOSGeometry):
|
||||
_init_func = capi.create_linestring
|
||||
_minlength = 2
|
||||
has_cs = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize on the given sequence -- may take lists, tuples, NumPy arrays
|
||||
of X,Y pairs, or Point objects. If Point objects are used, ownership is
|
||||
_not_ transferred to the LineString object.
|
||||
|
||||
Examples:
|
||||
ls = LineString((1, 1), (2, 2))
|
||||
ls = LineString([(1, 1), (2, 2)])
|
||||
ls = LineString(array([(1, 1), (2, 2)]))
|
||||
ls = LineString(Point(1, 1), Point(2, 2))
|
||||
"""
|
||||
# If only one argument provided, set the coords array appropriately
|
||||
if len(args) == 1:
|
||||
coords = args[0]
|
||||
else:
|
||||
coords = args
|
||||
|
||||
if not (
|
||||
isinstance(coords, (tuple, list))
|
||||
or numpy
|
||||
and isinstance(coords, numpy.ndarray)
|
||||
):
|
||||
raise TypeError("Invalid initialization input for LineStrings.")
|
||||
|
||||
# If SRID was passed in with the keyword arguments
|
||||
srid = kwargs.get("srid")
|
||||
|
||||
ncoords = len(coords)
|
||||
if not ncoords:
|
||||
super().__init__(self._init_func(None), srid=srid)
|
||||
return
|
||||
|
||||
if ncoords < self._minlength:
|
||||
raise ValueError(
|
||||
"%s requires at least %d points, got %s."
|
||||
% (
|
||||
self.__class__.__name__,
|
||||
self._minlength,
|
||||
ncoords,
|
||||
)
|
||||
)
|
||||
|
||||
numpy_coords = not isinstance(coords, (tuple, list))
|
||||
if numpy_coords:
|
||||
shape = coords.shape # Using numpy's shape.
|
||||
if len(shape) != 2:
|
||||
raise TypeError("Too many dimensions.")
|
||||
self._checkdim(shape[1])
|
||||
ndim = shape[1]
|
||||
else:
|
||||
# Getting the number of coords and the number of dimensions -- which
|
||||
# must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
|
||||
ndim = None
|
||||
# Incrementing through each of the coordinates and verifying
|
||||
for coord in coords:
|
||||
if not isinstance(coord, (tuple, list, Point)):
|
||||
raise TypeError(
|
||||
"Each coordinate should be a sequence (list or tuple)"
|
||||
)
|
||||
|
||||
if ndim is None:
|
||||
ndim = len(coord)
|
||||
self._checkdim(ndim)
|
||||
elif len(coord) != ndim:
|
||||
raise TypeError("Dimension mismatch.")
|
||||
|
||||
# Creating a coordinate sequence object because it is easier to
|
||||
# set the points using its methods.
|
||||
cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim == 3))
|
||||
point_setter = cs._set_point_3d if ndim == 3 else cs._set_point_2d
|
||||
|
||||
for i in range(ncoords):
|
||||
if numpy_coords:
|
||||
point_coords = coords[i, :]
|
||||
elif isinstance(coords[i], Point):
|
||||
point_coords = coords[i].tuple
|
||||
else:
|
||||
point_coords = coords[i]
|
||||
point_setter(i, point_coords)
|
||||
|
||||
# Calling the base geometry initialization with the returned pointer
|
||||
# from the function.
|
||||
super().__init__(self._init_func(cs.ptr), srid=srid)
|
||||
|
||||
def __iter__(self):
|
||||
"Allow iteration over this LineString."
|
||||
for i in range(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of points in this LineString."
|
||||
return len(self._cs)
|
||||
|
||||
def _get_single_external(self, index):
|
||||
return self._cs[index]
|
||||
|
||||
_get_single_internal = _get_single_external
|
||||
|
||||
def _set_list(self, length, items):
|
||||
ndim = self._cs.dims
|
||||
hasz = self._cs.hasz # I don't understand why these are different
|
||||
srid = self.srid
|
||||
|
||||
# create a new coordinate sequence and populate accordingly
|
||||
cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
|
||||
for i, c in enumerate(items):
|
||||
cs[i] = c
|
||||
|
||||
ptr = self._init_func(cs.ptr)
|
||||
if ptr:
|
||||
capi.destroy_geom(self.ptr)
|
||||
self.ptr = ptr
|
||||
if srid is not None:
|
||||
self.srid = srid
|
||||
self._post_init()
|
||||
else:
|
||||
# can this happen?
|
||||
raise GEOSException("Geometry resulting from slice deletion was invalid.")
|
||||
|
||||
def _set_single(self, index, value):
|
||||
self._cs[index] = value
|
||||
|
||||
def _checkdim(self, dim):
|
||||
if dim not in (2, 3):
|
||||
raise TypeError("Dimension mismatch.")
|
||||
|
||||
# #### Sequence Properties ####
|
||||
@property
|
||||
def tuple(self):
|
||||
"Return a tuple version of the geometry from the coordinate sequence."
|
||||
return self._cs.tuple
|
||||
|
||||
coords = tuple
|
||||
|
||||
def _listarr(self, func):
|
||||
"""
|
||||
Return a sequence (list) corresponding with the given function.
|
||||
Return a numpy array if possible.
|
||||
"""
|
||||
lst = [func(i) for i in range(len(self))]
|
||||
if numpy:
|
||||
return numpy.array(lst) # ARRRR!
|
||||
else:
|
||||
return lst
|
||||
|
||||
@property
|
||||
def array(self):
|
||||
"Return a numpy array for the LineString."
|
||||
return self._listarr(self._cs.__getitem__)
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
"Return a list or numpy array of the X variable."
|
||||
return self._listarr(self._cs.getX)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
"Return a list or numpy array of the Y variable."
|
||||
return self._listarr(self._cs.getY)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
"Return a list or numpy array of the Z variable."
|
||||
if not self.hasz:
|
||||
return None
|
||||
else:
|
||||
return self._listarr(self._cs.getZ)
|
||||
|
||||
|
||||
# LinearRings are LineStrings used within Polygons.
|
||||
class LinearRing(LineString):
|
||||
_minlength = 4
|
||||
_init_func = capi.create_linearring
|
||||
|
||||
@property
|
||||
def is_counterclockwise(self):
|
||||
if self.empty:
|
||||
raise ValueError("Orientation of an empty LinearRing cannot be determined.")
|
||||
return self._cs.is_counterclockwise
|
||||
@@ -0,0 +1,314 @@
|
||||
# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
|
||||
# Released under the New BSD license.
|
||||
"""
|
||||
This module contains a base type which provides list-style mutations
|
||||
without specific data storage methods.
|
||||
|
||||
See also http://static.aryehleib.com/oldsite/MutableLists.html
|
||||
|
||||
Author: Aryeh Leib Taurog.
|
||||
"""
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class ListMixin:
|
||||
"""
|
||||
A base class which provides complete list interface.
|
||||
Derived classes must call ListMixin's __init__() function
|
||||
and implement the following:
|
||||
|
||||
function _get_single_external(self, i):
|
||||
Return single item with index i for general use.
|
||||
The index i will always satisfy 0 <= i < len(self).
|
||||
|
||||
function _get_single_internal(self, i):
|
||||
Same as above, but for use within the class [Optional]
|
||||
Note that if _get_single_internal and _get_single_internal return
|
||||
different types of objects, _set_list must distinguish
|
||||
between the two and handle each appropriately.
|
||||
|
||||
function _set_list(self, length, items):
|
||||
Recreate the entire object.
|
||||
|
||||
NOTE: items may be a generator which calls _get_single_internal.
|
||||
Therefore, it is necessary to cache the values in a temporary:
|
||||
temp = list(items)
|
||||
before clobbering the original storage.
|
||||
|
||||
function _set_single(self, i, value):
|
||||
Set the single item at index i to value [Optional]
|
||||
If left undefined, all mutations will result in rebuilding
|
||||
the object using _set_list.
|
||||
|
||||
function __len__(self):
|
||||
Return the length
|
||||
|
||||
int _minlength:
|
||||
The minimum legal length [Optional]
|
||||
|
||||
int _maxlength:
|
||||
The maximum legal length [Optional]
|
||||
|
||||
type or tuple _allowed:
|
||||
A type or tuple of allowed item types [Optional]
|
||||
"""
|
||||
|
||||
_minlength = 0
|
||||
_maxlength = None
|
||||
|
||||
# ### Python initialization and special list interface methods ###
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not hasattr(self, "_get_single_internal"):
|
||||
self._get_single_internal = self._get_single_external
|
||||
|
||||
if not hasattr(self, "_set_single"):
|
||||
self._set_single = self._set_single_rebuild
|
||||
self._assign_extended_slice = self._assign_extended_slice_rebuild
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"Get the item(s) at the specified index/slice."
|
||||
if isinstance(index, slice):
|
||||
return [
|
||||
self._get_single_external(i) for i in range(*index.indices(len(self)))
|
||||
]
|
||||
else:
|
||||
index = self._checkindex(index)
|
||||
return self._get_single_external(index)
|
||||
|
||||
def __delitem__(self, index):
|
||||
"Delete the item(s) at the specified index/slice."
|
||||
if not isinstance(index, (int, slice)):
|
||||
raise TypeError("%s is not a legal index" % index)
|
||||
|
||||
# calculate new length and dimensions
|
||||
origLen = len(self)
|
||||
if isinstance(index, int):
|
||||
index = self._checkindex(index)
|
||||
indexRange = [index]
|
||||
else:
|
||||
indexRange = range(*index.indices(origLen))
|
||||
|
||||
newLen = origLen - len(indexRange)
|
||||
newItems = (
|
||||
self._get_single_internal(i) for i in range(origLen) if i not in indexRange
|
||||
)
|
||||
|
||||
self._rebuild(newLen, newItems)
|
||||
|
||||
def __setitem__(self, index, val):
|
||||
"Set the item(s) at the specified index/slice."
|
||||
if isinstance(index, slice):
|
||||
self._set_slice(index, val)
|
||||
else:
|
||||
index = self._checkindex(index)
|
||||
self._check_allowed((val,))
|
||||
self._set_single(index, val)
|
||||
|
||||
# ### Special methods for arithmetic operations ###
|
||||
def __add__(self, other):
|
||||
"add another list-like object"
|
||||
return self.__class__([*self, *other])
|
||||
|
||||
def __radd__(self, other):
|
||||
"add to another list-like object"
|
||||
return other.__class__([*other, *self])
|
||||
|
||||
def __iadd__(self, other):
|
||||
"add another list-like object to self"
|
||||
self.extend(other)
|
||||
return self
|
||||
|
||||
def __mul__(self, n):
|
||||
"multiply"
|
||||
return self.__class__(list(self) * n)
|
||||
|
||||
def __rmul__(self, n):
|
||||
"multiply"
|
||||
return self.__class__(list(self) * n)
|
||||
|
||||
def __imul__(self, n):
|
||||
"multiply"
|
||||
if n <= 0:
|
||||
del self[:]
|
||||
else:
|
||||
cache = list(self)
|
||||
for i in range(n - 1):
|
||||
self.extend(cache)
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
olen = len(other)
|
||||
for i in range(olen):
|
||||
try:
|
||||
c = self[i] == other[i]
|
||||
except IndexError:
|
||||
# self must be shorter
|
||||
return False
|
||||
if not c:
|
||||
return False
|
||||
return len(self) == olen
|
||||
|
||||
def __lt__(self, other):
|
||||
olen = len(other)
|
||||
for i in range(olen):
|
||||
try:
|
||||
c = self[i] < other[i]
|
||||
except IndexError:
|
||||
# self must be shorter
|
||||
return True
|
||||
if c:
|
||||
return c
|
||||
elif other[i] < self[i]:
|
||||
return False
|
||||
return len(self) < olen
|
||||
|
||||
# ### Public list interface Methods ###
|
||||
# ## Non-mutating ##
|
||||
def count(self, val):
|
||||
"Standard list count method"
|
||||
count = 0
|
||||
for i in self:
|
||||
if val == i:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def index(self, val):
|
||||
"Standard list index method"
|
||||
for i in range(0, len(self)):
|
||||
if self[i] == val:
|
||||
return i
|
||||
raise ValueError("%s not found in object" % val)
|
||||
|
||||
# ## Mutating ##
|
||||
def append(self, val):
|
||||
"Standard list append method"
|
||||
self[len(self) :] = [val]
|
||||
|
||||
def extend(self, vals):
|
||||
"Standard list extend method"
|
||||
self[len(self) :] = vals
|
||||
|
||||
def insert(self, index, val):
|
||||
"Standard list insert method"
|
||||
if not isinstance(index, int):
|
||||
raise TypeError("%s is not a legal index" % index)
|
||||
self[index:index] = [val]
|
||||
|
||||
def pop(self, index=-1):
|
||||
"Standard list pop method"
|
||||
result = self[index]
|
||||
del self[index]
|
||||
return result
|
||||
|
||||
def remove(self, val):
|
||||
"Standard list remove method"
|
||||
del self[self.index(val)]
|
||||
|
||||
def reverse(self):
|
||||
"Standard list reverse method"
|
||||
self[:] = self[-1::-1]
|
||||
|
||||
def sort(self, key=None, reverse=False):
|
||||
"Standard list sort method"
|
||||
self[:] = sorted(self, key=key, reverse=reverse)
|
||||
|
||||
# ### Private routines ###
|
||||
def _rebuild(self, newLen, newItems):
|
||||
if newLen and newLen < self._minlength:
|
||||
raise ValueError("Must have at least %d items" % self._minlength)
|
||||
if self._maxlength is not None and newLen > self._maxlength:
|
||||
raise ValueError("Cannot have more than %d items" % self._maxlength)
|
||||
|
||||
self._set_list(newLen, newItems)
|
||||
|
||||
def _set_single_rebuild(self, index, value):
|
||||
self._set_slice(slice(index, index + 1, 1), [value])
|
||||
|
||||
def _checkindex(self, index):
|
||||
length = len(self)
|
||||
if 0 <= index < length:
|
||||
return index
|
||||
if -length <= index < 0:
|
||||
return index + length
|
||||
raise IndexError("invalid index: %s" % index)
|
||||
|
||||
def _check_allowed(self, items):
|
||||
if hasattr(self, "_allowed"):
|
||||
if False in [isinstance(val, self._allowed) for val in items]:
|
||||
raise TypeError("Invalid type encountered in the arguments.")
|
||||
|
||||
def _set_slice(self, index, values):
|
||||
"Assign values to a slice of the object"
|
||||
try:
|
||||
valueList = list(values)
|
||||
except TypeError:
|
||||
raise TypeError("can only assign an iterable to a slice")
|
||||
|
||||
self._check_allowed(valueList)
|
||||
|
||||
origLen = len(self)
|
||||
start, stop, step = index.indices(origLen)
|
||||
|
||||
# CAREFUL: index.step and step are not the same!
|
||||
# step will never be None
|
||||
if index.step is None:
|
||||
self._assign_simple_slice(start, stop, valueList)
|
||||
else:
|
||||
self._assign_extended_slice(start, stop, step, valueList)
|
||||
|
||||
def _assign_extended_slice_rebuild(self, start, stop, step, valueList):
|
||||
"Assign an extended slice by rebuilding entire list"
|
||||
indexList = range(start, stop, step)
|
||||
# extended slice, only allow assigning slice of same size
|
||||
if len(valueList) != len(indexList):
|
||||
raise ValueError(
|
||||
"attempt to assign sequence of size %d "
|
||||
"to extended slice of size %d" % (len(valueList), len(indexList))
|
||||
)
|
||||
|
||||
# we're not changing the length of the sequence
|
||||
newLen = len(self)
|
||||
newVals = dict(zip(indexList, valueList))
|
||||
|
||||
def newItems():
|
||||
for i in range(newLen):
|
||||
if i in newVals:
|
||||
yield newVals[i]
|
||||
else:
|
||||
yield self._get_single_internal(i)
|
||||
|
||||
self._rebuild(newLen, newItems())
|
||||
|
||||
def _assign_extended_slice(self, start, stop, step, valueList):
|
||||
"Assign an extended slice by re-assigning individual items"
|
||||
indexList = range(start, stop, step)
|
||||
# extended slice, only allow assigning slice of same size
|
||||
if len(valueList) != len(indexList):
|
||||
raise ValueError(
|
||||
"attempt to assign sequence of size %d "
|
||||
"to extended slice of size %d" % (len(valueList), len(indexList))
|
||||
)
|
||||
|
||||
for i, val in zip(indexList, valueList):
|
||||
self._set_single(i, val)
|
||||
|
||||
def _assign_simple_slice(self, start, stop, valueList):
|
||||
"Assign a simple slice; Can assign slice of any length"
|
||||
origLen = len(self)
|
||||
stop = max(start, stop)
|
||||
newLen = origLen - stop + start + len(valueList)
|
||||
|
||||
def newItems():
|
||||
for i in range(origLen + 1):
|
||||
if i == start:
|
||||
yield from valueList
|
||||
|
||||
if i < origLen:
|
||||
if i < start or i >= stop:
|
||||
yield self._get_single_internal(i)
|
||||
|
||||
self._rebuild(newLen, newItems())
|
||||
@@ -0,0 +1,162 @@
|
||||
from ctypes import c_uint
|
||||
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
|
||||
|
||||
class Point(GEOSGeometry):
|
||||
_minlength = 2
|
||||
_maxlength = 3
|
||||
has_cs = True
|
||||
|
||||
def __init__(self, x=None, y=None, z=None, srid=None):
|
||||
"""
|
||||
The Point object may be initialized with either a tuple, or individual
|
||||
parameters.
|
||||
|
||||
For example:
|
||||
>>> p = Point((5, 23)) # 2D point, passed in as a tuple
|
||||
>>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
|
||||
"""
|
||||
if x is None:
|
||||
coords = []
|
||||
elif isinstance(x, (tuple, list)):
|
||||
# Here a tuple or list was passed in under the `x` parameter.
|
||||
coords = x
|
||||
elif isinstance(x, (float, int)) and isinstance(y, (float, int)):
|
||||
# Here X, Y, and (optionally) Z were passed in individually, as parameters.
|
||||
if isinstance(z, (float, int)):
|
||||
coords = [x, y, z]
|
||||
else:
|
||||
coords = [x, y]
|
||||
else:
|
||||
raise TypeError("Invalid parameters given for Point initialization.")
|
||||
|
||||
point = self._create_point(len(coords), coords)
|
||||
|
||||
# Initializing using the address returned from the GEOS
|
||||
# createPoint factory.
|
||||
super().__init__(point, srid=srid)
|
||||
|
||||
def _to_pickle_wkb(self):
|
||||
return None if self.empty else super()._to_pickle_wkb()
|
||||
|
||||
def _from_pickle_wkb(self, wkb):
|
||||
return self._create_empty() if wkb is None else super()._from_pickle_wkb(wkb)
|
||||
|
||||
def _ogr_ptr(self):
|
||||
return (
|
||||
gdal.geometries.Point._create_empty() if self.empty else super()._ogr_ptr()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _create_empty(cls):
|
||||
return cls._create_point(None, None)
|
||||
|
||||
@classmethod
|
||||
def _create_point(cls, ndim, coords):
|
||||
"""
|
||||
Create a coordinate sequence, set X, Y, [Z], and create point
|
||||
"""
|
||||
if not ndim:
|
||||
return capi.create_point(None)
|
||||
|
||||
if ndim < 2 or ndim > 3:
|
||||
raise TypeError("Invalid point dimension: %s" % ndim)
|
||||
|
||||
cs = capi.create_cs(c_uint(1), c_uint(ndim))
|
||||
i = iter(coords)
|
||||
capi.cs_setx(cs, 0, next(i))
|
||||
capi.cs_sety(cs, 0, next(i))
|
||||
if ndim == 3:
|
||||
capi.cs_setz(cs, 0, next(i))
|
||||
|
||||
return capi.create_point(cs)
|
||||
|
||||
def _set_list(self, length, items):
|
||||
ptr = self._create_point(length, items)
|
||||
if ptr:
|
||||
srid = self.srid
|
||||
capi.destroy_geom(self.ptr)
|
||||
self._ptr = ptr
|
||||
if srid is not None:
|
||||
self.srid = srid
|
||||
self._post_init()
|
||||
else:
|
||||
# can this happen?
|
||||
raise GEOSException("Geometry resulting from slice deletion was invalid.")
|
||||
|
||||
def _set_single(self, index, value):
|
||||
self._cs.setOrdinate(index, 0, value)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterate over coordinates of this Point."
|
||||
for i in range(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of dimensions for this Point (either 0, 2 or 3)."
|
||||
if self.empty:
|
||||
return 0
|
||||
if self.hasz:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
def _get_single_external(self, index):
|
||||
if index == 0:
|
||||
return self.x
|
||||
elif index == 1:
|
||||
return self.y
|
||||
elif index == 2:
|
||||
return self.z
|
||||
|
||||
_get_single_internal = _get_single_external
|
||||
|
||||
@property
|
||||
def x(self):
|
||||
"Return the X component of the Point."
|
||||
return self._cs.getOrdinate(0, 0)
|
||||
|
||||
@x.setter
|
||||
def x(self, value):
|
||||
"Set the X component of the Point."
|
||||
self._cs.setOrdinate(0, 0, value)
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
"Return the Y component of the Point."
|
||||
return self._cs.getOrdinate(1, 0)
|
||||
|
||||
@y.setter
|
||||
def y(self, value):
|
||||
"Set the Y component of the Point."
|
||||
self._cs.setOrdinate(1, 0, value)
|
||||
|
||||
@property
|
||||
def z(self):
|
||||
"Return the Z component of the Point."
|
||||
return self._cs.getOrdinate(2, 0) if self.hasz else None
|
||||
|
||||
@z.setter
|
||||
def z(self, value):
|
||||
"Set the Z component of the Point."
|
||||
if not self.hasz:
|
||||
raise GEOSException("Cannot set Z on 2D Point.")
|
||||
self._cs.setOrdinate(2, 0, value)
|
||||
|
||||
# ### Tuple setting and retrieval routines. ###
|
||||
@property
|
||||
def tuple(self):
|
||||
"Return a tuple of the point."
|
||||
return self._cs.tuple
|
||||
|
||||
@tuple.setter
|
||||
def tuple(self, tup):
|
||||
"Set the coordinates of the point with the given tuple."
|
||||
self._cs[0] = tup
|
||||
|
||||
# The tuple and coords properties
|
||||
coords = tuple
|
||||
@@ -0,0 +1,189 @@
|
||||
from django.contrib.gis.geos import prototypes as capi
|
||||
from django.contrib.gis.geos.geometry import GEOSGeometry
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR
|
||||
from django.contrib.gis.geos.linestring import LinearRing
|
||||
|
||||
|
||||
class Polygon(GEOSGeometry):
|
||||
_minlength = 1
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize on an exterior ring and a sequence of holes (both
|
||||
instances may be either LinearRing instances, or a tuple/list
|
||||
that may be constructed into a LinearRing).
|
||||
|
||||
Examples of initialization, where shell, hole1, and hole2 are
|
||||
valid LinearRing geometries:
|
||||
>>> from django.contrib.gis.geos import LinearRing, Polygon
|
||||
>>> shell = hole1 = hole2 = LinearRing()
|
||||
>>> poly = Polygon(shell, hole1, hole2)
|
||||
>>> poly = Polygon(shell, (hole1, hole2))
|
||||
|
||||
>>> # Example where a tuple parameters are used:
|
||||
>>> poly = Polygon(((0, 0), (0, 10), (10, 10), (10, 0), (0, 0)),
|
||||
... ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
|
||||
"""
|
||||
if not args:
|
||||
super().__init__(self._create_polygon(0, None), **kwargs)
|
||||
return
|
||||
|
||||
# Getting the ext_ring and init_holes parameters from the argument list
|
||||
ext_ring, *init_holes = args
|
||||
n_holes = len(init_holes)
|
||||
|
||||
# If initialized as Polygon(shell, (LinearRing, LinearRing))
|
||||
# [for backward-compatibility]
|
||||
if n_holes == 1 and isinstance(init_holes[0], (tuple, list)):
|
||||
if not init_holes[0]:
|
||||
init_holes = ()
|
||||
n_holes = 0
|
||||
elif isinstance(init_holes[0][0], LinearRing):
|
||||
init_holes = init_holes[0]
|
||||
n_holes = len(init_holes)
|
||||
|
||||
polygon = self._create_polygon(n_holes + 1, [ext_ring, *init_holes])
|
||||
super().__init__(polygon, **kwargs)
|
||||
|
||||
def __iter__(self):
|
||||
"Iterate over each ring in the polygon."
|
||||
for i in range(len(self)):
|
||||
yield self[i]
|
||||
|
||||
def __len__(self):
|
||||
"Return the number of rings in this Polygon."
|
||||
return self.num_interior_rings + 1
|
||||
|
||||
@classmethod
|
||||
def from_bbox(cls, bbox):
|
||||
"Construct a Polygon from a bounding box (4-tuple)."
|
||||
x0, y0, x1, y1 = bbox
|
||||
for z in bbox:
|
||||
if not isinstance(z, (float, int)):
|
||||
return GEOSGeometry(
|
||||
"POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))"
|
||||
% (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)
|
||||
)
|
||||
return Polygon(((x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0)))
|
||||
|
||||
# ### These routines are needed for list-like operation w/ListMixin ###
|
||||
def _create_polygon(self, length, items):
|
||||
# Instantiate LinearRing objects if necessary, but don't clone them yet
|
||||
# _construct_ring will throw a TypeError if a parameter isn't a valid ring
|
||||
# If we cloned the pointers here, we wouldn't be able to clean up
|
||||
# in case of error.
|
||||
if not length:
|
||||
return capi.create_empty_polygon()
|
||||
|
||||
rings = []
|
||||
for r in items:
|
||||
if isinstance(r, GEOM_PTR):
|
||||
rings.append(r)
|
||||
else:
|
||||
rings.append(self._construct_ring(r))
|
||||
|
||||
shell = self._clone(rings.pop(0))
|
||||
|
||||
n_holes = length - 1
|
||||
if n_holes:
|
||||
holes_param = (GEOM_PTR * n_holes)(*[self._clone(r) for r in rings])
|
||||
else:
|
||||
holes_param = None
|
||||
|
||||
return capi.create_polygon(shell, holes_param, n_holes)
|
||||
|
||||
def _clone(self, g):
|
||||
if isinstance(g, GEOM_PTR):
|
||||
return capi.geom_clone(g)
|
||||
else:
|
||||
return capi.geom_clone(g.ptr)
|
||||
|
||||
def _construct_ring(
|
||||
self,
|
||||
param,
|
||||
msg=(
|
||||
"Parameter must be a sequence of LinearRings or objects that can "
|
||||
"initialize to LinearRings"
|
||||
),
|
||||
):
|
||||
"Try to construct a ring from the given parameter."
|
||||
if isinstance(param, LinearRing):
|
||||
return param
|
||||
try:
|
||||
return LinearRing(param)
|
||||
except TypeError:
|
||||
raise TypeError(msg)
|
||||
|
||||
def _set_list(self, length, items):
|
||||
# Getting the current pointer, replacing with the newly constructed
|
||||
# geometry, and destroying the old geometry.
|
||||
prev_ptr = self.ptr
|
||||
srid = self.srid
|
||||
self.ptr = self._create_polygon(length, items)
|
||||
if srid:
|
||||
self.srid = srid
|
||||
capi.destroy_geom(prev_ptr)
|
||||
|
||||
def _get_single_internal(self, index):
|
||||
"""
|
||||
Return the ring at the specified index. The first index, 0, will
|
||||
always return the exterior ring. Indices > 0 will return the
|
||||
interior ring at the given index (e.g., poly[1] and poly[2] would
|
||||
return the first and second interior ring, respectively).
|
||||
|
||||
CAREFUL: Internal/External are not the same as Interior/Exterior!
|
||||
Return a pointer from the existing geometries for use internally by the
|
||||
object's methods. _get_single_external() returns a clone of the same
|
||||
geometry for use by external code.
|
||||
"""
|
||||
if index == 0:
|
||||
return capi.get_extring(self.ptr)
|
||||
else:
|
||||
# Getting the interior ring, have to subtract 1 from the index.
|
||||
return capi.get_intring(self.ptr, index - 1)
|
||||
|
||||
def _get_single_external(self, index):
|
||||
return GEOSGeometry(
|
||||
capi.geom_clone(self._get_single_internal(index)), srid=self.srid
|
||||
)
|
||||
|
||||
_set_single = GEOSGeometry._set_single_rebuild
|
||||
_assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
|
||||
|
||||
# #### Polygon Properties ####
|
||||
@property
|
||||
def num_interior_rings(self):
|
||||
"Return the number of interior rings."
|
||||
# Getting the number of rings
|
||||
return capi.get_nrings(self.ptr)
|
||||
|
||||
def _get_ext_ring(self):
|
||||
"Get the exterior ring of the Polygon."
|
||||
return self[0]
|
||||
|
||||
def _set_ext_ring(self, ring):
|
||||
"Set the exterior ring of the Polygon."
|
||||
self[0] = ring
|
||||
|
||||
# Properties for the exterior ring/shell.
|
||||
exterior_ring = property(_get_ext_ring, _set_ext_ring)
|
||||
shell = exterior_ring
|
||||
|
||||
@property
|
||||
def tuple(self):
|
||||
"Get the tuple for each ring in this Polygon."
|
||||
return tuple(self[i].tuple for i in range(len(self)))
|
||||
|
||||
coords = tuple
|
||||
|
||||
@property
|
||||
def kml(self):
|
||||
"Return the KML representation of this Polygon."
|
||||
inner_kml = "".join(
|
||||
"<innerBoundaryIs>%s</innerBoundaryIs>" % self[i + 1].kml
|
||||
for i in range(self.num_interior_rings)
|
||||
)
|
||||
return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (
|
||||
self[0].kml,
|
||||
inner_kml,
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
from .base import GEOSBase
|
||||
from .prototypes import prepared as capi
|
||||
|
||||
|
||||
class PreparedGeometry(GEOSBase):
|
||||
"""
|
||||
A geometry that is prepared for performing certain operations.
|
||||
At the moment this includes the contains covers, and intersects
|
||||
operations.
|
||||
"""
|
||||
|
||||
ptr_type = capi.PREPGEOM_PTR
|
||||
destructor = capi.prepared_destroy
|
||||
|
||||
def __init__(self, geom):
|
||||
# Keeping a reference to the original geometry object to prevent it
|
||||
# from being garbage collected which could then crash the prepared one
|
||||
# See #21662
|
||||
self._base_geom = geom
|
||||
from .geometry import GEOSGeometry
|
||||
|
||||
if not isinstance(geom, GEOSGeometry):
|
||||
raise TypeError
|
||||
self.ptr = capi.geos_prepare(geom.ptr)
|
||||
|
||||
def contains(self, other):
|
||||
return capi.prepared_contains(self.ptr, other.ptr)
|
||||
|
||||
def contains_properly(self, other):
|
||||
return capi.prepared_contains_properly(self.ptr, other.ptr)
|
||||
|
||||
def covers(self, other):
|
||||
return capi.prepared_covers(self.ptr, other.ptr)
|
||||
|
||||
def intersects(self, other):
|
||||
return capi.prepared_intersects(self.ptr, other.ptr)
|
||||
|
||||
def crosses(self, other):
|
||||
return capi.prepared_crosses(self.ptr, other.ptr)
|
||||
|
||||
def disjoint(self, other):
|
||||
return capi.prepared_disjoint(self.ptr, other.ptr)
|
||||
|
||||
def overlaps(self, other):
|
||||
return capi.prepared_overlaps(self.ptr, other.ptr)
|
||||
|
||||
def touches(self, other):
|
||||
return capi.prepared_touches(self.ptr, other.ptr)
|
||||
|
||||
def within(self, other):
|
||||
return capi.prepared_within(self.ptr, other.ptr)
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
This module contains all of the GEOS ctypes function prototypes. Each
|
||||
prototype handles the interaction between the GEOS library and Python
|
||||
via ctypes.
|
||||
"""
|
||||
|
||||
from django.contrib.gis.geos.prototypes.coordseq import ( # NOQA
|
||||
create_cs,
|
||||
cs_clone,
|
||||
cs_getdims,
|
||||
cs_getordinate,
|
||||
cs_getsize,
|
||||
cs_getx,
|
||||
cs_gety,
|
||||
cs_getz,
|
||||
cs_is_ccw,
|
||||
cs_setordinate,
|
||||
cs_setx,
|
||||
cs_sety,
|
||||
cs_setz,
|
||||
get_cs,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.geom import ( # NOQA
|
||||
create_collection,
|
||||
create_empty_polygon,
|
||||
create_linearring,
|
||||
create_linestring,
|
||||
create_point,
|
||||
create_polygon,
|
||||
destroy_geom,
|
||||
geom_clone,
|
||||
geos_get_srid,
|
||||
geos_makevalid,
|
||||
geos_normalize,
|
||||
geos_set_srid,
|
||||
geos_type,
|
||||
geos_typeid,
|
||||
get_dims,
|
||||
get_extring,
|
||||
get_geomn,
|
||||
get_intring,
|
||||
get_nrings,
|
||||
get_num_coords,
|
||||
get_num_geoms,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.misc import * # NOQA
|
||||
from django.contrib.gis.geos.prototypes.predicates import ( # NOQA
|
||||
geos_contains,
|
||||
geos_covers,
|
||||
geos_crosses,
|
||||
geos_disjoint,
|
||||
geos_equals,
|
||||
geos_equalsexact,
|
||||
geos_equalsidentical,
|
||||
geos_hasz,
|
||||
geos_intersects,
|
||||
geos_isclosed,
|
||||
geos_isempty,
|
||||
geos_isring,
|
||||
geos_issimple,
|
||||
geos_isvalid,
|
||||
geos_overlaps,
|
||||
geos_relatepattern,
|
||||
geos_touches,
|
||||
geos_within,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.topology import * # NOQA
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,97 @@
|
||||
from ctypes import POINTER, c_byte, c_double, c_int, c_uint
|
||||
|
||||
from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import GEOSException, last_arg_byref
|
||||
|
||||
|
||||
# ## Error-checking routines specific to coordinate sequences. ##
|
||||
def check_cs_op(result, func, cargs):
|
||||
"Check the status code of a coordinate sequence operation."
|
||||
if result == 0:
|
||||
raise GEOSException("Could not set value on coordinate sequence")
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def check_cs_get(result, func, cargs):
|
||||
"Check the coordinate sequence retrieval."
|
||||
check_cs_op(result, func, cargs)
|
||||
# Object in by reference, return its value.
|
||||
return last_arg_byref(cargs)
|
||||
|
||||
|
||||
# ## Coordinate sequence prototype factory classes. ##
|
||||
class CsInt(GEOSFuncFactory):
|
||||
"For coordinate sequence routines that return an integer."
|
||||
|
||||
argtypes = [CS_PTR, POINTER(c_uint)]
|
||||
restype = c_int
|
||||
errcheck = staticmethod(check_cs_get)
|
||||
|
||||
|
||||
class CsOperation(GEOSFuncFactory):
|
||||
"For coordinate sequence operations."
|
||||
|
||||
restype = c_int
|
||||
|
||||
def __init__(self, *args, ordinate=False, get=False, **kwargs):
|
||||
if get:
|
||||
# Get routines have double parameter passed-in by reference.
|
||||
errcheck = check_cs_get
|
||||
dbl_param = POINTER(c_double)
|
||||
else:
|
||||
errcheck = check_cs_op
|
||||
dbl_param = c_double
|
||||
|
||||
if ordinate:
|
||||
# Get/Set ordinate routines have an extra uint parameter.
|
||||
argtypes = [CS_PTR, c_uint, c_uint, dbl_param]
|
||||
else:
|
||||
argtypes = [CS_PTR, c_uint, dbl_param]
|
||||
|
||||
super().__init__(
|
||||
*args, **{**kwargs, "errcheck": errcheck, "argtypes": argtypes}
|
||||
)
|
||||
|
||||
|
||||
class CsOutput(GEOSFuncFactory):
|
||||
restype = CS_PTR
|
||||
|
||||
@staticmethod
|
||||
def errcheck(result, func, cargs):
|
||||
if not result:
|
||||
raise GEOSException(
|
||||
"Error encountered checking Coordinate Sequence returned from GEOS "
|
||||
'C function "%s".' % func.__name__
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
# ## Coordinate Sequence ctypes prototypes ##
|
||||
|
||||
# Coordinate Sequence constructors & cloning.
|
||||
cs_clone = CsOutput("GEOSCoordSeq_clone", argtypes=[CS_PTR])
|
||||
create_cs = CsOutput("GEOSCoordSeq_create", argtypes=[c_uint, c_uint])
|
||||
get_cs = CsOutput("GEOSGeom_getCoordSeq", argtypes=[GEOM_PTR])
|
||||
|
||||
# Getting, setting ordinate
|
||||
cs_getordinate = CsOperation("GEOSCoordSeq_getOrdinate", ordinate=True, get=True)
|
||||
cs_setordinate = CsOperation("GEOSCoordSeq_setOrdinate", ordinate=True)
|
||||
|
||||
# For getting, x, y, z
|
||||
cs_getx = CsOperation("GEOSCoordSeq_getX", get=True)
|
||||
cs_gety = CsOperation("GEOSCoordSeq_getY", get=True)
|
||||
cs_getz = CsOperation("GEOSCoordSeq_getZ", get=True)
|
||||
|
||||
# For setting, x, y, z
|
||||
cs_setx = CsOperation("GEOSCoordSeq_setX")
|
||||
cs_sety = CsOperation("GEOSCoordSeq_setY")
|
||||
cs_setz = CsOperation("GEOSCoordSeq_setZ")
|
||||
|
||||
# These routines return size & dimensions.
|
||||
cs_getsize = CsInt("GEOSCoordSeq_getSize")
|
||||
cs_getdims = CsInt("GEOSCoordSeq_getDimensions")
|
||||
|
||||
cs_is_ccw = GEOSFuncFactory(
|
||||
"GEOSCoordSeq_isCCW", restype=c_int, argtypes=[CS_PTR, POINTER(c_byte)]
|
||||
)
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Error checking functions for GEOS ctypes prototype functions.
|
||||
"""
|
||||
|
||||
from ctypes import c_void_p, string_at
|
||||
|
||||
from django.contrib.gis.geos.error import GEOSException
|
||||
from django.contrib.gis.geos.libgeos import GEOSFuncFactory
|
||||
|
||||
# Getting the `free` routine used to free the memory allocated for
|
||||
# string pointers returned by GEOS.
|
||||
free = GEOSFuncFactory("GEOSFree")
|
||||
free.argtypes = [c_void_p]
|
||||
|
||||
|
||||
def last_arg_byref(args):
|
||||
"Return the last C argument's value by reference."
|
||||
return args[-1]._obj.value
|
||||
|
||||
|
||||
def check_dbl(result, func, cargs):
|
||||
"Check the status code and returns the double value passed in by reference."
|
||||
# Checking the status code
|
||||
if result != 1:
|
||||
return None
|
||||
# Double passed in by reference, return its value.
|
||||
return last_arg_byref(cargs)
|
||||
|
||||
|
||||
def check_geom(result, func, cargs):
|
||||
"Error checking on routines that return Geometries."
|
||||
if not result:
|
||||
raise GEOSException(
|
||||
'Error encountered checking Geometry returned from GEOS C function "%s".'
|
||||
% func.__name__
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def check_minus_one(result, func, cargs):
|
||||
"Error checking on routines that should not return -1."
|
||||
if result == -1:
|
||||
raise GEOSException(
|
||||
'Error encountered in GEOS C function "%s".' % func.__name__
|
||||
)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def check_predicate(result, func, cargs):
|
||||
"Error checking for unary/binary predicate functions."
|
||||
if result == 1:
|
||||
return True
|
||||
elif result == 0:
|
||||
return False
|
||||
else:
|
||||
raise GEOSException(
|
||||
'Error encountered on GEOS C predicate function "%s".' % func.__name__
|
||||
)
|
||||
|
||||
|
||||
def check_sized_string(result, func, cargs):
|
||||
"""
|
||||
Error checking for routines that return explicitly sized strings.
|
||||
|
||||
This frees the memory allocated by GEOS at the result pointer.
|
||||
"""
|
||||
if not result:
|
||||
raise GEOSException(
|
||||
'Invalid string pointer returned by GEOS C function "%s"' % func.__name__
|
||||
)
|
||||
# A c_size_t object is passed in by reference for the second
|
||||
# argument on these routines, and its needed to determine the
|
||||
# correct size.
|
||||
s = string_at(result, last_arg_byref(cargs))
|
||||
# Freeing the memory allocated within GEOS
|
||||
free(result)
|
||||
return s
|
||||
|
||||
|
||||
def check_string(result, func, cargs):
|
||||
"""
|
||||
Error checking for routines that return strings.
|
||||
|
||||
This frees the memory allocated by GEOS at the result pointer.
|
||||
"""
|
||||
if not result:
|
||||
raise GEOSException(
|
||||
'Error encountered checking string return value in GEOS C function "%s".'
|
||||
% func.__name__
|
||||
)
|
||||
# Getting the string value at the pointer address.
|
||||
s = string_at(result)
|
||||
# Freeing the memory allocated within GEOS
|
||||
free(result)
|
||||
return s
|
||||
@@ -0,0 +1,94 @@
|
||||
from ctypes import POINTER, c_char_p, c_int, c_ubyte, c_uint
|
||||
|
||||
from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import (
|
||||
check_geom,
|
||||
check_minus_one,
|
||||
check_string,
|
||||
)
|
||||
|
||||
# This is the return type used by binary output (WKB, HEX) routines.
|
||||
c_uchar_p = POINTER(c_ubyte)
|
||||
|
||||
|
||||
# We create a simple subclass of c_char_p here because when the response
|
||||
# type is set to c_char_p, you get a _Python_ string and there's no way
|
||||
# to access the string's address inside the error checking function.
|
||||
# In other words, you can't free the memory allocated inside GEOS. Previously,
|
||||
# the return type would just be omitted and the integer address would be
|
||||
# used -- but this allows us to be specific in the function definition and
|
||||
# keeps the reference so it may be free'd.
|
||||
class geos_char_p(c_char_p):
|
||||
pass
|
||||
|
||||
|
||||
# ### ctypes factory classes ###
|
||||
class GeomOutput(GEOSFuncFactory):
|
||||
"For GEOS routines that return a geometry."
|
||||
|
||||
restype = GEOM_PTR
|
||||
errcheck = staticmethod(check_geom)
|
||||
|
||||
|
||||
class IntFromGeom(GEOSFuncFactory):
|
||||
"Argument is a geometry, return type is an integer."
|
||||
|
||||
argtypes = [GEOM_PTR]
|
||||
restype = c_int
|
||||
errcheck = staticmethod(check_minus_one)
|
||||
|
||||
|
||||
class StringFromGeom(GEOSFuncFactory):
|
||||
"Argument is a Geometry, return type is a string."
|
||||
|
||||
argtypes = [GEOM_PTR]
|
||||
restype = geos_char_p
|
||||
errcheck = staticmethod(check_string)
|
||||
|
||||
|
||||
# ### ctypes prototypes ###
|
||||
|
||||
# The GEOS geometry type, typeid, num_coordinates and number of geometries
|
||||
geos_makevalid = GeomOutput("GEOSMakeValid", argtypes=[GEOM_PTR])
|
||||
geos_normalize = IntFromGeom("GEOSNormalize")
|
||||
geos_type = StringFromGeom("GEOSGeomType")
|
||||
geos_typeid = IntFromGeom("GEOSGeomTypeId")
|
||||
get_dims = GEOSFuncFactory("GEOSGeom_getDimensions", argtypes=[GEOM_PTR], restype=c_int)
|
||||
get_num_coords = IntFromGeom("GEOSGetNumCoordinates")
|
||||
get_num_geoms = IntFromGeom("GEOSGetNumGeometries")
|
||||
|
||||
# Geometry creation factories
|
||||
create_point = GeomOutput("GEOSGeom_createPoint", argtypes=[CS_PTR])
|
||||
create_linestring = GeomOutput("GEOSGeom_createLineString", argtypes=[CS_PTR])
|
||||
create_linearring = GeomOutput("GEOSGeom_createLinearRing", argtypes=[CS_PTR])
|
||||
|
||||
# Polygon and collection creation routines need argument types defined
|
||||
# for compatibility with some platforms, e.g. macOS ARM64. With argtypes
|
||||
# defined, arrays are automatically cast and byref() calls are not needed.
|
||||
create_polygon = GeomOutput(
|
||||
"GEOSGeom_createPolygon",
|
||||
argtypes=[GEOM_PTR, POINTER(GEOM_PTR), c_uint],
|
||||
)
|
||||
create_empty_polygon = GeomOutput("GEOSGeom_createEmptyPolygon", argtypes=[])
|
||||
create_collection = GeomOutput(
|
||||
"GEOSGeom_createCollection",
|
||||
argtypes=[c_int, POINTER(GEOM_PTR), c_uint],
|
||||
)
|
||||
|
||||
# Ring routines
|
||||
get_extring = GeomOutput("GEOSGetExteriorRing", argtypes=[GEOM_PTR])
|
||||
get_intring = GeomOutput("GEOSGetInteriorRingN", argtypes=[GEOM_PTR, c_int])
|
||||
get_nrings = IntFromGeom("GEOSGetNumInteriorRings")
|
||||
|
||||
# Collection Routines
|
||||
get_geomn = GeomOutput("GEOSGetGeometryN", argtypes=[GEOM_PTR, c_int])
|
||||
|
||||
# Cloning
|
||||
geom_clone = GEOSFuncFactory("GEOSGeom_clone", argtypes=[GEOM_PTR], restype=GEOM_PTR)
|
||||
|
||||
# Destruction routine.
|
||||
destroy_geom = GEOSFuncFactory("GEOSGeom_destroy", argtypes=[GEOM_PTR])
|
||||
|
||||
# SRID routines
|
||||
geos_get_srid = GEOSFuncFactory("GEOSGetSRID", argtypes=[GEOM_PTR], restype=c_int)
|
||||
geos_set_srid = GEOSFuncFactory("GEOSSetSRID", argtypes=[GEOM_PTR, c_int])
|
||||
@@ -0,0 +1,369 @@
|
||||
import threading
|
||||
from ctypes import POINTER, Structure, byref, c_byte, c_char_p, c_int, c_size_t
|
||||
|
||||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.libgeos import (
|
||||
GEOM_PTR,
|
||||
GEOSFuncFactory,
|
||||
geos_version_tuple,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.errcheck import (
|
||||
check_geom,
|
||||
check_sized_string,
|
||||
check_string,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
|
||||
|
||||
# ### The WKB/WKT Reader/Writer structures and pointers ###
|
||||
class WKTReader_st(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class WKTWriter_st(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class WKBReader_st(Structure):
|
||||
pass
|
||||
|
||||
|
||||
class WKBWriter_st(Structure):
|
||||
pass
|
||||
|
||||
|
||||
WKT_READ_PTR = POINTER(WKTReader_st)
|
||||
WKT_WRITE_PTR = POINTER(WKTWriter_st)
|
||||
WKB_READ_PTR = POINTER(WKBReader_st)
|
||||
WKB_WRITE_PTR = POINTER(WKBReader_st)
|
||||
|
||||
# WKTReader routines
|
||||
wkt_reader_create = GEOSFuncFactory("GEOSWKTReader_create", restype=WKT_READ_PTR)
|
||||
wkt_reader_destroy = GEOSFuncFactory("GEOSWKTReader_destroy", argtypes=[WKT_READ_PTR])
|
||||
|
||||
wkt_reader_read = GEOSFuncFactory(
|
||||
"GEOSWKTReader_read",
|
||||
argtypes=[WKT_READ_PTR, c_char_p],
|
||||
restype=GEOM_PTR,
|
||||
errcheck=check_geom,
|
||||
)
|
||||
# WKTWriter routines
|
||||
wkt_writer_create = GEOSFuncFactory("GEOSWKTWriter_create", restype=WKT_WRITE_PTR)
|
||||
wkt_writer_destroy = GEOSFuncFactory("GEOSWKTWriter_destroy", argtypes=[WKT_WRITE_PTR])
|
||||
|
||||
wkt_writer_write = GEOSFuncFactory(
|
||||
"GEOSWKTWriter_write",
|
||||
argtypes=[WKT_WRITE_PTR, GEOM_PTR],
|
||||
restype=geos_char_p,
|
||||
errcheck=check_string,
|
||||
)
|
||||
|
||||
wkt_writer_get_outdim = GEOSFuncFactory(
|
||||
"GEOSWKTWriter_getOutputDimension", argtypes=[WKT_WRITE_PTR], restype=c_int
|
||||
)
|
||||
wkt_writer_set_outdim = GEOSFuncFactory(
|
||||
"GEOSWKTWriter_setOutputDimension", argtypes=[WKT_WRITE_PTR, c_int]
|
||||
)
|
||||
|
||||
wkt_writer_set_trim = GEOSFuncFactory(
|
||||
"GEOSWKTWriter_setTrim", argtypes=[WKT_WRITE_PTR, c_byte]
|
||||
)
|
||||
wkt_writer_set_precision = GEOSFuncFactory(
|
||||
"GEOSWKTWriter_setRoundingPrecision", argtypes=[WKT_WRITE_PTR, c_int]
|
||||
)
|
||||
|
||||
# WKBReader routines
|
||||
wkb_reader_create = GEOSFuncFactory("GEOSWKBReader_create", restype=WKB_READ_PTR)
|
||||
wkb_reader_destroy = GEOSFuncFactory("GEOSWKBReader_destroy", argtypes=[WKB_READ_PTR])
|
||||
|
||||
|
||||
class WKBReadFunc(GEOSFuncFactory):
|
||||
# Although the function definitions take `const unsigned char *`
|
||||
# as their parameter, we use c_char_p here so the function may
|
||||
# take Python strings directly as parameters. Inside Python there
|
||||
# is not a difference between signed and unsigned characters, so
|
||||
# it is not a problem.
|
||||
argtypes = [WKB_READ_PTR, c_char_p, c_size_t]
|
||||
restype = GEOM_PTR
|
||||
errcheck = staticmethod(check_geom)
|
||||
|
||||
|
||||
wkb_reader_read = WKBReadFunc("GEOSWKBReader_read")
|
||||
wkb_reader_read_hex = WKBReadFunc("GEOSWKBReader_readHEX")
|
||||
|
||||
# WKBWriter routines
|
||||
wkb_writer_create = GEOSFuncFactory("GEOSWKBWriter_create", restype=WKB_WRITE_PTR)
|
||||
wkb_writer_destroy = GEOSFuncFactory("GEOSWKBWriter_destroy", argtypes=[WKB_WRITE_PTR])
|
||||
|
||||
|
||||
# WKB Writing prototypes.
|
||||
class WKBWriteFunc(GEOSFuncFactory):
|
||||
argtypes = [WKB_WRITE_PTR, GEOM_PTR, POINTER(c_size_t)]
|
||||
restype = c_uchar_p
|
||||
errcheck = staticmethod(check_sized_string)
|
||||
|
||||
|
||||
wkb_writer_write = WKBWriteFunc("GEOSWKBWriter_write")
|
||||
wkb_writer_write_hex = WKBWriteFunc("GEOSWKBWriter_writeHEX")
|
||||
|
||||
|
||||
# WKBWriter property getter/setter prototypes.
|
||||
class WKBWriterGet(GEOSFuncFactory):
|
||||
argtypes = [WKB_WRITE_PTR]
|
||||
restype = c_int
|
||||
|
||||
|
||||
class WKBWriterSet(GEOSFuncFactory):
|
||||
argtypes = [WKB_WRITE_PTR, c_int]
|
||||
|
||||
|
||||
wkb_writer_get_byteorder = WKBWriterGet("GEOSWKBWriter_getByteOrder")
|
||||
wkb_writer_set_byteorder = WKBWriterSet("GEOSWKBWriter_setByteOrder")
|
||||
wkb_writer_get_outdim = WKBWriterGet("GEOSWKBWriter_getOutputDimension")
|
||||
wkb_writer_set_outdim = WKBWriterSet("GEOSWKBWriter_setOutputDimension")
|
||||
wkb_writer_get_include_srid = WKBWriterGet(
|
||||
"GEOSWKBWriter_getIncludeSRID", restype=c_byte
|
||||
)
|
||||
wkb_writer_set_include_srid = WKBWriterSet(
|
||||
"GEOSWKBWriter_setIncludeSRID", argtypes=[WKB_WRITE_PTR, c_byte]
|
||||
)
|
||||
|
||||
|
||||
# ### Base I/O Class ###
|
||||
class IOBase(GEOSBase):
|
||||
"Base class for GEOS I/O objects."
|
||||
|
||||
def __init__(self):
|
||||
# Getting the pointer with the constructor.
|
||||
self.ptr = self._constructor()
|
||||
# Loading the real destructor function at this point as doing it in
|
||||
# __del__ is too late (import error).
|
||||
self.destructor.func
|
||||
|
||||
|
||||
# ### Base WKB/WKT Reading and Writing objects ###
|
||||
|
||||
|
||||
# Non-public WKB/WKT reader classes for internal use because
|
||||
# their `read` methods return _pointers_ instead of GEOSGeometry
|
||||
# objects.
|
||||
class _WKTReader(IOBase):
|
||||
_constructor = wkt_reader_create
|
||||
ptr_type = WKT_READ_PTR
|
||||
destructor = wkt_reader_destroy
|
||||
|
||||
def read(self, wkt):
|
||||
if not isinstance(wkt, (bytes, str)):
|
||||
raise TypeError
|
||||
return wkt_reader_read(self.ptr, force_bytes(wkt))
|
||||
|
||||
|
||||
class _WKBReader(IOBase):
|
||||
_constructor = wkb_reader_create
|
||||
ptr_type = WKB_READ_PTR
|
||||
destructor = wkb_reader_destroy
|
||||
|
||||
def read(self, wkb):
|
||||
"Return a _pointer_ to C GEOS Geometry object from the given WKB."
|
||||
if isinstance(wkb, memoryview):
|
||||
wkb_s = bytes(wkb)
|
||||
return wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
|
||||
elif isinstance(wkb, bytes):
|
||||
return wkb_reader_read_hex(self.ptr, wkb, len(wkb))
|
||||
elif isinstance(wkb, str):
|
||||
wkb_s = wkb.encode()
|
||||
return wkb_reader_read_hex(self.ptr, wkb_s, len(wkb_s))
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
|
||||
def default_trim_value():
|
||||
"""
|
||||
GEOS changed the default value in 3.12.0. Can be replaced by True when
|
||||
3.12.0 becomes the minimum supported version.
|
||||
"""
|
||||
return geos_version_tuple() >= (3, 12)
|
||||
|
||||
|
||||
DEFAULT_TRIM_VALUE = SimpleLazyObject(default_trim_value)
|
||||
|
||||
|
||||
# ### WKB/WKT Writer Classes ###
|
||||
class WKTWriter(IOBase):
|
||||
_constructor = wkt_writer_create
|
||||
ptr_type = WKT_WRITE_PTR
|
||||
destructor = wkt_writer_destroy
|
||||
_precision = None
|
||||
|
||||
def __init__(self, dim=2, trim=False, precision=None):
|
||||
super().__init__()
|
||||
self._trim = DEFAULT_TRIM_VALUE
|
||||
self.trim = trim
|
||||
if precision is not None:
|
||||
self.precision = precision
|
||||
self.outdim = dim
|
||||
|
||||
def write(self, geom):
|
||||
"Return the WKT representation of the given geometry."
|
||||
return wkt_writer_write(self.ptr, geom.ptr)
|
||||
|
||||
@property
|
||||
def outdim(self):
|
||||
return wkt_writer_get_outdim(self.ptr)
|
||||
|
||||
@outdim.setter
|
||||
def outdim(self, new_dim):
|
||||
if new_dim not in (2, 3):
|
||||
raise ValueError("WKT output dimension must be 2 or 3")
|
||||
wkt_writer_set_outdim(self.ptr, new_dim)
|
||||
|
||||
@property
|
||||
def trim(self):
|
||||
return self._trim
|
||||
|
||||
@trim.setter
|
||||
def trim(self, flag):
|
||||
if bool(flag) != self._trim:
|
||||
self._trim = bool(flag)
|
||||
wkt_writer_set_trim(self.ptr, self._trim)
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
return self._precision
|
||||
|
||||
@precision.setter
|
||||
def precision(self, precision):
|
||||
if (not isinstance(precision, int) or precision < 0) and precision is not None:
|
||||
raise AttributeError(
|
||||
"WKT output rounding precision must be non-negative integer or None."
|
||||
)
|
||||
if precision != self._precision:
|
||||
self._precision = precision
|
||||
wkt_writer_set_precision(self.ptr, -1 if precision is None else precision)
|
||||
|
||||
|
||||
class WKBWriter(IOBase):
|
||||
_constructor = wkb_writer_create
|
||||
ptr_type = WKB_WRITE_PTR
|
||||
destructor = wkb_writer_destroy
|
||||
geos_version = geos_version_tuple()
|
||||
|
||||
def __init__(self, dim=2):
|
||||
super().__init__()
|
||||
self.outdim = dim
|
||||
|
||||
def _handle_empty_point(self, geom):
|
||||
from django.contrib.gis.geos import Point
|
||||
|
||||
if isinstance(geom, Point) and geom.empty:
|
||||
if self.srid:
|
||||
# PostGIS uses POINT(NaN NaN) for WKB representation of empty
|
||||
# points. Use it for EWKB as it's a PostGIS specific format.
|
||||
# https://trac.osgeo.org/postgis/ticket/3181
|
||||
geom = Point(float("NaN"), float("NaN"), srid=geom.srid)
|
||||
else:
|
||||
raise ValueError("Empty point is not representable in WKB.")
|
||||
return geom
|
||||
|
||||
def write(self, geom):
|
||||
"Return the WKB representation of the given geometry."
|
||||
geom = self._handle_empty_point(geom)
|
||||
wkb = wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t()))
|
||||
return memoryview(wkb)
|
||||
|
||||
def write_hex(self, geom):
|
||||
"Return the HEXEWKB representation of the given geometry."
|
||||
geom = self._handle_empty_point(geom)
|
||||
wkb = wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))
|
||||
return wkb
|
||||
|
||||
# ### WKBWriter Properties ###
|
||||
|
||||
# Property for getting/setting the byteorder.
|
||||
def _get_byteorder(self):
|
||||
return wkb_writer_get_byteorder(self.ptr)
|
||||
|
||||
def _set_byteorder(self, order):
|
||||
if order not in (0, 1):
|
||||
raise ValueError(
|
||||
"Byte order parameter must be 0 (Big Endian) or 1 (Little Endian)."
|
||||
)
|
||||
wkb_writer_set_byteorder(self.ptr, order)
|
||||
|
||||
byteorder = property(_get_byteorder, _set_byteorder)
|
||||
|
||||
# Property for getting/setting the output dimension.
|
||||
@property
|
||||
def outdim(self):
|
||||
return wkb_writer_get_outdim(self.ptr)
|
||||
|
||||
@outdim.setter
|
||||
def outdim(self, new_dim):
|
||||
if new_dim not in (2, 3):
|
||||
raise ValueError("WKB output dimension must be 2 or 3")
|
||||
wkb_writer_set_outdim(self.ptr, new_dim)
|
||||
|
||||
# Property for getting/setting the include srid flag.
|
||||
@property
|
||||
def srid(self):
|
||||
return bool(wkb_writer_get_include_srid(self.ptr))
|
||||
|
||||
@srid.setter
|
||||
def srid(self, include):
|
||||
wkb_writer_set_include_srid(self.ptr, bool(include))
|
||||
|
||||
|
||||
# `ThreadLocalIO` object holds instances of the WKT and WKB reader/writer
|
||||
# objects that are local to the thread. The `GEOSGeometry` internals
|
||||
# access these instances by calling the module-level functions, defined
|
||||
# below.
|
||||
class ThreadLocalIO(threading.local):
|
||||
wkt_r = None
|
||||
wkt_w = None
|
||||
wkb_r = None
|
||||
wkb_w = None
|
||||
ewkb_w = None
|
||||
|
||||
|
||||
thread_context = ThreadLocalIO()
|
||||
|
||||
|
||||
# These module-level routines return the I/O object that is local to the
|
||||
# thread. If the I/O object does not exist yet it will be initialized.
|
||||
def wkt_r():
|
||||
thread_context.wkt_r = thread_context.wkt_r or _WKTReader()
|
||||
return thread_context.wkt_r
|
||||
|
||||
|
||||
def wkt_w(dim=2, trim=False, precision=None):
|
||||
if not thread_context.wkt_w:
|
||||
thread_context.wkt_w = WKTWriter(dim=dim, trim=trim, precision=precision)
|
||||
else:
|
||||
thread_context.wkt_w.outdim = dim
|
||||
thread_context.wkt_w.trim = trim
|
||||
thread_context.wkt_w.precision = precision
|
||||
return thread_context.wkt_w
|
||||
|
||||
|
||||
def wkb_r():
|
||||
thread_context.wkb_r = thread_context.wkb_r or _WKBReader()
|
||||
return thread_context.wkb_r
|
||||
|
||||
|
||||
def wkb_w(dim=2):
|
||||
if not thread_context.wkb_w:
|
||||
thread_context.wkb_w = WKBWriter(dim=dim)
|
||||
else:
|
||||
thread_context.wkb_w.outdim = dim
|
||||
return thread_context.wkb_w
|
||||
|
||||
|
||||
def ewkb_w(dim=2):
|
||||
if not thread_context.ewkb_w:
|
||||
thread_context.ewkb_w = WKBWriter(dim=dim)
|
||||
thread_context.ewkb_w.srid = True
|
||||
else:
|
||||
thread_context.ewkb_w.outdim = dim
|
||||
return thread_context.ewkb_w
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
This module is for the miscellaneous GEOS routines, particularly the
|
||||
ones that return the area, distance, and length.
|
||||
"""
|
||||
|
||||
from ctypes import POINTER, c_double, c_int
|
||||
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string
|
||||
from django.contrib.gis.geos.prototypes.geom import geos_char_p
|
||||
|
||||
__all__ = ["geos_area", "geos_distance", "geos_length", "geos_isvalidreason"]
|
||||
|
||||
|
||||
class DblFromGeom(GEOSFuncFactory):
|
||||
"""
|
||||
Argument is a Geometry, return type is double that is passed
|
||||
in by reference as the last argument.
|
||||
"""
|
||||
|
||||
restype = c_int # Status code returned
|
||||
errcheck = staticmethod(check_dbl)
|
||||
|
||||
|
||||
# ### ctypes prototypes ###
|
||||
|
||||
# Area, distance, and length prototypes.
|
||||
geos_area = DblFromGeom("GEOSArea", argtypes=[GEOM_PTR, POINTER(c_double)])
|
||||
geos_distance = DblFromGeom(
|
||||
"GEOSDistance", argtypes=[GEOM_PTR, GEOM_PTR, POINTER(c_double)]
|
||||
)
|
||||
geos_length = DblFromGeom("GEOSLength", argtypes=[GEOM_PTR, POINTER(c_double)])
|
||||
geos_isvalidreason = GEOSFuncFactory(
|
||||
"GEOSisValidReason", restype=geos_char_p, errcheck=check_string, argtypes=[GEOM_PTR]
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
This module houses the GEOS ctypes prototype functions for the
|
||||
unary and binary predicate operations on geometries.
|
||||
"""
|
||||
|
||||
from ctypes import c_byte, c_char_p, c_double
|
||||
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_predicate
|
||||
|
||||
|
||||
# ## Binary & unary predicate factories ##
|
||||
class UnaryPredicate(GEOSFuncFactory):
|
||||
"For GEOS unary predicate functions."
|
||||
|
||||
argtypes = [GEOM_PTR]
|
||||
restype = c_byte
|
||||
errcheck = staticmethod(check_predicate)
|
||||
|
||||
|
||||
class BinaryPredicate(UnaryPredicate):
|
||||
"For GEOS binary predicate functions."
|
||||
|
||||
argtypes = [GEOM_PTR, GEOM_PTR]
|
||||
|
||||
|
||||
# ## Unary Predicates ##
|
||||
geos_hasz = UnaryPredicate("GEOSHasZ")
|
||||
geos_isclosed = UnaryPredicate("GEOSisClosed")
|
||||
geos_isempty = UnaryPredicate("GEOSisEmpty")
|
||||
geos_isring = UnaryPredicate("GEOSisRing")
|
||||
geos_issimple = UnaryPredicate("GEOSisSimple")
|
||||
geos_isvalid = UnaryPredicate("GEOSisValid")
|
||||
|
||||
# ## Binary Predicates ##
|
||||
geos_contains = BinaryPredicate("GEOSContains")
|
||||
geos_covers = BinaryPredicate("GEOSCovers")
|
||||
geos_crosses = BinaryPredicate("GEOSCrosses")
|
||||
geos_disjoint = BinaryPredicate("GEOSDisjoint")
|
||||
geos_equals = BinaryPredicate("GEOSEquals")
|
||||
geos_equalsexact = BinaryPredicate(
|
||||
"GEOSEqualsExact", argtypes=[GEOM_PTR, GEOM_PTR, c_double]
|
||||
)
|
||||
geos_equalsidentical = BinaryPredicate("GEOSEqualsIdentical")
|
||||
geos_intersects = BinaryPredicate("GEOSIntersects")
|
||||
geos_overlaps = BinaryPredicate("GEOSOverlaps")
|
||||
geos_relatepattern = BinaryPredicate(
|
||||
"GEOSRelatePattern", argtypes=[GEOM_PTR, GEOM_PTR, c_char_p]
|
||||
)
|
||||
geos_touches = BinaryPredicate("GEOSTouches")
|
||||
geos_within = BinaryPredicate("GEOSWithin")
|
||||
@@ -0,0 +1,26 @@
|
||||
from ctypes import c_byte
|
||||
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR, PREPGEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_predicate
|
||||
|
||||
# Prepared geometry constructor and destructors.
|
||||
geos_prepare = GEOSFuncFactory("GEOSPrepare", argtypes=[GEOM_PTR], restype=PREPGEOM_PTR)
|
||||
prepared_destroy = GEOSFuncFactory("GEOSPreparedGeom_destroy", argtypes=[PREPGEOM_PTR])
|
||||
|
||||
|
||||
# Prepared geometry binary predicate support.
|
||||
class PreparedPredicate(GEOSFuncFactory):
|
||||
argtypes = [PREPGEOM_PTR, GEOM_PTR]
|
||||
restype = c_byte
|
||||
errcheck = staticmethod(check_predicate)
|
||||
|
||||
|
||||
prepared_contains = PreparedPredicate("GEOSPreparedContains")
|
||||
prepared_contains_properly = PreparedPredicate("GEOSPreparedContainsProperly")
|
||||
prepared_covers = PreparedPredicate("GEOSPreparedCovers")
|
||||
prepared_crosses = PreparedPredicate("GEOSPreparedCrosses")
|
||||
prepared_disjoint = PreparedPredicate("GEOSPreparedDisjoint")
|
||||
prepared_intersects = PreparedPredicate("GEOSPreparedIntersects")
|
||||
prepared_overlaps = PreparedPredicate("GEOSPreparedOverlaps")
|
||||
prepared_touches = PreparedPredicate("GEOSPreparedTouches")
|
||||
prepared_within = PreparedPredicate("GEOSPreparedWithin")
|
||||
@@ -0,0 +1,77 @@
|
||||
import threading
|
||||
|
||||
from django.contrib.gis.geos.base import GEOSBase
|
||||
from django.contrib.gis.geos.libgeos import CONTEXT_PTR, error_h, lgeos, notice_h
|
||||
|
||||
|
||||
class GEOSContextHandle(GEOSBase):
|
||||
"""Represent a GEOS context handle."""
|
||||
|
||||
ptr_type = CONTEXT_PTR
|
||||
destructor = lgeos.finishGEOS_r
|
||||
|
||||
def __init__(self):
|
||||
# Initializing the context handler for this thread with
|
||||
# the notice and error handler.
|
||||
self.ptr = lgeos.initGEOS_r(notice_h, error_h)
|
||||
|
||||
|
||||
# Defining a thread-local object and creating an instance
|
||||
# to hold a reference to GEOSContextHandle for this thread.
|
||||
class GEOSContext(threading.local):
|
||||
handle = None
|
||||
|
||||
|
||||
thread_context = GEOSContext()
|
||||
|
||||
|
||||
class GEOSFunc:
|
||||
"""
|
||||
Serve as a wrapper for GEOS C Functions. Use thread-safe function
|
||||
variants when available.
|
||||
"""
|
||||
|
||||
def __init__(self, func_name):
|
||||
# GEOS thread-safe function signatures end with '_r' and take an
|
||||
# additional context handle parameter.
|
||||
self.cfunc = getattr(lgeos, func_name + "_r")
|
||||
# Create a reference to thread_context so it's not garbage-collected
|
||||
# before an attempt to call this object.
|
||||
self.thread_context = thread_context
|
||||
|
||||
def __call__(self, *args):
|
||||
# Create a context handle if one doesn't exist for this thread.
|
||||
self.thread_context.handle = self.thread_context.handle or GEOSContextHandle()
|
||||
# Call the threaded GEOS routine with the pointer of the context handle
|
||||
# as the first argument.
|
||||
return self.cfunc(self.thread_context.handle.ptr, *args)
|
||||
|
||||
def __str__(self):
|
||||
return self.cfunc.__name__
|
||||
|
||||
# argtypes property
|
||||
def _get_argtypes(self):
|
||||
return self.cfunc.argtypes
|
||||
|
||||
def _set_argtypes(self, argtypes):
|
||||
self.cfunc.argtypes = [CONTEXT_PTR, *argtypes]
|
||||
|
||||
argtypes = property(_get_argtypes, _set_argtypes)
|
||||
|
||||
# restype property
|
||||
def _get_restype(self):
|
||||
return self.cfunc.restype
|
||||
|
||||
def _set_restype(self, restype):
|
||||
self.cfunc.restype = restype
|
||||
|
||||
restype = property(_get_restype, _set_restype)
|
||||
|
||||
# errcheck property
|
||||
def _get_errcheck(self):
|
||||
return self.cfunc.errcheck
|
||||
|
||||
def _set_errcheck(self, errcheck):
|
||||
self.cfunc.errcheck = errcheck
|
||||
|
||||
errcheck = property(_get_errcheck, _set_errcheck)
|
||||
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
This module houses the GEOS ctypes prototype functions for the
|
||||
topological operations on geometries.
|
||||
"""
|
||||
|
||||
from ctypes import c_double, c_int
|
||||
|
||||
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory
|
||||
from django.contrib.gis.geos.prototypes.errcheck import (
|
||||
check_geom,
|
||||
check_minus_one,
|
||||
check_string,
|
||||
)
|
||||
from django.contrib.gis.geos.prototypes.geom import geos_char_p
|
||||
|
||||
|
||||
class Topology(GEOSFuncFactory):
|
||||
"For GEOS unary topology functions."
|
||||
|
||||
argtypes = [GEOM_PTR]
|
||||
restype = GEOM_PTR
|
||||
errcheck = staticmethod(check_geom)
|
||||
|
||||
|
||||
# Topology Routines
|
||||
geos_boundary = Topology("GEOSBoundary")
|
||||
geos_buffer = Topology("GEOSBuffer", argtypes=[GEOM_PTR, c_double, c_int])
|
||||
geos_bufferwithstyle = Topology(
|
||||
"GEOSBufferWithStyle", argtypes=[GEOM_PTR, c_double, c_int, c_int, c_int, c_double]
|
||||
)
|
||||
geos_centroid = Topology("GEOSGetCentroid")
|
||||
geos_convexhull = Topology("GEOSConvexHull")
|
||||
geos_difference = Topology("GEOSDifference", argtypes=[GEOM_PTR, GEOM_PTR])
|
||||
geos_envelope = Topology("GEOSEnvelope")
|
||||
geos_intersection = Topology("GEOSIntersection", argtypes=[GEOM_PTR, GEOM_PTR])
|
||||
geos_linemerge = Topology("GEOSLineMerge")
|
||||
geos_pointonsurface = Topology("GEOSPointOnSurface")
|
||||
geos_preservesimplify = Topology(
|
||||
"GEOSTopologyPreserveSimplify", argtypes=[GEOM_PTR, c_double]
|
||||
)
|
||||
geos_simplify = Topology("GEOSSimplify", argtypes=[GEOM_PTR, c_double])
|
||||
geos_symdifference = Topology("GEOSSymDifference", argtypes=[GEOM_PTR, GEOM_PTR])
|
||||
geos_union = Topology("GEOSUnion", argtypes=[GEOM_PTR, GEOM_PTR])
|
||||
|
||||
geos_unary_union = GEOSFuncFactory(
|
||||
"GEOSUnaryUnion", argtypes=[GEOM_PTR], restype=GEOM_PTR
|
||||
)
|
||||
|
||||
# GEOSRelate returns a string, not a geometry.
|
||||
geos_relate = GEOSFuncFactory(
|
||||
"GEOSRelate",
|
||||
argtypes=[GEOM_PTR, GEOM_PTR],
|
||||
restype=geos_char_p,
|
||||
errcheck=check_string,
|
||||
)
|
||||
|
||||
# Linear referencing routines
|
||||
geos_project = GEOSFuncFactory(
|
||||
"GEOSProject",
|
||||
argtypes=[GEOM_PTR, GEOM_PTR],
|
||||
restype=c_double,
|
||||
errcheck=check_minus_one,
|
||||
)
|
||||
geos_interpolate = Topology("GEOSInterpolate", argtypes=[GEOM_PTR, c_double])
|
||||
|
||||
geos_project_normalized = GEOSFuncFactory(
|
||||
"GEOSProjectNormalized",
|
||||
argtypes=[GEOM_PTR, GEOM_PTR],
|
||||
restype=c_double,
|
||||
errcheck=check_minus_one,
|
||||
)
|
||||
geos_interpolate_normalized = Topology(
|
||||
"GEOSInterpolateNormalized", argtypes=[GEOM_PTR, c_double]
|
||||
)
|
||||
Reference in New Issue
Block a user