/django/contrib/gis/geos/tests/test_geos.py

https://code.google.com/p/mango-py/ · Python · 1055 lines · 742 code · 166 blank · 147 comment · 119 complexity · c027da673681fc14aed60d9d48f4d029 MD5 · raw file

  1. import ctypes, random, unittest, sys
  2. from django.contrib.gis.geos import *
  3. from django.contrib.gis.geos.base import gdal, numpy, GEOSBase
  4. from django.contrib.gis.geos.libgeos import GEOS_PREPARE
  5. from django.contrib.gis.geometry.test_data import TestDataMixin
  6. class GEOSTest(unittest.TestCase, TestDataMixin):
  7. @property
  8. def null_srid(self):
  9. """
  10. Returns the proper null SRID depending on the GEOS version.
  11. See the comments in `test15_srid` for more details.
  12. """
  13. info = geos_version_info()
  14. if info['version'] == '3.0.0' and info['release_candidate']:
  15. return -1
  16. else:
  17. return None
  18. def test00_base(self):
  19. "Tests out the GEOSBase class."
  20. # Testing out GEOSBase class, which provides a `ptr` property
  21. # that abstracts out access to underlying C pointers.
  22. class FakeGeom1(GEOSBase):
  23. pass
  24. # This one only accepts pointers to floats
  25. c_float_p = ctypes.POINTER(ctypes.c_float)
  26. class FakeGeom2(GEOSBase):
  27. ptr_type = c_float_p
  28. # Default ptr_type is `c_void_p`.
  29. fg1 = FakeGeom1()
  30. # Default ptr_type is C float pointer
  31. fg2 = FakeGeom2()
  32. # These assignments are OK -- None is allowed because
  33. # it's equivalent to the NULL pointer.
  34. fg1.ptr = ctypes.c_void_p()
  35. fg1.ptr = None
  36. fg2.ptr = c_float_p(ctypes.c_float(5.23))
  37. fg2.ptr = None
  38. # Because pointers have been set to NULL, an exception should be
  39. # raised when we try to access it. Raising an exception is
  40. # preferrable to a segmentation fault that commonly occurs when
  41. # a C method is given a NULL memory reference.
  42. for fg in (fg1, fg2):
  43. # Equivalent to `fg.ptr`
  44. self.assertRaises(GEOSException, fg._get_ptr)
  45. # Anything that is either not None or the acceptable pointer type will
  46. # result in a TypeError when trying to assign it to the `ptr` property.
  47. # Thus, memmory addresses (integers) and pointers of the incorrect type
  48. # (in `bad_ptrs`) will not be allowed.
  49. bad_ptrs = (5, ctypes.c_char_p('foobar'))
  50. for bad_ptr in bad_ptrs:
  51. # Equivalent to `fg.ptr = bad_ptr`
  52. self.assertRaises(TypeError, fg1._set_ptr, bad_ptr)
  53. self.assertRaises(TypeError, fg2._set_ptr, bad_ptr)
  54. def test01a_wkt(self):
  55. "Testing WKT output."
  56. for g in self.geometries.wkt_out:
  57. geom = fromstr(g.wkt)
  58. self.assertEqual(g.ewkt, geom.wkt)
  59. def test01b_hex(self):
  60. "Testing HEX output."
  61. for g in self.geometries.hex_wkt:
  62. geom = fromstr(g.wkt)
  63. self.assertEqual(g.hex, geom.hex)
  64. def test01b_hexewkb(self):
  65. "Testing (HEX)EWKB output."
  66. from binascii import a2b_hex
  67. # For testing HEX(EWKB).
  68. ogc_hex = '01010000000000000000000000000000000000F03F'
  69. # `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));`
  70. hexewkb_2d = '0101000020E61000000000000000000000000000000000F03F'
  71. # `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));`
  72. hexewkb_3d = '01010000A0E61000000000000000000000000000000000F03F0000000000000040'
  73. pnt_2d = Point(0, 1, srid=4326)
  74. pnt_3d = Point(0, 1, 2, srid=4326)
  75. # OGC-compliant HEX will not have SRID nor Z value.
  76. self.assertEqual(ogc_hex, pnt_2d.hex)
  77. self.assertEqual(ogc_hex, pnt_3d.hex)
  78. # HEXEWKB should be appropriate for its dimension -- have to use an
  79. # a WKBWriter w/dimension set accordingly, else GEOS will insert
  80. # garbage into 3D coordinate if there is none. Also, GEOS has a
  81. # a bug in versions prior to 3.1 that puts the X coordinate in
  82. # place of Z; an exception should be raised on those versions.
  83. self.assertEqual(hexewkb_2d, pnt_2d.hexewkb)
  84. if GEOS_PREPARE:
  85. self.assertEqual(hexewkb_3d, pnt_3d.hexewkb)
  86. self.assertEqual(True, GEOSGeometry(hexewkb_3d).hasz)
  87. else:
  88. try:
  89. hexewkb = pnt_3d.hexewkb
  90. except GEOSException:
  91. pass
  92. else:
  93. self.fail('Should have raised GEOSException.')
  94. # Same for EWKB.
  95. self.assertEqual(buffer(a2b_hex(hexewkb_2d)), pnt_2d.ewkb)
  96. if GEOS_PREPARE:
  97. self.assertEqual(buffer(a2b_hex(hexewkb_3d)), pnt_3d.ewkb)
  98. else:
  99. try:
  100. ewkb = pnt_3d.ewkb
  101. except GEOSException:
  102. pass
  103. else:
  104. self.fail('Should have raised GEOSException')
  105. # Redundant sanity check.
  106. self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)
  107. def test01c_kml(self):
  108. "Testing KML output."
  109. for tg in self.geometries.wkt_out:
  110. geom = fromstr(tg.wkt)
  111. kml = getattr(tg, 'kml', False)
  112. if kml: self.assertEqual(kml, geom.kml)
  113. def test01d_errors(self):
  114. "Testing the Error handlers."
  115. # string-based
  116. print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n"
  117. for err in self.geometries.errors:
  118. try:
  119. g = fromstr(err.wkt)
  120. except (GEOSException, ValueError):
  121. pass
  122. # Bad WKB
  123. self.assertRaises(GEOSException, GEOSGeometry, buffer('0'))
  124. print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"
  125. class NotAGeometry(object):
  126. pass
  127. # Some other object
  128. self.assertRaises(TypeError, GEOSGeometry, NotAGeometry())
  129. # None
  130. self.assertRaises(TypeError, GEOSGeometry, None)
  131. def test01e_wkb(self):
  132. "Testing WKB output."
  133. from binascii import b2a_hex
  134. for g in self.geometries.hex_wkt:
  135. geom = fromstr(g.wkt)
  136. wkb = geom.wkb
  137. self.assertEqual(b2a_hex(wkb).upper(), g.hex)
  138. def test01f_create_hex(self):
  139. "Testing creation from HEX."
  140. for g in self.geometries.hex_wkt:
  141. geom_h = GEOSGeometry(g.hex)
  142. # we need to do this so decimal places get normalised
  143. geom_t = fromstr(g.wkt)
  144. self.assertEqual(geom_t.wkt, geom_h.wkt)
  145. def test01g_create_wkb(self):
  146. "Testing creation from WKB."
  147. from binascii import a2b_hex
  148. for g in self.geometries.hex_wkt:
  149. wkb = buffer(a2b_hex(g.hex))
  150. geom_h = GEOSGeometry(wkb)
  151. # we need to do this so decimal places get normalised
  152. geom_t = fromstr(g.wkt)
  153. self.assertEqual(geom_t.wkt, geom_h.wkt)
  154. def test01h_ewkt(self):
  155. "Testing EWKT."
  156. srid = 32140
  157. for p in self.geometries.polygons:
  158. ewkt = 'SRID=%d;%s' % (srid, p.wkt)
  159. poly = fromstr(ewkt)
  160. self.assertEqual(srid, poly.srid)
  161. self.assertEqual(srid, poly.shell.srid)
  162. self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export
  163. def test01i_json(self):
  164. "Testing GeoJSON input/output (via GDAL)."
  165. if not gdal or not gdal.GEOJSON: return
  166. for g in self.geometries.json_geoms:
  167. geom = GEOSGeometry(g.wkt)
  168. if not hasattr(g, 'not_equal'):
  169. self.assertEqual(g.json, geom.json)
  170. self.assertEqual(g.json, geom.geojson)
  171. self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
  172. def test01k_fromfile(self):
  173. "Testing the fromfile() factory."
  174. from StringIO import StringIO
  175. ref_pnt = GEOSGeometry('POINT(5 23)')
  176. wkt_f = StringIO()
  177. wkt_f.write(ref_pnt.wkt)
  178. wkb_f = StringIO()
  179. wkb_f.write(str(ref_pnt.wkb))
  180. # Other tests use `fromfile()` on string filenames so those
  181. # aren't tested here.
  182. for fh in (wkt_f, wkb_f):
  183. fh.seek(0)
  184. pnt = fromfile(fh)
  185. self.assertEqual(ref_pnt, pnt)
  186. def test01k_eq(self):
  187. "Testing equivalence."
  188. p = fromstr('POINT(5 23)')
  189. self.assertEqual(p, p.wkt)
  190. self.assertNotEqual(p, 'foo')
  191. ls = fromstr('LINESTRING(0 0, 1 1, 5 5)')
  192. self.assertEqual(ls, ls.wkt)
  193. self.assertNotEqual(p, 'bar')
  194. # Error shouldn't be raise on equivalence testing with
  195. # an invalid type.
  196. for g in (p, ls):
  197. self.assertNotEqual(g, None)
  198. self.assertNotEqual(g, {'foo' : 'bar'})
  199. self.assertNotEqual(g, False)
  200. def test02a_points(self):
  201. "Testing Point objects."
  202. prev = fromstr('POINT(0 0)')
  203. for p in self.geometries.points:
  204. # Creating the point from the WKT
  205. pnt = fromstr(p.wkt)
  206. self.assertEqual(pnt.geom_type, 'Point')
  207. self.assertEqual(pnt.geom_typeid, 0)
  208. self.assertEqual(p.x, pnt.x)
  209. self.assertEqual(p.y, pnt.y)
  210. self.assertEqual(True, pnt == fromstr(p.wkt))
  211. self.assertEqual(False, pnt == prev)
  212. # Making sure that the point's X, Y components are what we expect
  213. self.assertAlmostEqual(p.x, pnt.tuple[0], 9)
  214. self.assertAlmostEqual(p.y, pnt.tuple[1], 9)
  215. # Testing the third dimension, and getting the tuple arguments
  216. if hasattr(p, 'z'):
  217. self.assertEqual(True, pnt.hasz)
  218. self.assertEqual(p.z, pnt.z)
  219. self.assertEqual(p.z, pnt.tuple[2], 9)
  220. tup_args = (p.x, p.y, p.z)
  221. set_tup1 = (2.71, 3.14, 5.23)
  222. set_tup2 = (5.23, 2.71, 3.14)
  223. else:
  224. self.assertEqual(False, pnt.hasz)
  225. self.assertEqual(None, pnt.z)
  226. tup_args = (p.x, p.y)
  227. set_tup1 = (2.71, 3.14)
  228. set_tup2 = (3.14, 2.71)
  229. # Centroid operation on point should be point itself
  230. self.assertEqual(p.centroid, pnt.centroid.tuple)
  231. # Now testing the different constructors
  232. pnt2 = Point(tup_args) # e.g., Point((1, 2))
  233. pnt3 = Point(*tup_args) # e.g., Point(1, 2)
  234. self.assertEqual(True, pnt == pnt2)
  235. self.assertEqual(True, pnt == pnt3)
  236. # Now testing setting the x and y
  237. pnt.y = 3.14
  238. pnt.x = 2.71
  239. self.assertEqual(3.14, pnt.y)
  240. self.assertEqual(2.71, pnt.x)
  241. # Setting via the tuple/coords property
  242. pnt.tuple = set_tup1
  243. self.assertEqual(set_tup1, pnt.tuple)
  244. pnt.coords = set_tup2
  245. self.assertEqual(set_tup2, pnt.coords)
  246. prev = pnt # setting the previous geometry
  247. def test02b_multipoints(self):
  248. "Testing MultiPoint objects."
  249. for mp in self.geometries.multipoints:
  250. mpnt = fromstr(mp.wkt)
  251. self.assertEqual(mpnt.geom_type, 'MultiPoint')
  252. self.assertEqual(mpnt.geom_typeid, 4)
  253. self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9)
  254. self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9)
  255. self.assertRaises(GEOSIndexError, mpnt.__getitem__, len(mpnt))
  256. self.assertEqual(mp.centroid, mpnt.centroid.tuple)
  257. self.assertEqual(mp.coords, tuple(m.tuple for m in mpnt))
  258. for p in mpnt:
  259. self.assertEqual(p.geom_type, 'Point')
  260. self.assertEqual(p.geom_typeid, 0)
  261. self.assertEqual(p.empty, False)
  262. self.assertEqual(p.valid, True)
  263. def test03a_linestring(self):
  264. "Testing LineString objects."
  265. prev = fromstr('POINT(0 0)')
  266. for l in self.geometries.linestrings:
  267. ls = fromstr(l.wkt)
  268. self.assertEqual(ls.geom_type, 'LineString')
  269. self.assertEqual(ls.geom_typeid, 1)
  270. self.assertEqual(ls.empty, False)
  271. self.assertEqual(ls.ring, False)
  272. if hasattr(l, 'centroid'):
  273. self.assertEqual(l.centroid, ls.centroid.tuple)
  274. if hasattr(l, 'tup'):
  275. self.assertEqual(l.tup, ls.tuple)
  276. self.assertEqual(True, ls == fromstr(l.wkt))
  277. self.assertEqual(False, ls == prev)
  278. self.assertRaises(GEOSIndexError, ls.__getitem__, len(ls))
  279. prev = ls
  280. # Creating a LineString from a tuple, list, and numpy array
  281. self.assertEqual(ls, LineString(ls.tuple)) # tuple
  282. self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments
  283. self.assertEqual(ls, LineString([list(tup) for tup in ls.tuple])) # as list
  284. self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments
  285. if numpy: self.assertEqual(ls, LineString(numpy.array(ls.tuple))) # as numpy array
  286. def test03b_multilinestring(self):
  287. "Testing MultiLineString objects."
  288. prev = fromstr('POINT(0 0)')
  289. for l in self.geometries.multilinestrings:
  290. ml = fromstr(l.wkt)
  291. self.assertEqual(ml.geom_type, 'MultiLineString')
  292. self.assertEqual(ml.geom_typeid, 5)
  293. self.assertAlmostEqual(l.centroid[0], ml.centroid.x, 9)
  294. self.assertAlmostEqual(l.centroid[1], ml.centroid.y, 9)
  295. self.assertEqual(True, ml == fromstr(l.wkt))
  296. self.assertEqual(False, ml == prev)
  297. prev = ml
  298. for ls in ml:
  299. self.assertEqual(ls.geom_type, 'LineString')
  300. self.assertEqual(ls.geom_typeid, 1)
  301. self.assertEqual(ls.empty, False)
  302. self.assertRaises(GEOSIndexError, ml.__getitem__, len(ml))
  303. self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt)
  304. self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)))
  305. def test04_linearring(self):
  306. "Testing LinearRing objects."
  307. for rr in self.geometries.linearrings:
  308. lr = fromstr(rr.wkt)
  309. self.assertEqual(lr.geom_type, 'LinearRing')
  310. self.assertEqual(lr.geom_typeid, 2)
  311. self.assertEqual(rr.n_p, len(lr))
  312. self.assertEqual(True, lr.valid)
  313. self.assertEqual(False, lr.empty)
  314. # Creating a LinearRing from a tuple, list, and numpy array
  315. self.assertEqual(lr, LinearRing(lr.tuple))
  316. self.assertEqual(lr, LinearRing(*lr.tuple))
  317. self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
  318. if numpy: self.assertEqual(lr, LinearRing(numpy.array(lr.tuple)))
  319. def test05a_polygons(self):
  320. "Testing Polygon objects."
  321. # Testing `from_bbox` class method
  322. bbox = (-180, -90, 180, 90)
  323. p = Polygon.from_bbox( bbox )
  324. self.assertEqual(bbox, p.extent)
  325. prev = fromstr('POINT(0 0)')
  326. for p in self.geometries.polygons:
  327. # Creating the Polygon, testing its properties.
  328. poly = fromstr(p.wkt)
  329. self.assertEqual(poly.geom_type, 'Polygon')
  330. self.assertEqual(poly.geom_typeid, 3)
  331. self.assertEqual(poly.empty, False)
  332. self.assertEqual(poly.ring, False)
  333. self.assertEqual(p.n_i, poly.num_interior_rings)
  334. self.assertEqual(p.n_i + 1, len(poly)) # Testing __len__
  335. self.assertEqual(p.n_p, poly.num_points)
  336. # Area & Centroid
  337. self.assertAlmostEqual(p.area, poly.area, 9)
  338. self.assertAlmostEqual(p.centroid[0], poly.centroid.tuple[0], 9)
  339. self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9)
  340. # Testing the geometry equivalence
  341. self.assertEqual(True, poly == fromstr(p.wkt))
  342. self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
  343. self.assertEqual(True, poly != prev)
  344. # Testing the exterior ring
  345. ring = poly.exterior_ring
  346. self.assertEqual(ring.geom_type, 'LinearRing')
  347. self.assertEqual(ring.geom_typeid, 2)
  348. if p.ext_ring_cs:
  349. self.assertEqual(p.ext_ring_cs, ring.tuple)
  350. self.assertEqual(p.ext_ring_cs, poly[0].tuple) # Testing __getitem__
  351. # Testing __getitem__ and __setitem__ on invalid indices
  352. self.assertRaises(GEOSIndexError, poly.__getitem__, len(poly))
  353. self.assertRaises(GEOSIndexError, poly.__setitem__, len(poly), False)
  354. self.assertRaises(GEOSIndexError, poly.__getitem__, -1 * len(poly) - 1)
  355. # Testing __iter__
  356. for r in poly:
  357. self.assertEqual(r.geom_type, 'LinearRing')
  358. self.assertEqual(r.geom_typeid, 2)
  359. # Testing polygon construction.
  360. self.assertRaises(TypeError, Polygon.__init__, 0, [1, 2, 3])
  361. self.assertRaises(TypeError, Polygon.__init__, 'foo')
  362. # Polygon(shell, (hole1, ... holeN))
  363. rings = tuple(r for r in poly)
  364. self.assertEqual(poly, Polygon(rings[0], rings[1:]))
  365. # Polygon(shell_tuple, hole_tuple1, ... , hole_tupleN)
  366. ring_tuples = tuple(r.tuple for r in poly)
  367. self.assertEqual(poly, Polygon(*ring_tuples))
  368. # Constructing with tuples of LinearRings.
  369. self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt)
  370. self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt)
  371. def test05b_multipolygons(self):
  372. "Testing MultiPolygon objects."
  373. print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
  374. prev = fromstr('POINT (0 0)')
  375. for mp in self.geometries.multipolygons:
  376. mpoly = fromstr(mp.wkt)
  377. self.assertEqual(mpoly.geom_type, 'MultiPolygon')
  378. self.assertEqual(mpoly.geom_typeid, 6)
  379. self.assertEqual(mp.valid, mpoly.valid)
  380. if mp.valid:
  381. self.assertEqual(mp.num_geom, mpoly.num_geom)
  382. self.assertEqual(mp.n_p, mpoly.num_coords)
  383. self.assertEqual(mp.num_geom, len(mpoly))
  384. self.assertRaises(GEOSIndexError, mpoly.__getitem__, len(mpoly))
  385. for p in mpoly:
  386. self.assertEqual(p.geom_type, 'Polygon')
  387. self.assertEqual(p.geom_typeid, 3)
  388. self.assertEqual(p.valid, True)
  389. self.assertEqual(mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt)
  390. print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
  391. def test06a_memory_hijinks(self):
  392. "Testing Geometry __del__() on rings and polygons."
  393. #### Memory issues with rings and polygons
  394. # These tests are needed to ensure sanity with writable geometries.
  395. # Getting a polygon with interior rings, and pulling out the interior rings
  396. poly = fromstr(self.geometries.polygons[1].wkt)
  397. ring1 = poly[0]
  398. ring2 = poly[1]
  399. # These deletes should be 'harmless' since they are done on child geometries
  400. del ring1
  401. del ring2
  402. ring1 = poly[0]
  403. ring2 = poly[1]
  404. # Deleting the polygon
  405. del poly
  406. # Access to these rings is OK since they are clones.
  407. s1, s2 = str(ring1), str(ring2)
  408. def test08_coord_seq(self):
  409. "Testing Coordinate Sequence objects."
  410. for p in self.geometries.polygons:
  411. if p.ext_ring_cs:
  412. # Constructing the polygon and getting the coordinate sequence
  413. poly = fromstr(p.wkt)
  414. cs = poly.exterior_ring.coord_seq
  415. self.assertEqual(p.ext_ring_cs, cs.tuple) # done in the Polygon test too.
  416. self.assertEqual(len(p.ext_ring_cs), len(cs)) # Making sure __len__ works
  417. # Checks __getitem__ and __setitem__
  418. for i in xrange(len(p.ext_ring_cs)):
  419. c1 = p.ext_ring_cs[i] # Expected value
  420. c2 = cs[i] # Value from coordseq
  421. self.assertEqual(c1, c2)
  422. # Constructing the test value to set the coordinate sequence with
  423. if len(c1) == 2: tset = (5, 23)
  424. else: tset = (5, 23, 8)
  425. cs[i] = tset
  426. # Making sure every set point matches what we expect
  427. for j in range(len(tset)):
  428. cs[i] = tset
  429. self.assertEqual(tset[j], cs[i][j])
  430. def test09_relate_pattern(self):
  431. "Testing relate() and relate_pattern()."
  432. g = fromstr('POINT (0 0)')
  433. self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
  434. for rg in self.geometries.relate_geoms:
  435. a = fromstr(rg.wkt_a)
  436. b = fromstr(rg.wkt_b)
  437. self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern))
  438. self.assertEqual(rg.pattern, a.relate(b))
  439. def test10_intersection(self):
  440. "Testing intersects() and intersection()."
  441. for i in xrange(len(self.geometries.topology_geoms)):
  442. a = fromstr(self.geometries.topology_geoms[i].wkt_a)
  443. b = fromstr(self.geometries.topology_geoms[i].wkt_b)
  444. i1 = fromstr(self.geometries.intersect_geoms[i].wkt)
  445. self.assertEqual(True, a.intersects(b))
  446. i2 = a.intersection(b)
  447. self.assertEqual(i1, i2)
  448. self.assertEqual(i1, a & b) # __and__ is intersection operator
  449. a &= b # testing __iand__
  450. self.assertEqual(i1, a)
  451. def test11_union(self):
  452. "Testing union()."
  453. for i in xrange(len(self.geometries.topology_geoms)):
  454. a = fromstr(self.geometries.topology_geoms[i].wkt_a)
  455. b = fromstr(self.geometries.topology_geoms[i].wkt_b)
  456. u1 = fromstr(self.geometries.union_geoms[i].wkt)
  457. u2 = a.union(b)
  458. self.assertEqual(u1, u2)
  459. self.assertEqual(u1, a | b) # __or__ is union operator
  460. a |= b # testing __ior__
  461. self.assertEqual(u1, a)
  462. def test12_difference(self):
  463. "Testing difference()."
  464. for i in xrange(len(self.geometries.topology_geoms)):
  465. a = fromstr(self.geometries.topology_geoms[i].wkt_a)
  466. b = fromstr(self.geometries.topology_geoms[i].wkt_b)
  467. d1 = fromstr(self.geometries.diff_geoms[i].wkt)
  468. d2 = a.difference(b)
  469. self.assertEqual(d1, d2)
  470. self.assertEqual(d1, a - b) # __sub__ is difference operator
  471. a -= b # testing __isub__
  472. self.assertEqual(d1, a)
  473. def test13_symdifference(self):
  474. "Testing sym_difference()."
  475. for i in xrange(len(self.geometries.topology_geoms)):
  476. a = fromstr(self.geometries.topology_geoms[i].wkt_a)
  477. b = fromstr(self.geometries.topology_geoms[i].wkt_b)
  478. d1 = fromstr(self.geometries.sdiff_geoms[i].wkt)
  479. d2 = a.sym_difference(b)
  480. self.assertEqual(d1, d2)
  481. self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator
  482. a ^= b # testing __ixor__
  483. self.assertEqual(d1, a)
  484. def test14_buffer(self):
  485. "Testing buffer()."
  486. for bg in self.geometries.buffer_geoms:
  487. g = fromstr(bg.wkt)
  488. # The buffer we expect
  489. exp_buf = fromstr(bg.buffer_wkt)
  490. quadsegs = bg.quadsegs
  491. width = bg.width
  492. # Can't use a floating-point for the number of quadsegs.
  493. self.assertRaises(ctypes.ArgumentError, g.buffer, width, float(quadsegs))
  494. # Constructing our buffer
  495. buf = g.buffer(width, quadsegs)
  496. self.assertEqual(exp_buf.num_coords, buf.num_coords)
  497. self.assertEqual(len(exp_buf), len(buf))
  498. # Now assuring that each point in the buffer is almost equal
  499. for j in xrange(len(exp_buf)):
  500. exp_ring = exp_buf[j]
  501. buf_ring = buf[j]
  502. self.assertEqual(len(exp_ring), len(buf_ring))
  503. for k in xrange(len(exp_ring)):
  504. # Asserting the X, Y of each point are almost equal (due to floating point imprecision)
  505. self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9)
  506. self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9)
  507. def test15_srid(self):
  508. "Testing the SRID property and keyword."
  509. # Testing SRID keyword on Point
  510. pnt = Point(5, 23, srid=4326)
  511. self.assertEqual(4326, pnt.srid)
  512. pnt.srid = 3084
  513. self.assertEqual(3084, pnt.srid)
  514. self.assertRaises(ctypes.ArgumentError, pnt.set_srid, '4326')
  515. # Testing SRID keyword on fromstr(), and on Polygon rings.
  516. poly = fromstr(self.geometries.polygons[1].wkt, srid=4269)
  517. self.assertEqual(4269, poly.srid)
  518. for ring in poly: self.assertEqual(4269, ring.srid)
  519. poly.srid = 4326
  520. self.assertEqual(4326, poly.shell.srid)
  521. # Testing SRID keyword on GeometryCollection
  522. gc = GeometryCollection(Point(5, 23), LineString((0, 0), (1.5, 1.5), (3, 3)), srid=32021)
  523. self.assertEqual(32021, gc.srid)
  524. for i in range(len(gc)): self.assertEqual(32021, gc[i].srid)
  525. # GEOS may get the SRID from HEXEWKB
  526. # 'POINT(5 23)' at SRID=4326 in hex form -- obtained from PostGIS
  527. # using `SELECT GeomFromText('POINT (5 23)', 4326);`.
  528. hex = '0101000020E610000000000000000014400000000000003740'
  529. p1 = fromstr(hex)
  530. self.assertEqual(4326, p1.srid)
  531. # In GEOS 3.0.0rc1-4 when the EWKB and/or HEXEWKB is exported,
  532. # the SRID information is lost and set to -1 -- this is not a
  533. # problem on the 3.0.0 version (another reason to upgrade).
  534. exp_srid = self.null_srid
  535. p2 = fromstr(p1.hex)
  536. self.assertEqual(exp_srid, p2.srid)
  537. p3 = fromstr(p1.hex, srid=-1) # -1 is intended.
  538. self.assertEqual(-1, p3.srid)
  539. def test16_mutable_geometries(self):
  540. "Testing the mutability of Polygons and Geometry Collections."
  541. ### Testing the mutability of Polygons ###
  542. for p in self.geometries.polygons:
  543. poly = fromstr(p.wkt)
  544. # Should only be able to use __setitem__ with LinearRing geometries.
  545. self.assertRaises(TypeError, poly.__setitem__, 0, LineString((1, 1), (2, 2)))
  546. # Constructing the new shell by adding 500 to every point in the old shell.
  547. shell_tup = poly.shell.tuple
  548. new_coords = []
  549. for point in shell_tup: new_coords.append((point[0] + 500., point[1] + 500.))
  550. new_shell = LinearRing(*tuple(new_coords))
  551. # Assigning polygon's exterior ring w/the new shell
  552. poly.exterior_ring = new_shell
  553. s = str(new_shell) # new shell is still accessible
  554. self.assertEqual(poly.exterior_ring, new_shell)
  555. self.assertEqual(poly[0], new_shell)
  556. ### Testing the mutability of Geometry Collections
  557. for tg in self.geometries.multipoints:
  558. mp = fromstr(tg.wkt)
  559. for i in range(len(mp)):
  560. # Creating a random point.
  561. pnt = mp[i]
  562. new = Point(random.randint(1, 100), random.randint(1, 100))
  563. # Testing the assignment
  564. mp[i] = new
  565. s = str(new) # what was used for the assignment is still accessible
  566. self.assertEqual(mp[i], new)
  567. self.assertEqual(mp[i].wkt, new.wkt)
  568. self.assertNotEqual(pnt, mp[i])
  569. # MultiPolygons involve much more memory management because each
  570. # Polygon w/in the collection has its own rings.
  571. for tg in self.geometries.multipolygons:
  572. mpoly = fromstr(tg.wkt)
  573. for i in xrange(len(mpoly)):
  574. poly = mpoly[i]
  575. old_poly = mpoly[i]
  576. # Offsetting the each ring in the polygon by 500.
  577. for j in xrange(len(poly)):
  578. r = poly[j]
  579. for k in xrange(len(r)): r[k] = (r[k][0] + 500., r[k][1] + 500.)
  580. poly[j] = r
  581. self.assertNotEqual(mpoly[i], poly)
  582. # Testing the assignment
  583. mpoly[i] = poly
  584. s = str(poly) # Still accessible
  585. self.assertEqual(mpoly[i], poly)
  586. self.assertNotEqual(mpoly[i], old_poly)
  587. # Extreme (!!) __setitem__ -- no longer works, have to detect
  588. # in the first object that __setitem__ is called in the subsequent
  589. # objects -- maybe mpoly[0, 0, 0] = (3.14, 2.71)?
  590. #mpoly[0][0][0] = (3.14, 2.71)
  591. #self.assertEqual((3.14, 2.71), mpoly[0][0][0])
  592. # Doing it more slowly..
  593. #self.assertEqual((3.14, 2.71), mpoly[0].shell[0])
  594. #del mpoly
  595. def test17_threed(self):
  596. "Testing three-dimensional geometries."
  597. # Testing a 3D Point
  598. pnt = Point(2, 3, 8)
  599. self.assertEqual((2.,3.,8.), pnt.coords)
  600. self.assertRaises(TypeError, pnt.set_coords, (1.,2.))
  601. pnt.coords = (1.,2.,3.)
  602. self.assertEqual((1.,2.,3.), pnt.coords)
  603. # Testing a 3D LineString
  604. ls = LineString((2., 3., 8.), (50., 250., -117.))
  605. self.assertEqual(((2.,3.,8.), (50.,250.,-117.)), ls.tuple)
  606. self.assertRaises(TypeError, ls.__setitem__, 0, (1.,2.))
  607. ls[0] = (1.,2.,3.)
  608. self.assertEqual((1.,2.,3.), ls[0])
  609. def test18_distance(self):
  610. "Testing the distance() function."
  611. # Distance to self should be 0.
  612. pnt = Point(0, 0)
  613. self.assertEqual(0.0, pnt.distance(Point(0, 0)))
  614. # Distance should be 1
  615. self.assertEqual(1.0, pnt.distance(Point(0, 1)))
  616. # Distance should be ~ sqrt(2)
  617. self.assertAlmostEqual(1.41421356237, pnt.distance(Point(1, 1)), 11)
  618. # Distances are from the closest vertex in each geometry --
  619. # should be 3 (distance from (2, 2) to (5, 2)).
  620. ls1 = LineString((0, 0), (1, 1), (2, 2))
  621. ls2 = LineString((5, 2), (6, 1), (7, 0))
  622. self.assertEqual(3, ls1.distance(ls2))
  623. def test19_length(self):
  624. "Testing the length property."
  625. # Points have 0 length.
  626. pnt = Point(0, 0)
  627. self.assertEqual(0.0, pnt.length)
  628. # Should be ~ sqrt(2)
  629. ls = LineString((0, 0), (1, 1))
  630. self.assertAlmostEqual(1.41421356237, ls.length, 11)
  631. # Should be circumfrence of Polygon
  632. poly = Polygon(LinearRing((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
  633. self.assertEqual(4.0, poly.length)
  634. # Should be sum of each element's length in collection.
  635. mpoly = MultiPolygon(poly.clone(), poly)
  636. self.assertEqual(8.0, mpoly.length)
  637. def test20a_emptyCollections(self):
  638. "Testing empty geometries and collections."
  639. gc1 = GeometryCollection([])
  640. gc2 = fromstr('GEOMETRYCOLLECTION EMPTY')
  641. pnt = fromstr('POINT EMPTY')
  642. ls = fromstr('LINESTRING EMPTY')
  643. poly = fromstr('POLYGON EMPTY')
  644. mls = fromstr('MULTILINESTRING EMPTY')
  645. mpoly1 = fromstr('MULTIPOLYGON EMPTY')
  646. mpoly2 = MultiPolygon(())
  647. for g in [gc1, gc2, pnt, ls, poly, mls, mpoly1, mpoly2]:
  648. self.assertEqual(True, g.empty)
  649. # Testing len() and num_geom.
  650. if isinstance(g, Polygon):
  651. self.assertEqual(1, len(g)) # Has one empty linear ring
  652. self.assertEqual(1, g.num_geom)
  653. self.assertEqual(0, len(g[0]))
  654. elif isinstance(g, (Point, LineString)):
  655. self.assertEqual(1, g.num_geom)
  656. self.assertEqual(0, len(g))
  657. else:
  658. self.assertEqual(0, g.num_geom)
  659. self.assertEqual(0, len(g))
  660. # Testing __getitem__ (doesn't work on Point or Polygon)
  661. if isinstance(g, Point):
  662. self.assertRaises(GEOSIndexError, g.get_x)
  663. elif isinstance(g, Polygon):
  664. lr = g.shell
  665. self.assertEqual('LINEARRING EMPTY', lr.wkt)
  666. self.assertEqual(0, len(lr))
  667. self.assertEqual(True, lr.empty)
  668. self.assertRaises(GEOSIndexError, lr.__getitem__, 0)
  669. else:
  670. self.assertRaises(GEOSIndexError, g.__getitem__, 0)
  671. def test20b_collections_of_collections(self):
  672. "Testing GeometryCollection handling of other collections."
  673. # Creating a GeometryCollection WKT string composed of other
  674. # collections and polygons.
  675. coll = [mp.wkt for mp in self.geometries.multipolygons if mp.valid]
  676. coll.extend([mls.wkt for mls in self.geometries.multilinestrings])
  677. coll.extend([p.wkt for p in self.geometries.polygons])
  678. coll.extend([mp.wkt for mp in self.geometries.multipoints])
  679. gc_wkt = 'GEOMETRYCOLLECTION(%s)' % ','.join(coll)
  680. # Should construct ok from WKT
  681. gc1 = GEOSGeometry(gc_wkt)
  682. # Should also construct ok from individual geometry arguments.
  683. gc2 = GeometryCollection(*tuple(g for g in gc1))
  684. # And, they should be equal.
  685. self.assertEqual(gc1, gc2)
  686. def test21_test_gdal(self):
  687. "Testing `ogr` and `srs` properties."
  688. if not gdal.HAS_GDAL: return
  689. g1 = fromstr('POINT(5 23)')
  690. self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry))
  691. self.assertEqual(g1.srs, None)
  692. g2 = fromstr('LINESTRING(0 0, 5 5, 23 23)', srid=4326)
  693. self.assertEqual(True, isinstance(g2.ogr, gdal.OGRGeometry))
  694. self.assertEqual(True, isinstance(g2.srs, gdal.SpatialReference))
  695. self.assertEqual(g2.hex, g2.ogr.hex)
  696. self.assertEqual('WGS 84', g2.srs.name)
  697. def test22_copy(self):
  698. "Testing use with the Python `copy` module."
  699. import django.utils.copycompat as copy
  700. poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 23, 23 0, 0 0), (5 5, 5 10, 10 10, 10 5, 5 5))')
  701. cpy1 = copy.copy(poly)
  702. cpy2 = copy.deepcopy(poly)
  703. self.assertNotEqual(poly._ptr, cpy1._ptr)
  704. self.assertNotEqual(poly._ptr, cpy2._ptr)
  705. def test23_transform(self):
  706. "Testing `transform` method."
  707. if not gdal.HAS_GDAL: return
  708. orig = GEOSGeometry('POINT (-104.609 38.255)', 4326)
  709. trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774)
  710. # Using a srid, a SpatialReference object, and a CoordTransform object
  711. # for transformations.
  712. t1, t2, t3 = orig.clone(), orig.clone(), orig.clone()
  713. t1.transform(trans.srid)
  714. t2.transform(gdal.SpatialReference('EPSG:2774'))
  715. ct = gdal.CoordTransform(gdal.SpatialReference('WGS84'), gdal.SpatialReference(2774))
  716. t3.transform(ct)
  717. # Testing use of the `clone` keyword.
  718. k1 = orig.clone()
  719. k2 = k1.transform(trans.srid, clone=True)
  720. self.assertEqual(k1, orig)
  721. self.assertNotEqual(k1, k2)
  722. prec = 3
  723. for p in (t1, t2, t3, k2):
  724. self.assertAlmostEqual(trans.x, p.x, prec)
  725. self.assertAlmostEqual(trans.y, p.y, prec)
  726. def test23_transform_noop(self):
  727. """ Testing `transform` method (SRID match) """
  728. # transform() should no-op if source & dest SRIDs match,
  729. # regardless of whether GDAL is available.
  730. if gdal.HAS_GDAL:
  731. g = GEOSGeometry('POINT (-104.609 38.255)', 4326)
  732. gt = g.tuple
  733. g.transform(4326)
  734. self.assertEqual(g.tuple, gt)
  735. self.assertEqual(g.srid, 4326)
  736. g = GEOSGeometry('POINT (-104.609 38.255)', 4326)
  737. g1 = g.transform(4326, clone=True)
  738. self.assertEqual(g1.tuple, g.tuple)
  739. self.assertEqual(g1.srid, 4326)
  740. self.assertTrue(g1 is not g, "Clone didn't happen")
  741. old_has_gdal = gdal.HAS_GDAL
  742. try:
  743. gdal.HAS_GDAL = False
  744. g = GEOSGeometry('POINT (-104.609 38.255)', 4326)
  745. gt = g.tuple
  746. g.transform(4326)
  747. self.assertEqual(g.tuple, gt)
  748. self.assertEqual(g.srid, 4326)
  749. g = GEOSGeometry('POINT (-104.609 38.255)', 4326)
  750. g1 = g.transform(4326, clone=True)
  751. self.assertEqual(g1.tuple, g.tuple)
  752. self.assertEqual(g1.srid, 4326)
  753. self.assertTrue(g1 is not g, "Clone didn't happen")
  754. finally:
  755. gdal.HAS_GDAL = old_has_gdal
  756. def test23_transform_nosrid(self):
  757. """ Testing `transform` method (no SRID) """
  758. # raise a warning if SRID <0/None
  759. import warnings
  760. print "\nBEGIN - expecting Warnings; safe to ignore.\n"
  761. # test for do-nothing behaviour.
  762. try:
  763. # Keeping line-noise down by only printing the relevant
  764. # warnings once.
  765. warnings.simplefilter('once', UserWarning)
  766. warnings.simplefilter('once', FutureWarning)
  767. g = GEOSGeometry('POINT (-104.609 38.255)', srid=None)
  768. g.transform(2774)
  769. self.assertEqual(g.tuple, (-104.609, 38.255))
  770. self.assertEqual(g.srid, None)
  771. g = GEOSGeometry('POINT (-104.609 38.255)', srid=None)
  772. g1 = g.transform(2774, clone=True)
  773. self.assertTrue(g1 is None)
  774. g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1)
  775. g.transform(2774)
  776. self.assertEqual(g.tuple, (-104.609, 38.255))
  777. self.assertEqual(g.srid, -1)
  778. g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1)
  779. g1 = g.transform(2774, clone=True)
  780. self.assertTrue(g1 is None)
  781. finally:
  782. warnings.simplefilter('default', UserWarning)
  783. warnings.simplefilter('default', FutureWarning)
  784. print "\nEND - expecting Warnings; safe to ignore.\n"
  785. # test warning is raised
  786. try:
  787. warnings.simplefilter('error', FutureWarning)
  788. warnings.simplefilter('ignore', UserWarning)
  789. g = GEOSGeometry('POINT (-104.609 38.255)', srid=None)
  790. self.assertRaises(FutureWarning, g.transform, 2774)
  791. g = GEOSGeometry('POINT (-104.609 38.255)', srid=None)
  792. self.assertRaises(FutureWarning, g.transform, 2774, clone=True)
  793. g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1)
  794. self.assertRaises(FutureWarning, g.transform, 2774)
  795. g = GEOSGeometry('POINT (-104.609 38.255)', srid=-1)
  796. self.assertRaises(FutureWarning, g.transform, 2774, clone=True)
  797. finally:
  798. warnings.simplefilter('default', FutureWarning)
  799. warnings.simplefilter('default', UserWarning)
  800. def test23_transform_nogdal(self):
  801. """ Testing `transform` method (GDAL not available) """
  802. old_has_gdal = gdal.HAS_GDAL
  803. try:
  804. gdal.HAS_GDAL = False
  805. g = GEOSGeometry('POINT (-104.609 38.255)', 4326)
  806. self.assertRaises(GEOSException, g.transform, 2774)
  807. g = GEOSGeometry('POINT (-104.609 38.255)', 4326)
  808. self.assertRaises(GEOSException, g.transform, 2774, clone=True)
  809. finally:
  810. gdal.HAS_GDAL = old_has_gdal
  811. def test24_extent(self):
  812. "Testing `extent` method."
  813. # The xmin, ymin, xmax, ymax of the MultiPoint should be returned.
  814. mp = MultiPoint(Point(5, 23), Point(0, 0), Point(10, 50))
  815. self.assertEqual((0.0, 0.0, 10.0, 50.0), mp.extent)
  816. pnt = Point(5.23, 17.8)
  817. # Extent of points is just the point itself repeated.
  818. self.assertEqual((5.23, 17.8, 5.23, 17.8), pnt.extent)
  819. # Testing on the 'real world' Polygon.
  820. poly = fromstr(self.geometries.polygons[3].wkt)
  821. ring = poly.shell
  822. x, y = ring.x, ring.y
  823. xmin, ymin = min(x), min(y)
  824. xmax, ymax = max(x), max(y)
  825. self.assertEqual((xmin, ymin, xmax, ymax), poly.extent)
  826. def test25_pickle(self):
  827. "Testing pickling and unpickling support."
  828. # Using both pickle and cPickle -- just 'cause.
  829. import pickle, cPickle
  830. # Creating a list of test geometries for pickling,
  831. # and setting the SRID on some of them.
  832. def get_geoms(lst, srid=None):
  833. return [GEOSGeometry(tg.wkt, srid) for tg in lst]
  834. tgeoms = get_geoms(self.geometries.points)
  835. tgeoms.extend(get_geoms(self.geometries.multilinestrings, 4326))
  836. tgeoms.extend(get_geoms(self.geometries.polygons, 3084))
  837. tgeoms.extend(get_geoms(self.geometries.multipolygons, 900913))
  838. # The SRID won't be exported in GEOS 3.0 release candidates.
  839. no_srid = self.null_srid == -1
  840. for geom in tgeoms:
  841. s1, s2 = cPickle.dumps(geom), pickle.dumps(geom)
  842. g1, g2 = cPickle.loads(s1), pickle.loads(s2)
  843. for tmpg in (g1, g2):
  844. self.assertEqual(geom, tmpg)
  845. if not no_srid: self.assertEqual(geom.srid, tmpg.srid)
  846. def test26_prepared(self):
  847. "Testing PreparedGeometry support."
  848. if not GEOS_PREPARE: return
  849. # Creating a simple multipolygon and getting a prepared version.
  850. mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))')
  851. prep = mpoly.prepared
  852. # A set of test points.
  853. pnts = [Point(5, 5), Point(7.5, 7.5), Point(2.5, 7.5)]
  854. covers = [True, True, False] # No `covers` op for regular GEOS geoms.
  855. for pnt, c in zip(pnts, covers):
  856. # Results should be the same (but faster)
  857. self.assertEqual(mpoly.contains(pnt), prep.contains(pnt))
  858. self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt))
  859. self.assertEqual(c, prep.covers(pnt))
  860. def test26_line_merge(self):
  861. "Testing line merge support"
  862. ref_geoms = (fromstr('LINESTRING(1 1, 1 1, 3 3)'),
  863. fromstr('MULTILINESTRING((1 1, 3 3), (3 3, 4 2))'),
  864. )
  865. ref_merged = (fromstr('LINESTRING(1 1, 3 3)'),
  866. fromstr('LINESTRING (1 1, 3 3, 4 2)'),
  867. )
  868. for geom, merged in zip(ref_geoms, ref_merged):
  869. self.assertEqual(merged, geom.merged)
  870. def test27_valid_reason(self):
  871. "Testing IsValidReason support"
  872. # Skipping tests if GEOS < v3.1.
  873. if not GEOS_PREPARE: return
  874. g = GEOSGeometry("POINT(0 0)")
  875. self.assertTrue(g.valid)
  876. self.assertTrue(isinstance(g.valid_reason, basestring))
  877. self.assertEqual(g.valid_reason, "Valid Geometry")
  878. print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
  879. g = GEOSGeometry("LINESTRING(0 0, 0 0)")
  880. self.assertTrue(not g.valid)
  881. self.assertTrue(isinstance(g.valid_reason, basestring))
  882. self.assertTrue(g.valid_reason.startswith("Too few points in geometry component"))
  883. print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
  884. def suite():
  885. s = unittest.TestSuite()
  886. s.addTest(unittest.makeSuite(GEOSTest))
  887. return s
  888. def run(verbosity=2):
  889. unittest.TextTestRunner(verbosity=verbosity).run(suite())