test_measurement.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import numpy as np
  2. import pytest
  3. from numpy.testing import assert_allclose, assert_array_equal
  4. import shapely
  5. from shapely import GeometryCollection, LineString, MultiPoint, Point, Polygon
  6. from shapely.tests.common import (
  7. empty,
  8. geometry_collection,
  9. ignore_invalid,
  10. line_string,
  11. linear_ring,
  12. multi_line_string,
  13. multi_point,
  14. multi_polygon,
  15. point,
  16. point_polygon_testdata,
  17. polygon,
  18. polygon_with_hole,
  19. )
  20. @pytest.mark.parametrize(
  21. "geom",
  22. [
  23. point,
  24. line_string,
  25. linear_ring,
  26. multi_point,
  27. multi_line_string,
  28. geometry_collection,
  29. ],
  30. )
  31. def test_area_non_polygon(geom):
  32. assert shapely.area(geom) == 0.0
  33. def test_area():
  34. actual = shapely.area([polygon, polygon_with_hole, multi_polygon])
  35. assert actual.tolist() == [4.0, 96.0, 1.01]
  36. def test_distance():
  37. actual = shapely.distance(*point_polygon_testdata)
  38. expected = [2 * 2**0.5, 2**0.5, 0, 0, 0, 2**0.5]
  39. np.testing.assert_allclose(actual, expected)
  40. def test_distance_missing():
  41. actual = shapely.distance(point, None)
  42. assert np.isnan(actual)
  43. def test_distance_duplicated():
  44. a = Point(1, 2)
  45. b = LineString([(0, 0), (0, 0), (1, 1)])
  46. with ignore_invalid(shapely.geos_version < (3, 12, 0)):
  47. # https://github.com/shapely/shapely/issues/1552
  48. # GEOS < 3.12 raises "invalid" floating point errors
  49. actual = shapely.distance(a, b)
  50. assert actual == 1.0
  51. @pytest.mark.parametrize(
  52. "geom,expected",
  53. [
  54. (point, [2, 3, 2, 3]),
  55. ([point, multi_point], [[2, 3, 2, 3], [0, 0, 1, 2]]),
  56. (shapely.linestrings([[0, 0], [0, 1]]), [0, 0, 0, 1]),
  57. (shapely.linestrings([[0, 0], [1, 0]]), [0, 0, 1, 0]),
  58. (multi_point, [0, 0, 1, 2]),
  59. (multi_polygon, [0, 0, 2.2, 2.2]),
  60. (geometry_collection, [49, -1, 52, 2]),
  61. (empty, [np.nan, np.nan, np.nan, np.nan]),
  62. (None, [np.nan, np.nan, np.nan, np.nan]),
  63. ],
  64. )
  65. def test_bounds(geom, expected):
  66. assert_array_equal(shapely.bounds(geom), expected)
  67. @pytest.mark.parametrize(
  68. "geom,shape",
  69. [
  70. (point, (4,)),
  71. (None, (4,)),
  72. ([point, multi_point], (2, 4)),
  73. ([[point, multi_point], [polygon, point]], (2, 2, 4)),
  74. ([[[point, multi_point]], [[polygon, point]]], (2, 1, 2, 4)),
  75. ],
  76. )
  77. def test_bounds_dimensions(geom, shape):
  78. assert shapely.bounds(geom).shape == shape
  79. @pytest.mark.parametrize(
  80. "geom,expected",
  81. [
  82. (point, [2, 3, 2, 3]),
  83. (shapely.linestrings([[0, 0], [0, 1]]), [0, 0, 0, 1]),
  84. (shapely.linestrings([[0, 0], [1, 0]]), [0, 0, 1, 0]),
  85. (multi_point, [0, 0, 1, 2]),
  86. (multi_polygon, [0, 0, 2.2, 2.2]),
  87. (geometry_collection, [49, -1, 52, 2]),
  88. (empty, [np.nan, np.nan, np.nan, np.nan]),
  89. (None, [np.nan, np.nan, np.nan, np.nan]),
  90. ([empty, empty, None], [np.nan, np.nan, np.nan, np.nan]),
  91. # mixed missing and non-missing coordinates
  92. ([point, None], [2, 3, 2, 3]),
  93. ([point, empty], [2, 3, 2, 3]),
  94. ([point, empty, None], [2, 3, 2, 3]),
  95. ([point, empty, None, multi_point], [0, 0, 2, 3]),
  96. ],
  97. )
  98. def test_total_bounds(geom, expected):
  99. assert_array_equal(shapely.total_bounds(geom), expected)
  100. @pytest.mark.parametrize(
  101. "geom",
  102. [
  103. point,
  104. None,
  105. [point, multi_point],
  106. [[point, multi_point], [polygon, point]],
  107. [[[point, multi_point]], [[polygon, point]]],
  108. ],
  109. )
  110. def test_total_bounds_dimensions(geom):
  111. assert shapely.total_bounds(geom).shape == (4,)
  112. def test_length():
  113. actual = shapely.length(
  114. [
  115. point,
  116. line_string,
  117. linear_ring,
  118. polygon,
  119. polygon_with_hole,
  120. multi_point,
  121. multi_polygon,
  122. ]
  123. )
  124. assert actual.tolist() == [0.0, 2.0, 4.0, 8.0, 48.0, 0.0, 4.4]
  125. def test_length_missing():
  126. actual = shapely.length(None)
  127. assert np.isnan(actual)
  128. def test_hausdorff_distance():
  129. # example from GEOS docs
  130. a = shapely.linestrings([[0, 0], [100, 0], [10, 100], [10, 100]])
  131. b = shapely.linestrings([[0, 100], [0, 10], [80, 10]])
  132. with ignore_invalid(shapely.geos_version < (3, 12, 0)):
  133. # Hausdorff distance emits "invalid value encountered"
  134. # (see https://github.com/libgeos/geos/issues/515)
  135. actual = shapely.hausdorff_distance(a, b)
  136. assert actual == pytest.approx(22.360679775, abs=1e-7)
  137. def test_hausdorff_distance_densify():
  138. # example from GEOS docs
  139. a = shapely.linestrings([[0, 0], [100, 0], [10, 100], [10, 100]])
  140. b = shapely.linestrings([[0, 100], [0, 10], [80, 10]])
  141. with ignore_invalid(shapely.geos_version < (3, 12, 0)):
  142. # Hausdorff distance emits "invalid value encountered"
  143. # (see https://github.com/libgeos/geos/issues/515)
  144. actual = shapely.hausdorff_distance(a, b, densify=0.001)
  145. assert actual == pytest.approx(47.8, abs=0.1)
  146. def test_hausdorff_distance_missing():
  147. actual = shapely.hausdorff_distance(point, None)
  148. assert np.isnan(actual)
  149. actual = shapely.hausdorff_distance(point, None, densify=0.001)
  150. assert np.isnan(actual)
  151. def test_hausdorff_densify_nan():
  152. actual = shapely.hausdorff_distance(point, point, densify=np.nan)
  153. assert np.isnan(actual)
  154. def test_distance_empty():
  155. actual = shapely.distance(point, empty)
  156. assert np.isnan(actual)
  157. def test_hausdorff_distance_empty():
  158. actual = shapely.hausdorff_distance(point, empty)
  159. assert np.isnan(actual)
  160. def test_hausdorff_distance_densify_empty():
  161. actual = shapely.hausdorff_distance(point, empty, densify=0.2)
  162. assert np.isnan(actual)
  163. @pytest.mark.parametrize(
  164. "geom1, geom2, expected",
  165. [
  166. # identical geometries should have 0 distance
  167. (
  168. shapely.linestrings([[0, 0], [100, 0]]),
  169. shapely.linestrings([[0, 0], [100, 0]]),
  170. 0,
  171. ),
  172. # example from GEOS docs
  173. (
  174. shapely.linestrings([[0, 0], [50, 200], [100, 0], [150, 200], [200, 0]]),
  175. shapely.linestrings([[0, 200], [200, 150], [0, 100], [200, 50], [0, 0]]),
  176. 200,
  177. ),
  178. # same geometries but different curve direction results in maximum
  179. # distance between vertices on the lines.
  180. (
  181. shapely.linestrings([[0, 0], [50, 200], [100, 0], [150, 200], [200, 0]]),
  182. shapely.linestrings([[200, 0], [150, 200], [100, 0], [50, 200], [0, 0]]),
  183. 200,
  184. ),
  185. # another example from GEOS docs
  186. (
  187. shapely.linestrings([[0, 0], [50, 200], [100, 0], [150, 200], [200, 0]]),
  188. shapely.linestrings([[0, 0], [200, 50], [0, 100], [200, 150], [0, 200]]),
  189. 282.842712474619,
  190. ),
  191. # example from GEOS tests
  192. (
  193. shapely.linestrings([[0, 0], [100, 0]]),
  194. shapely.linestrings([[0, 0], [50, 50], [100, 0]]),
  195. 70.7106781186548,
  196. ),
  197. ],
  198. )
  199. def test_frechet_distance(geom1, geom2, expected):
  200. actual = shapely.frechet_distance(geom1, geom2)
  201. assert actual == pytest.approx(expected, abs=1e-12)
  202. @pytest.mark.parametrize(
  203. "geom1, geom2, densify, expected",
  204. [
  205. # example from GEOS tests
  206. (
  207. shapely.linestrings([[0, 0], [100, 0]]),
  208. shapely.linestrings([[0, 0], [50, 50], [100, 0]]),
  209. 0.002,
  210. 50,
  211. )
  212. ],
  213. )
  214. def test_frechet_distance_densify(geom1, geom2, densify, expected):
  215. actual = shapely.frechet_distance(geom1, geom2, densify=densify)
  216. assert actual == pytest.approx(expected, abs=1e-12)
  217. @pytest.mark.parametrize(
  218. "geom1, geom2",
  219. [
  220. (line_string, None),
  221. (None, line_string),
  222. (None, None),
  223. (line_string, empty),
  224. (empty, line_string),
  225. (empty, empty),
  226. ],
  227. )
  228. def test_frechet_distance_nan_for_invalid_geometry_inputs(geom1, geom2):
  229. actual = shapely.frechet_distance(geom1, geom2)
  230. assert np.isnan(actual)
  231. def test_frechet_densify_ndarray():
  232. actual = shapely.frechet_distance(
  233. shapely.linestrings([[0, 0], [100, 0]]),
  234. shapely.linestrings([[0, 0], [50, 50], [100, 0]]),
  235. densify=[0.1, 0.2, 1],
  236. )
  237. expected = np.array([50, 50.99019514, 70.7106781186548])
  238. np.testing.assert_array_almost_equal(actual, expected)
  239. def test_frechet_densify_nan():
  240. actual = shapely.frechet_distance(line_string, line_string, densify=np.nan)
  241. assert np.isnan(actual)
  242. @pytest.mark.parametrize("densify", [0, -1, 2])
  243. def test_frechet_densify_invalid_values(densify):
  244. with pytest.raises(shapely.GEOSException, match="Fraction is not in range"):
  245. shapely.frechet_distance(line_string, line_string, densify=densify)
  246. def test_frechet_distance_densify_empty():
  247. actual = shapely.frechet_distance(line_string, empty, densify=0.2)
  248. assert np.isnan(actual)
  249. def test_minimum_clearance():
  250. actual = shapely.minimum_clearance([polygon, polygon_with_hole, multi_polygon])
  251. assert_allclose(actual, [2.0, 2.0, 0.1])
  252. def test_minimum_clearance_nonexistent():
  253. actual = shapely.minimum_clearance([point, empty])
  254. assert np.isinf(actual).all()
  255. def test_minimum_clearance_missing():
  256. actual = shapely.minimum_clearance(None)
  257. assert np.isnan(actual)
  258. @pytest.mark.parametrize(
  259. "geometry, expected",
  260. [
  261. (
  262. Polygon([(0, 5), (5, 10), (10, 5), (5, 0), (0, 5)]),
  263. 5,
  264. ),
  265. (
  266. LineString([(1, 0), (1, 10)]),
  267. 5,
  268. ),
  269. (
  270. MultiPoint([(2, 2), (4, 2)]),
  271. 1,
  272. ),
  273. (
  274. Point(2, 2),
  275. 0,
  276. ),
  277. (
  278. GeometryCollection(),
  279. 0,
  280. ),
  281. ],
  282. )
  283. def test_minimum_bounding_radius(geometry, expected):
  284. actual = shapely.minimum_bounding_radius(geometry)
  285. assert actual == pytest.approx(expected, abs=1e-12)