linestring.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. from django.contrib.gis.geos import prototypes as capi
  2. from django.contrib.gis.geos.coordseq import GEOSCoordSeq
  3. from django.contrib.gis.geos.error import GEOSException
  4. from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin
  5. from django.contrib.gis.geos.point import Point
  6. from django.contrib.gis.shortcuts import numpy
  7. class LineString(LinearGeometryMixin, GEOSGeometry):
  8. _init_func = capi.create_linestring
  9. _minlength = 2
  10. has_cs = True
  11. def __init__(self, *args, **kwargs):
  12. """
  13. Initialize on the given sequence -- may take lists, tuples, NumPy arrays
  14. of X,Y pairs, or Point objects. If Point objects are used, ownership is
  15. _not_ transferred to the LineString object.
  16. Examples:
  17. ls = LineString((1, 1), (2, 2))
  18. ls = LineString([(1, 1), (2, 2)])
  19. ls = LineString(array([(1, 1), (2, 2)]))
  20. ls = LineString(Point(1, 1), Point(2, 2))
  21. """
  22. # If only one argument provided, set the coords array appropriately
  23. if len(args) == 1:
  24. coords = args[0]
  25. else:
  26. coords = args
  27. if not (
  28. isinstance(coords, (tuple, list))
  29. or numpy
  30. and isinstance(coords, numpy.ndarray)
  31. ):
  32. raise TypeError("Invalid initialization input for LineStrings.")
  33. # If SRID was passed in with the keyword arguments
  34. srid = kwargs.get("srid")
  35. ncoords = len(coords)
  36. if not ncoords:
  37. super().__init__(self._init_func(None), srid=srid)
  38. return
  39. if ncoords < self._minlength:
  40. raise ValueError(
  41. "%s requires at least %d points, got %s."
  42. % (
  43. self.__class__.__name__,
  44. self._minlength,
  45. ncoords,
  46. )
  47. )
  48. numpy_coords = not isinstance(coords, (tuple, list))
  49. if numpy_coords:
  50. shape = coords.shape # Using numpy's shape.
  51. if len(shape) != 2:
  52. raise TypeError("Too many dimensions.")
  53. self._checkdim(shape[1])
  54. ndim = shape[1]
  55. else:
  56. # Getting the number of coords and the number of dimensions -- which
  57. # must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
  58. ndim = None
  59. # Incrementing through each of the coordinates and verifying
  60. for coord in coords:
  61. if not isinstance(coord, (tuple, list, Point)):
  62. raise TypeError(
  63. "Each coordinate should be a sequence (list or tuple)"
  64. )
  65. if ndim is None:
  66. ndim = len(coord)
  67. self._checkdim(ndim)
  68. elif len(coord) != ndim:
  69. raise TypeError("Dimension mismatch.")
  70. # Creating a coordinate sequence object because it is easier to
  71. # set the points using its methods.
  72. cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim == 3))
  73. point_setter = cs._set_point_3d if ndim == 3 else cs._set_point_2d
  74. for i in range(ncoords):
  75. if numpy_coords:
  76. point_coords = coords[i, :]
  77. elif isinstance(coords[i], Point):
  78. point_coords = coords[i].tuple
  79. else:
  80. point_coords = coords[i]
  81. point_setter(i, point_coords)
  82. # Calling the base geometry initialization with the returned pointer
  83. # from the function.
  84. super().__init__(self._init_func(cs.ptr), srid=srid)
  85. def __iter__(self):
  86. "Allow iteration over this LineString."
  87. for i in range(len(self)):
  88. yield self[i]
  89. def __len__(self):
  90. "Return the number of points in this LineString."
  91. return len(self._cs)
  92. def _get_single_external(self, index):
  93. return self._cs[index]
  94. _get_single_internal = _get_single_external
  95. def _set_list(self, length, items):
  96. ndim = self._cs.dims
  97. hasz = self._cs.hasz # I don't understand why these are different
  98. srid = self.srid
  99. # create a new coordinate sequence and populate accordingly
  100. cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
  101. for i, c in enumerate(items):
  102. cs[i] = c
  103. ptr = self._init_func(cs.ptr)
  104. if ptr:
  105. capi.destroy_geom(self.ptr)
  106. self.ptr = ptr
  107. if srid is not None:
  108. self.srid = srid
  109. self._post_init()
  110. else:
  111. # can this happen?
  112. raise GEOSException("Geometry resulting from slice deletion was invalid.")
  113. def _set_single(self, index, value):
  114. self._cs[index] = value
  115. def _checkdim(self, dim):
  116. if dim not in (2, 3):
  117. raise TypeError("Dimension mismatch.")
  118. # #### Sequence Properties ####
  119. @property
  120. def tuple(self):
  121. "Return a tuple version of the geometry from the coordinate sequence."
  122. return self._cs.tuple
  123. coords = tuple
  124. def _listarr(self, func):
  125. """
  126. Return a sequence (list) corresponding with the given function.
  127. Return a numpy array if possible.
  128. """
  129. lst = [func(i) for i in range(len(self))]
  130. if numpy:
  131. return numpy.array(lst) # ARRRR!
  132. else:
  133. return lst
  134. @property
  135. def array(self):
  136. "Return a numpy array for the LineString."
  137. return self._listarr(self._cs.__getitem__)
  138. @property
  139. def x(self):
  140. "Return a list or numpy array of the X variable."
  141. return self._listarr(self._cs.getX)
  142. @property
  143. def y(self):
  144. "Return a list or numpy array of the Y variable."
  145. return self._listarr(self._cs.getY)
  146. @property
  147. def z(self):
  148. "Return a list or numpy array of the Z variable."
  149. if not self.hasz:
  150. return None
  151. else:
  152. return self._listarr(self._cs.getZ)
  153. # LinearRings are LineStrings used within Polygons.
  154. class LinearRing(LineString):
  155. _minlength = 4
  156. _init_func = capi.create_linearring
  157. @property
  158. def is_counterclockwise(self):
  159. if self.empty:
  160. raise ValueError("Orientation of an empty LinearRing cannot be determined.")
  161. return self._cs.is_counterclockwise