test_wkb.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import binascii
  2. import math
  3. import struct
  4. import sys
  5. import pytest
  6. from shapely import wkt
  7. from shapely.geometry import Point
  8. from shapely.tests.legacy.conftest import shapely20_todo
  9. from shapely.wkb import dump, dumps, load, loads
  10. @pytest.fixture(scope="module")
  11. def some_point():
  12. return Point(1.2, 3.4)
  13. def bin2hex(value):
  14. return binascii.b2a_hex(value).upper().decode("utf-8")
  15. def hex2bin(value):
  16. return binascii.a2b_hex(value)
  17. def hostorder(fmt, value):
  18. """Re-pack a hex WKB value to native endianness if needed
  19. This routine does not understand WKB format, so it must be provided a
  20. struct module format string, without initial indicator character ("@=<>!"),
  21. which will be interpreted as big- or little-endian with standard sizes
  22. depending on the endian flag in the first byte of the value.
  23. """
  24. if fmt and fmt[0] in "@=<>!":
  25. raise ValueError("Initial indicator character, one of @=<>!, in fmt")
  26. if not fmt or fmt[0] not in "cbB":
  27. raise ValueError("Missing endian flag in fmt")
  28. (hexendian,) = struct.unpack(fmt[0], hex2bin(value[:2]))
  29. hexorder = {0: ">", 1: "<"}[hexendian]
  30. sysorder = {"little": "<", "big": ">"}[sys.byteorder]
  31. if hexorder == sysorder:
  32. return value # Nothing to do
  33. return bin2hex(
  34. struct.pack(
  35. sysorder + fmt,
  36. {">": 0, "<": 1}[sysorder],
  37. *struct.unpack(hexorder + fmt, hex2bin(value))[1:],
  38. )
  39. )
  40. def test_dumps_srid(some_point):
  41. result = dumps(some_point)
  42. assert bin2hex(result) == hostorder(
  43. "BIdd", "0101000000333333333333F33F3333333333330B40"
  44. )
  45. result = dumps(some_point, srid=4326)
  46. assert bin2hex(result) == hostorder(
  47. "BIIdd", "0101000020E6100000333333333333F33F3333333333330B40"
  48. )
  49. def test_dumps_endianness(some_point):
  50. result = dumps(some_point)
  51. assert bin2hex(result) == hostorder(
  52. "BIdd", "0101000000333333333333F33F3333333333330B40"
  53. )
  54. result = dumps(some_point, big_endian=False)
  55. assert bin2hex(result) == "0101000000333333333333F33F3333333333330B40"
  56. result = dumps(some_point, big_endian=True)
  57. assert bin2hex(result) == "00000000013FF3333333333333400B333333333333"
  58. def test_dumps_hex(some_point):
  59. result = dumps(some_point, hex=True)
  60. assert result == hostorder("BIdd", "0101000000333333333333F33F3333333333330B40")
  61. def test_loads_srid():
  62. # load a geometry which includes an srid
  63. geom = loads(hex2bin("0101000020E6100000333333333333F33F3333333333330B40"))
  64. assert isinstance(geom, Point)
  65. assert geom.coords[:] == [(1.2, 3.4)]
  66. # by default srid is not exported
  67. result = dumps(geom)
  68. assert bin2hex(result) == hostorder(
  69. "BIdd", "0101000000333333333333F33F3333333333330B40"
  70. )
  71. # include the srid in the output
  72. result = dumps(geom, include_srid=True)
  73. assert bin2hex(result) == hostorder(
  74. "BIIdd", "0101000020E6100000333333333333F33F3333333333330B40"
  75. )
  76. # replace geometry srid with another
  77. result = dumps(geom, srid=27700)
  78. assert bin2hex(result) == hostorder(
  79. "BIIdd", "0101000020346C0000333333333333F33F3333333333330B40"
  80. )
  81. def test_loads_hex(some_point):
  82. assert loads(dumps(some_point, hex=True), hex=True) == some_point
  83. def test_dump_load_binary(some_point, tmpdir):
  84. file = tmpdir.join("test.wkb")
  85. with open(file, "wb") as file_pointer:
  86. dump(some_point, file_pointer)
  87. with open(file, "rb") as file_pointer:
  88. restored = load(file_pointer)
  89. assert some_point == restored
  90. def test_dump_load_hex(some_point, tmpdir):
  91. file = tmpdir.join("test.wkb")
  92. with open(file, "w") as file_pointer:
  93. dump(some_point, file_pointer, hex=True)
  94. with open(file) as file_pointer:
  95. restored = load(file_pointer, hex=True)
  96. assert some_point == restored
  97. # pygeos handles both bytes and str
  98. @shapely20_todo
  99. def test_dump_hex_load_binary(some_point, tmpdir):
  100. """Asserts that reading a binary file as text (hex mode) fails."""
  101. file = tmpdir.join("test.wkb")
  102. with open(file, "w") as file_pointer:
  103. dump(some_point, file_pointer, hex=True)
  104. with pytest.raises(TypeError):
  105. with open(file, "rb") as file_pointer:
  106. load(file_pointer)
  107. def test_dump_binary_load_hex(some_point, tmpdir):
  108. """Asserts that reading a text file (hex mode) as binary fails."""
  109. file = tmpdir.join("test.wkb")
  110. with open(file, "wb") as file_pointer:
  111. dump(some_point, file_pointer)
  112. # TODO(shapely-2.0) on windows this doesn't seem to error with pygeos,
  113. # but you get back a point with garbage coordinates
  114. if sys.platform == "win32":
  115. with open(file) as file_pointer:
  116. restored = load(file_pointer, hex=True)
  117. assert some_point != restored
  118. return
  119. with pytest.raises((UnicodeEncodeError, UnicodeDecodeError)):
  120. with open(file) as file_pointer:
  121. load(file_pointer, hex=True)
  122. def test_point_empty():
  123. g = wkt.loads("POINT EMPTY")
  124. result = dumps(g, big_endian=False)
  125. # Use math.isnan for second part of the WKB representation there are
  126. # many byte representations for NaN)
  127. assert result[: -2 * 8] == b"\x01\x01\x00\x00\x00"
  128. coords = struct.unpack("<2d", result[-2 * 8 :])
  129. assert len(coords) == 2
  130. assert all(math.isnan(val) for val in coords)
  131. def test_point_z_empty():
  132. g = wkt.loads("POINT Z EMPTY")
  133. assert g.wkb_hex == hostorder(
  134. "BIddd", "0101000080000000000000F87F000000000000F87F000000000000F87F"
  135. )