Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change log

### 0.2.8
- Fixed [BUG 2040](https://dev.azure.com/TDEI-UW/TDEI/_workitems/edit/2040)
- Removing the width tag if the width is not float or integer
- Added unit test cases

### 0.2.7
- Fixed [BUG 1654](https://dev.azure.com/TDEI-UW/TDEI/_workitems/edit/1654)
- Added functionality to retain the `ext` tags
Expand Down
2 changes: 1 addition & 1 deletion src/osm_osw_reformatter/osw2osm/osw2osm.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def convert(self) -> Response:
Path(input_file).unlink()
resp = Response(status=True, generated_files=str(output_file))
except Exception as error:
print(error)
print(f'Error during conversion: {error}')
resp = Response(status=False, error=str(error))
finally:
gc.collect()
Expand Down
20 changes: 20 additions & 0 deletions src/osm_osw_reformatter/serializer/osm/osm_normalizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ class OSMNormalizer(ogr2osm.TranslationBase):
"living_street"
)

OSM_TAG_DATATYPES = {
'width': float,
'step_count': int,
}

def _check_datatypes(self, tags):
for key, expected_type in self.OSM_TAG_DATATYPES.items():
value = tags.get(key)
if value is not None:
try:
cast_value = expected_type(value)
if isinstance(cast_value, float) and (cast_value != cast_value): # NaN check
tags.pop(key)
else:
tags[key] = str(cast_value)
except (ValueError, TypeError):
tags.pop(key)

def filter_tags(self, tags):
'''
Override this method if you want to modify or add tags to the xml output
Expand All @@ -30,6 +48,8 @@ def filter_tags(self, tags):
# OSW fields with similar OSM field names
tags['incline'] = tags.pop('climb', '')

self._check_datatypes(tags)

return tags

def process_feature_post(self, osmgeometry, ogrfeature, ogrgeometry):
Expand Down
12 changes: 12 additions & 0 deletions src/osm_osw_reformatter/serializer/osw/osw_normalizer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import types
import math
OSW_SCHEMA_ID = "https://sidewalks.washington.edu/opensidewalks/0.2/schema.json"

class OSWWayNormalizer:
Expand Down Expand Up @@ -463,6 +464,13 @@ def _normalize_zone(self, keep_keys={}, defaults = {}):

def is_pedestrian(self):
return self.tags.get("highway", "") == "pedestrian"


def check_nan_and_raise(tag_type, temp):
if (tag_type == float or tag_type == int) and math.isnan(temp):
raise ValueError("Value cannot be NaN")
return temp


def _normalize(tags, keep_keys, defaults):
new_tags = {}
Expand All @@ -474,12 +482,14 @@ def _normalize(tags, keep_keys, defaults):
elif isinstance(tag_type[1], types.FunctionType):
temp = tag_type[1](tags[tag], tags)
if temp is not None:
check_nan_and_raise(tag_type[0], temp)
new_tags[tag_type[0]] = temp
else:
raise ValueError
else:
temp = tag_type[1](tags[tag])
if temp is not None:
check_nan_and_raise(tag_type[0], temp)
new_tags[tag_type[0]] = temp
else:
raise ValueError
Expand All @@ -489,12 +499,14 @@ def _normalize(tags, keep_keys, defaults):
elif isinstance(tag_type, types.FunctionType):
temp = tag_type(tags[tag], tags)
if temp is not None:
check_nan_and_raise(tag_type, temp)
new_tags[tag] = temp
else:
raise ValueError
else:
temp = tag_type(tags[tag])
if temp is not None:
check_nan_and_raise(tag_type, temp)
new_tags[tag] = temp
else:
raise ValueError
Expand Down
2 changes: 1 addition & 1 deletion src/osm_osw_reformatter/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.2.7'
__version__ = '0.2.8'
453 changes: 453 additions & 0 deletions tests/unit_tests/test_files/width-test.xml

Large diffs are not rendered by default.

Binary file added tests/unit_tests/test_files/width-test.zip
Binary file not shown.
36 changes: 35 additions & 1 deletion tests/unit_tests/test_osm2osw/test_osm2osw.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@
import json
import asyncio
import unittest
import math
from src.osm_osw_reformatter.osm2osw.osm2osw import OSM2OSW

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.dirname(ROOT_DIR)), 'output')
TEST_FILE = os.path.join(ROOT_DIR, 'test_files/wa.microsoft.osm.pbf')
TEST_WIDTH_FILE = os.path.join(ROOT_DIR, 'test_files/width-test.xml')


def is_valid_float(value):
try:
f = float(value)
return not math.isnan(f)
except (ValueError, TypeError):
return False

class TestOSM2OSW(unittest.IsolatedAsyncioTestCase):
def test_convert_successful(self):
osm_file_path = TEST_FILE
Expand All @@ -35,6 +44,32 @@ async def run_test():

asyncio.run(run_test())

def test_generated_with_valid_width_tag(self):
osm_file_path = TEST_FILE

async def run_test():
osm2osw = OSM2OSW(osm_file=osm_file_path, workdir=OUTPUT_DIR, prefix='test')
result = await osm2osw.convert()

self.assertEqual(len(result.generated_files), 4)

for file in result.generated_files:
if file.endswith('.geojson'):
with open(file, 'r') as f:
geojson = json.load(f)
for feature in geojson.get("features", []):
props = feature.get("properties", {})
if "width" in props:
width_val = props["width"]
self.assertTrue(
is_valid_float(width_val),
msg=f"Invalid width value '{width_val}' in file {file}"
)

os.remove(file)

asyncio.run(run_test())

def test_generated_files_include_nodes_points_edges(self):
osm_file_path = TEST_FILE

Expand Down Expand Up @@ -139,7 +174,6 @@ async def run_test():

asyncio.run(run_test())


def test_no_empty_features(self):
osm_file_path = TEST_FILE

Expand Down
23 changes: 23 additions & 0 deletions tests/unit_tests/test_osw2osm/test_osw2osm.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os
import unittest
from src.osm_osw_reformatter.osw2osm.osw2osm import OSW2OSM
import xml.etree.ElementTree as ET

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.dirname(ROOT_DIR)), 'output')
TEST_ZIP_FILE = os.path.join(ROOT_DIR, 'test_files/osw.zip')
TEST_WIDTH_ZIP_FILE = os.path.join(ROOT_DIR, 'test_files/width-test.zip')


class TestOSW2OSM(unittest.IsolatedAsyncioTestCase):
Expand All @@ -29,6 +31,27 @@ def test_generated_file_should_be_xml(self):
self.assertTrue(result.generated_files.endswith('.xml'))
os.remove(result.generated_files)

def test_generated_file_should_be_xml_and_valid_width_tag(self):
zip_file = TEST_WIDTH_ZIP_FILE
osw2osm = OSW2OSM(zip_file_path=zip_file, workdir=OUTPUT_DIR, prefix='test')
result = osw2osm.convert()
self.assertTrue(result.generated_files.endswith('.xml'))
xml_file_path = result.generated_files

tree = ET.parse(xml_file_path)
root = tree.getroot()

for tag in root.findall(".//tag[@k='width']"):
value = tag.get('v', '').strip()
self.assertNotEqual(value, '', msg="Width tag value is empty")
try:
float_val = float(value)
self.assertFalse(float_val != float_val, msg=f"Width tag value is NaN: {value}")
except ValueError:
self.fail(f"Width tag value is not a valid float: {value}")

os.remove(result.generated_files)

def test_convert_generated_files_are_string(self):
zip_file = TEST_ZIP_FILE
osw2osm = OSW2OSM(zip_file_path=zip_file, workdir=OUTPUT_DIR, prefix='test')
Expand Down
51 changes: 51 additions & 0 deletions tests/unit_tests/test_serializer/test_osm_normalizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import unittest
from src.osm_osw_reformatter.serializer.osm.osm_normalizer import OSMNormalizer

class TestOSMNormalizeWidthField(unittest.TestCase):
def setUp(self):
self.normalizer = OSMNormalizer()


def test_removes_width_when_value_is_nan_string(self):
tags = {"highway": "footway", "width": 'NaN'}
normalizer = self.normalizer.filter_tags(tags)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertNotIn('width', normalizer)


def test_removes_width_when_value_is_non_numeric_string(self):
tags = {"highway": "footway", "width": 'hello'}
normalizer = self.normalizer.filter_tags(tags)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertNotIn('width', normalizer)

def test_preserves_width_when_value_is_float(self):
tags = {"highway": "footway", "width": 1.2}
normalizer = self.normalizer.filter_tags(tags)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertIn('width', normalizer)

def test_preserves_width_when_value_is_int(self):
tags = {"highway": "footway", "width": 10}
normalizer = self.normalizer.filter_tags(tags)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertIn('width', normalizer)

def test_removes_width_when_value_is_actual_nan(self):
tags = {"highway": "footway", "width": float('nan')}
normalizer = self.normalizer.filter_tags(tags)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertNotIn('width', normalizer)

def test_preserves_width_when_value_is_float_with_string(self):
tags = {"highway": "footway", "width": '1.525'}
normalizer = self.normalizer.filter_tags(tags)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertIn('width', normalizer)
self.assertEqual(normalizer['width'], '1.525')
60 changes: 59 additions & 1 deletion tests/unit_tests/test_serializer/test_osw_normalizer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest
from src.osm_osw_reformatter.serializer.osw.osw_normalizer import OSWWayNormalizer, OSWNodeNormalizer, \
OSWPointNormalizer, tactile_paving, surface, crossing_markings, climb
OSWPointNormalizer, tactile_paving, surface, crossing_markings, climb, _normalize


class TestOSWWayNormalizer(unittest.TestCase):
Expand Down Expand Up @@ -127,5 +127,63 @@ def test_incline(self):
self.assertIsNone(climb('invalid_value', {}))


class TestNormalizeWidthField(unittest.TestCase):
def test_removes_width_when_value_is_nan_string(self):
generic_keep_keys = {"highway": str, "width": float}
generic_defaults = {}
tags = {"highway": "footway", "width": 'NaN'}
normalizer = _normalize(tags, generic_keep_keys, generic_defaults)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertNotIn('width', normalizer)

def test_removes_width_when_value_is_non_numeric_string(self):
generic_keep_keys = {"highway": str, "width": float}
generic_defaults = {}
tags = {"highway": "footway", "width": 'hello'}
normalizer = _normalize(tags, generic_keep_keys, generic_defaults)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertNotIn('width', normalizer)

def test_preserves_width_when_value_is_float(self):
generic_keep_keys = {"highway": str, "width": float}
generic_defaults = {}
tags = {"highway": "footway", "width": 1.2}
normalizer = _normalize(tags, generic_keep_keys, generic_defaults)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertIn('width', normalizer)

def test_preserves_width_when_value_is_int(self):
generic_keep_keys = {"highway": str, "width": float}
generic_defaults = {}
tags = {"highway": "footway", "width": 10}
normalizer = _normalize(tags, generic_keep_keys, generic_defaults)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertIn('width', normalizer)

def test_removes_width_when_value_is_actual_nan(self):
generic_keep_keys = {"highway": str, "width": float}
generic_defaults = {}
tags = {"highway": "footway", "width": float('nan')}
normalizer = _normalize(tags, generic_keep_keys, generic_defaults)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertNotIn('width', normalizer)

def test_preserves_width_when_value_is_float_with_string(self):
generic_keep_keys = {"highway": str, "width": float}
generic_defaults = {}
tags = {"highway": "footway", "width": '1.525'}
normalizer = _normalize(tags, generic_keep_keys, generic_defaults)
self.assertIsInstance(normalizer, dict)
self.assertIn('highway', normalizer)
self.assertIn('width', normalizer)
self.assertEqual(normalizer['width'], 1.525)



if __name__ == '__main__':
unittest.main()
Loading