1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
|
# Python module tests
import json
import pytest # type: ignore
import shapely # type: ignore
from shapely.geometry import LineString, MultiPoint, Point, mapping, shape # type: ignore
from fiona.errors import ReduceError
from fiona.features import ( # type: ignore
map_feature,
reduce_features,
vertex_count,
area,
buffer,
collect,
distance,
dump,
identity,
length,
unary_projectable_property_wrapper,
unary_projectable_constructive_wrapper,
binary_projectable_property_wrapper,
)
def test_modulate_simple():
"""Set a feature's geometry."""
# map_feature() is a generator. list() materializes the values.
feat = list(map_feature("Point 0 0", {"type": "Feature"}))
assert len(feat) == 1
feat = feat[0]
assert "Point" == feat["type"]
assert (0.0, 0.0) == feat["coordinates"]
def test_modulate_complex():
"""Exercise a fairly complicated pipeline."""
bufkwd = "resolution" if shapely.__version__.startswith("1") else "quad_segs"
with open("tests/data/trio.geojson") as src:
collection = json.loads(src.read())
feat = collection["features"][0]
results = list(
map_feature(
f"simplify (buffer g (* 0.1 2) :projected false :{bufkwd} (- 4 3)) 0.001 :projected false :preserve_topology false",
feat,
)
)
assert 1 == len(results)
geom = results[0]
assert geom["type"] == "Polygon"
assert len(geom["coordinates"][0]) == 5
@pytest.mark.parametrize(
"obj, count",
[
(Point(0, 0), 1),
(MultiPoint([(0, 0), (1, 1)]), 2),
(Point(0, 0).buffer(10.0).difference(Point(0, 0).buffer(1.0)), 130),
],
)
def test_vertex_count(obj, count):
"""Check vertex counting correctness."""
assert count == vertex_count(obj)
@pytest.mark.parametrize(
"obj, count",
[
(Point(0, 0), 1),
(MultiPoint([(0, 0), (1, 1)]), 2),
(Point(0, 0).buffer(10.0).difference(Point(0, 0).buffer(1.0)), 130),
],
)
def test_calculate_vertex_count(obj, count):
"""Confirm vertex counting is in func_map."""
feat = {"type": "Feature", "properties": {}, "geometry": mapping(obj)}
assert count == list(map_feature("vertex_count g", feat))[0]
def test_calculate_builtin():
"""Confirm builtin function evaluation."""
assert 42 == list(map_feature("int '42'", None))[0]
def test_calculate_feature_attr():
"""Confirm feature attr evaluation."""
assert "LOLWUT" == list(map_feature("upper f", "lolwut"))[0]
def test_calculate_point():
"""Confirm feature attr evaluation."""
result = list(map_feature("Point 0 0", None))[0]
assert "Point" == result["type"]
def test_calculate_points():
"""Confirm feature attr evaluation."""
result = list(map_feature("list (Point 0 0) (buffer (Point 1 1) 1)", None))
assert 2 == len(result)
assert "Point" == result[0]["type"]
assert "Polygon" == result[1]["type"]
def test_reduce_len():
"""Reduce can count the number of input features."""
with open("tests/data/trio.seq") as seq:
data = [json.loads(line) for line in seq.readlines()]
# reduce() is a generator. list() materializes the values.
assert 3 == list(reduce_features("len c", data))[0]
def test_reduce_union():
"""Reduce yields one feature by default."""
with open("tests/data/trio.seq") as seq:
data = [json.loads(line) for line in seq.readlines()]
# reduce() is a generator. list() materializes the values.
result = list(reduce_features("unary_union c", data))
assert len(result) == 1
val = result[0]
assert "GeometryCollection" == val["type"]
assert 2 == len(val["geometries"])
def test_reduce_union_area():
"""Reduce can yield total area using raw output."""
with open("tests/data/trio.seq") as seq:
data = [json.loads(line) for line in seq.readlines()]
# reduce() is a generator.
result = list(reduce_features("area (unary_union c)", data))
assert len(result) == 1
val = result[0]
assert isinstance(val, float)
assert 3e4 < val < 4e4
def test_reduce_union_geom_type():
"""Reduce and print geom_type using raw output."""
with open("tests/data/trio.seq") as seq:
data = [json.loads(line) for line in seq.readlines()]
# reduce() is a generator.
result = list(reduce_features("geom_type (unary_union c)", data))
assert len(result) == 1
assert "GeometryCollection" == result[0]
def test_reduce_error():
"""Raise ReduceError when expression doesn't reduce."""
with open("tests/data/trio.seq") as seq:
data = [json.loads(line) for line in seq.readlines()]
with pytest.raises(ReduceError):
list(reduce_features("(identity c)", data))
@pytest.mark.parametrize(
"obj, count",
[
(MultiPoint([(0, 0), (1, 1)]), 2),
],
)
def test_dump_eval(obj, count):
feature = {"type": "Feature", "properties": {}, "geometry": mapping(obj)}
result = map_feature("identity g", feature, dump_parts=True)
assert len(list(result)) == count
def test_collect():
"""Collect two points."""
geom = collect((Point(0, 0), Point(1, 1)))
assert geom.geom_type == "GeometryCollection"
def test_dump():
"""Dump a point."""
geoms = list(dump(Point(0, 0)))
assert len(geoms) == 1
assert geoms[0].geom_type == "Point"
def test_dump_multi():
"""Dump two points."""
geoms = list(dump(MultiPoint([(0, 0), (1, 1)])))
assert len(geoms) == 2
assert all(g.geom_type == "Point" for g in geoms)
def test_identity():
"""Check identity."""
geom = Point(1.1, 2.2)
assert geom == identity(geom)
def test_area():
"""Check projected area of RMNP against QGIS."""
with open("tests/data/rmnp.geojson", "rb") as f:
collection = json.load(f)
geom = shape(collection["features"][0]["geometry"])
# QGIS uses a geodesic area computation and WGS84 ellipsoid.
qgis_ellipsoidal_area = 1117.433937055 # kilometer squared
# We expect no more than a 0.0001 km^2 difference. That's .00001%.
assert round(qgis_ellipsoidal_area, 4) == round(area(geom) / 1e6, 4)
@pytest.mark.parametrize(
["kwargs", "exp_distance"],
[({}, 9648.6280), ({"projected": True}, 9648.6280), ({"projected": False}, 0.1)],
)
def test_distance(kwargs, exp_distance):
"""Distance measured properly."""
assert round(exp_distance, 4) == round(
distance(Point(0, 0), Point(0.1, 0), **kwargs), 4
)
@pytest.mark.parametrize(
["kwargs", "distance", "exp_area"],
[
({}, 1.0e4, 312e6),
({"projected": True}, 10000.0, 312e6),
({"projected": False}, 0.1, 0.0312),
],
)
def test_buffer(kwargs, distance, exp_area):
"""Check area of a point buffered by 10km using 8 quadrant segments, should be ~312 km2."""
# float(f"{x:.3g}") is used to round x to 3 significant figures.
assert exp_area == float(
f"{area(buffer(Point(0, 0), distance, **kwargs), **kwargs):.3g}"
)
@pytest.mark.parametrize(
["kwargs", "exp_length"],
[({}, 9648.6280), ({"projected": True}, 9648.6280), ({"projected": False}, 0.1)],
)
def test_length(kwargs, exp_length):
"""Length measured properly."""
assert round(exp_length, 4) == round(
length(LineString([(0, 0), (0.1, 0)]), **kwargs), 4
)
@pytest.mark.parametrize(
["in_xy", "exp_xy", "kwargs"],
[
((0.1, 0.0), (9648.628, 0.0), {}),
((0.1, 0.0), (9648.628, 0.0), {"projected": True}),
((0.1, 0.0), (0.1, 0.0), {"projected": False}),
],
)
def test_unary_property_wrapper(in_xy, exp_xy, kwargs):
"""Correctly wraps a function like shapely.area."""
def func(geom, *args, **kwargs):
"""Echoes its input."""
return geom, args, kwargs
wrapper = unary_projectable_property_wrapper(func)
assert wrapper.__doc__ == "Echoes its input."
assert wrapper.__name__ == "func"
g, *rest = wrapper(Point(*in_xy), "hello", this=True, **kwargs)
assert rest == [("hello",), {"this": True}]
assert round(g.x, 4) == round(exp_xy[0], 4)
assert round(g.y, 4) == round(exp_xy[1], 4)
@pytest.mark.parametrize(
["in_xy", "exp_xy", "kwargs"],
[
((0.1, 0.0), (9648.628, 0.0), {}),
((0.1, 0.0), (9648.628, 0.0), {"projected": True}),
((0.1, 0.0), (0.1, 0.0), {"projected": False}),
],
)
def test_unary_projectable_constructive_wrapper(in_xy, exp_xy, kwargs):
"""Correctly wraps a function like shapely.buffer."""
def func(geom, required, this=False):
"""Echoes its input geom."""
assert round(geom.x, 4) == round(exp_xy[0], 4)
assert round(geom.y, 4) == round(exp_xy[1], 4)
assert this is True
return geom
wrapper = unary_projectable_constructive_wrapper(func)
assert wrapper.__doc__ == "Echoes its input geom."
assert wrapper.__name__ == "func"
g = wrapper(Point(*in_xy), "hello", this=True, **kwargs)
assert round(g.x, 4) == round(in_xy[0], 4)
assert round(g.y, 4) == round(in_xy[1], 4)
@pytest.mark.parametrize(
["in_xy", "exp_xy", "kwargs"],
[
((0.1, 0.0), (9648.628, 0.0), {}),
((0.1, 0.0), (9648.628, 0.0), {"projected": True}),
((0.1, 0.0), (0.1, 0.0), {"projected": False}),
],
)
def test_binary_projectable_property_wrapper(in_xy, exp_xy, kwargs):
"""Correctly wraps a function like shapely.distance."""
def func(geom1, geom2, *args, **kwargs):
"""Echoes its inputs."""
return geom1, geom2, args, kwargs
wrapper = binary_projectable_property_wrapper(func)
assert wrapper.__doc__ == "Echoes its inputs."
assert wrapper.__name__ == "func"
g1, g2, *rest = wrapper(Point(*in_xy), Point(*in_xy), "hello", this=True, **kwargs)
assert rest == [("hello",), {"this": True}]
assert round(g1.x, 4) == round(exp_xy[0], 4)
assert round(g1.y, 4) == round(exp_xy[1], 4)
assert round(g2.x, 4) == round(exp_xy[0], 4)
assert round(g2.y, 4) == round(exp_xy[1], 4)
|