Skip to content

Commit 36a820d

Browse files
committed
Increase locations helper test coverage
1 parent 519538d commit 36a820d

2 files changed

Lines changed: 190 additions & 14 deletions

File tree

functions-python/helpers/locations.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def get_country_code(country_name: str) -> Optional[str]:
4646
if country:
4747
return country.alpha_2
4848

49-
# Try searching by name
49+
# Try searching with fuzzy matching
5050
countries = pycountry.countries.search_fuzzy(country_name)
5151
if countries:
5252
return countries[0].alpha_2
@@ -234,24 +234,47 @@ def get_geopolygons_covers(stop_point: WKTElement, db_session: Session):
234234

235235

236236
def round_geojson_coords(geometry, precision=5):
237-
"""Recursively round coordinates in a GeoJSON geometry dict to the given precision."""
238-
if isinstance(geometry, dict):
239-
if "coordinates" in geometry:
240-
geometry = geometry.copy()
241-
geometry["coordinates"] = round_coords(geometry["coordinates"], precision)
242-
if "geometries" in geometry: # GeometryCollection
243-
geometry = geometry.copy()
244-
geometry["geometries"] = [
245-
round_geojson_coords(g, precision) for g in geometry["geometries"]
246-
]
237+
"""
238+
Recursively round all coordinates in a GeoJSON geometry to the given precision.
239+
Handles Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection.
240+
"""
241+
geom_type = geometry.get("type")
242+
if geom_type == "GeometryCollection":
243+
return {
244+
"type": "GeometryCollection",
245+
"geometries": [
246+
round_geojson_coords(g, precision)
247+
for g in geometry.get("geometries", [])
248+
],
249+
}
250+
elif "coordinates" in geometry:
251+
return {
252+
**geometry,
253+
"coordinates": round_coords(geometry["coordinates"], precision),
254+
}
255+
else:
247256
return geometry
248-
return geometry
249257

250258

251259
def round_coords(coords, precision):
260+
"""
261+
Recursively round coordinates to the given precision.
262+
Handles nested lists of coordinates.
263+
Args:
264+
coords: A coordinate or list of coordinates (can be nested)
265+
precision: Number of decimal places to round to
266+
Returns:
267+
Rounded coordinates with the same structure as input
268+
"""
252269
if isinstance(coords, (list, tuple)):
253270
if coords and isinstance(coords[0], (list, tuple)):
254271
return [round_coords(c, precision) for c in coords]
255272
else:
256-
return [round(float(c), precision) for c in coords]
273+
result = []
274+
for c in coords:
275+
if isinstance(c, (int, float)):
276+
result.append(round(float(c), precision))
277+
else:
278+
result.append(c)
279+
return result
257280
return coords

functions-python/helpers/tests/test_locations.py

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Unit tests for locations helper module."""
2-
2+
import math
33
import unittest
44
from unittest.mock import MagicMock
55

@@ -16,6 +16,8 @@
1616
select_highest_level_polygon,
1717
select_lowest_level_polygon,
1818
get_country_code_from_polygons,
19+
round_geojson_coords,
20+
round_coords,
1921
)
2022
from unittest.mock import patch
2123

@@ -455,3 +457,154 @@ def test_get_country_code_from_polygons_all_none_admin_levels_returns_one_with_c
455457
]
456458
result = get_country_code_from_polygons(polys)
457459
self.assertIn(result, {"US", "CA"})
460+
461+
def _coords_equal(self, a, b, abs_tol=1e-5):
462+
if isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
463+
if len(a) != len(b):
464+
return False
465+
return all(self._coords_equal(x, y, abs_tol=abs_tol) for x, y in zip(a, b))
466+
elif isinstance(a, (list, tuple)) or isinstance(b, (list, tuple)):
467+
return False
468+
else:
469+
return math.isclose(a, b, abs_tol=abs_tol)
470+
471+
def test_round_point(self):
472+
geom = {"type": "Point", "coordinates": [1.1234567, 2.9876543]}
473+
rounded = round_geojson_coords(geom, precision=5)
474+
assert self._coords_equal(rounded["coordinates"], [1.12346, 2.98765])
475+
476+
def test_round_linestring(self):
477+
geom = {
478+
"type": "LineString",
479+
"coordinates": [[1.1234567, 2.9876543], [3.1111111, 4.9999999]],
480+
}
481+
rounded = round_geojson_coords(geom, precision=5)
482+
assert self._coords_equal(
483+
rounded["coordinates"], [[1.12346, 2.98765], [3.11111, 5.0]]
484+
)
485+
486+
def test_round_polygon(self):
487+
geom = {
488+
"type": "Polygon",
489+
"coordinates": [
490+
[[1.1234567, 2.9876543], [3.1111111, 4.9999999], [1.1234567, 2.9876543]]
491+
],
492+
}
493+
rounded = round_geojson_coords(geom, precision=5)
494+
assert self._coords_equal(
495+
rounded["coordinates"],
496+
[[[1.12346, 2.98765], [3.11111, 5.0], [1.12346, 2.98765]]],
497+
)
498+
499+
def test_round_multipoint(self):
500+
geom = {
501+
"type": "MultiPoint",
502+
"coordinates": [[1.1234567, 2.9876543], [3.1111111, 4.9999999]],
503+
}
504+
rounded = round_geojson_coords(geom, precision=5)
505+
assert self._coords_equal(
506+
rounded["coordinates"], [[1.12346, 2.98765], [3.11111, 5.0]]
507+
)
508+
509+
def test_round_multilinestring(self):
510+
geom = {
511+
"type": "MultiLineString",
512+
"coordinates": [
513+
[[1.1234567, 2.9876543], [3.1111111, 4.9999999]],
514+
[[5.5555555, 6.6666666], [7.7777777, 8.8888888]],
515+
],
516+
}
517+
rounded = round_geojson_coords(geom, precision=5)
518+
assert self._coords_equal(
519+
rounded["coordinates"],
520+
[
521+
[[1.12346, 2.98765], [3.11111, 5.0]],
522+
[[5.55556, 6.66667], [7.77778, 8.88889]],
523+
],
524+
)
525+
526+
def test_round_multipolygon(self):
527+
geom = {
528+
"type": "MultiPolygon",
529+
"coordinates": [
530+
[
531+
[
532+
[1.1234567, 2.9876543],
533+
[3.1111111, 4.9999999],
534+
[1.1234567, 2.9876543],
535+
]
536+
],
537+
[
538+
[
539+
[5.5555555, 6.6666666],
540+
[7.7777777, 8.8888888],
541+
[5.5555555, 6.6666666],
542+
]
543+
],
544+
],
545+
}
546+
rounded = round_geojson_coords(geom, precision=5)
547+
assert self._coords_equal(
548+
rounded["coordinates"],
549+
[
550+
[[[1.12346, 2.98765], [3.11111, 5.0], [1.12346, 2.98765]]],
551+
[[[5.55556, 6.66667], [7.77778, 8.88889], [5.55556, 6.66667]]],
552+
],
553+
)
554+
555+
def test_round_geometrycollection(self):
556+
geom = {
557+
"type": "GeometryCollection",
558+
"geometries": [
559+
{"type": "Point", "coordinates": [1.1234567, 2.9876543]},
560+
{
561+
"type": "LineString",
562+
"coordinates": [[3.1111111, 4.9999999], [5.5555555, 6.6666666]],
563+
},
564+
],
565+
}
566+
rounded = round_geojson_coords(geom, precision=5)
567+
assert self._coords_equal(
568+
rounded["geometries"][0]["coordinates"], [1.12346, 2.98765]
569+
)
570+
assert self._coords_equal(
571+
rounded["geometries"][1]["coordinates"],
572+
[[3.11111, 5.0], [5.55556, 6.66667]],
573+
)
574+
575+
def test_empty_coords(self):
576+
geom = {"type": "Point", "coordinates": []}
577+
rounded = round_geojson_coords(geom, precision=5)
578+
assert rounded["coordinates"] == []
579+
580+
def test_non_list_coords(self):
581+
geom = {"type": "Point", "coordinates": 1.1234567}
582+
rounded = round_geojson_coords(geom, precision=5)
583+
assert rounded["coordinates"] == 1.1234567
584+
585+
def test_round_coords_single_float(self):
586+
assert (
587+
round_coords(1.1234567, 5) == 1.1234567
588+
) # Non-list input returns unchanged
589+
590+
def test_round_coords_list_of_floats(self):
591+
assert round_coords([1.1234567, 2.9876543], 5) == [1.12346, 2.98765]
592+
593+
def test_round_coords_tuple_of_floats(self):
594+
assert round_coords((1.1234567, 2.9876543), 5) == [1.12346, 2.98765]
595+
596+
def test_round_coords_nested_lists(self):
597+
coords = [[[1.1234567, 2.9876543], [3.1111111, 4.9999999]]]
598+
expected = [[[1.12346, 2.98765], [3.11111, 5.0]]]
599+
assert round_coords(coords, 5) == expected
600+
601+
def test_round_coords_empty_list(self):
602+
assert round_coords([], 5) == []
603+
604+
def test_round_coords_non_numeric(self):
605+
assert round_coords("not_a_number", 5) == "not_a_number"
606+
607+
def test_round_coords_mixed_types(self):
608+
coords = [1.1234567, "foo", 2.9876543]
609+
expected = [1.12346, "foo", 2.98765]
610+
assert round_coords(coords, 5) == expected

0 commit comments

Comments
 (0)