You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

linestring.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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 (isinstance(coords, (tuple, list)) or numpy and isinstance(coords, numpy.ndarray)):
  28. raise TypeError('Invalid initialization input for LineStrings.')
  29. # If SRID was passed in with the keyword arguments
  30. srid = kwargs.get('srid')
  31. ncoords = len(coords)
  32. if not ncoords:
  33. super().__init__(self._init_func(None), srid=srid)
  34. return
  35. if ncoords < self._minlength:
  36. raise ValueError(
  37. '%s requires at least %d points, got %s.' % (
  38. self.__class__.__name__,
  39. self._minlength,
  40. ncoords,
  41. )
  42. )
  43. numpy_coords = not isinstance(coords, (tuple, list))
  44. if numpy_coords:
  45. shape = coords.shape # Using numpy's shape.
  46. if len(shape) != 2:
  47. raise TypeError('Too many dimensions.')
  48. self._checkdim(shape[1])
  49. ndim = shape[1]
  50. else:
  51. # Getting the number of coords and the number of dimensions -- which
  52. # must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
  53. ndim = None
  54. # Incrementing through each of the coordinates and verifying
  55. for coord in coords:
  56. if not isinstance(coord, (tuple, list, Point)):
  57. raise TypeError('Each coordinate should be a sequence (list or tuple)')
  58. if ndim is None:
  59. ndim = len(coord)
  60. self._checkdim(ndim)
  61. elif len(coord) != ndim:
  62. raise TypeError('Dimension mismatch.')
  63. # Creating a coordinate sequence object because it is easier to
  64. # set the points using its methods.
  65. cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim == 3))
  66. point_setter = cs._set_point_3d if ndim == 3 else cs._set_point_2d
  67. for i in range(ncoords):
  68. if numpy_coords:
  69. point_coords = coords[i, :]
  70. elif isinstance(coords[i], Point):
  71. point_coords = coords[i].tuple
  72. else:
  73. point_coords = coords[i]
  74. point_setter(i, point_coords)
  75. # Calling the base geometry initialization with the returned pointer
  76. # from the function.
  77. super().__init__(self._init_func(cs.ptr), srid=srid)
  78. def __iter__(self):
  79. "Allow iteration over this LineString."
  80. return iter(self._cs)
  81. def __len__(self):
  82. "Return the number of points in this LineString."
  83. return len(self._cs)
  84. def _get_single_external(self, index):
  85. return self._cs[index]
  86. _get_single_internal = _get_single_external
  87. def _set_list(self, length, items):
  88. ndim = self._cs.dims
  89. hasz = self._cs.hasz # I don't understand why these are different
  90. # create a new coordinate sequence and populate accordingly
  91. cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
  92. for i, c in enumerate(items):
  93. cs[i] = c
  94. ptr = self._init_func(cs.ptr)
  95. if ptr:
  96. capi.destroy_geom(self.ptr)
  97. self.ptr = ptr
  98. self._post_init()
  99. else:
  100. # can this happen?
  101. raise GEOSException('Geometry resulting from slice deletion was invalid.')
  102. def _set_single(self, index, value):
  103. self._cs[index] = value
  104. def _checkdim(self, dim):
  105. if dim not in (2, 3):
  106. raise TypeError('Dimension mismatch.')
  107. # #### Sequence Properties ####
  108. @property
  109. def tuple(self):
  110. "Return a tuple version of the geometry from the coordinate sequence."
  111. return self._cs.tuple
  112. coords = tuple
  113. def _listarr(self, func):
  114. """
  115. Return a sequence (list) corresponding with the given function.
  116. Return a numpy array if possible.
  117. """
  118. lst = [func(i) for i in range(len(self))]
  119. if numpy:
  120. return numpy.array(lst) # ARRRR!
  121. else:
  122. return lst
  123. @property
  124. def array(self):
  125. "Return a numpy array for the LineString."
  126. return self._listarr(self._cs.__getitem__)
  127. @property
  128. def x(self):
  129. "Return a list or numpy array of the X variable."
  130. return self._listarr(self._cs.getX)
  131. @property
  132. def y(self):
  133. "Return a list or numpy array of the Y variable."
  134. return self._listarr(self._cs.getY)
  135. @property
  136. def z(self):
  137. "Return a list or numpy array of the Z variable."
  138. if not self.hasz:
  139. return None
  140. else:
  141. return self._listarr(self._cs.getZ)
  142. # LinearRings are LineStrings used within Polygons.
  143. class LinearRing(LineString):
  144. _minlength = 4
  145. _init_func = capi.create_linearring