Skip to content

Commit 5e54b11

Browse files
authored
Merge pull request #10 from TaskarCenterAtUW/dev
Dev to Main
2 parents 199183d + 3721ba5 commit 5e54b11

File tree

10 files changed

+127
-26
lines changed

10 files changed

+127
-26
lines changed

.github/workflows/unit_tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
branches-ignore:
66
- '**'
77
pull_request:
8-
branches: [main, dev, stage]
8+
branches: [dev]
99

1010
jobs:
1111
UnitTest:
@@ -27,7 +27,7 @@ jobs:
2727
2828
- name: Run tests with coverage
2929
run: |
30-
coverage run --source=src -m unittest discover -s tests/
30+
coverage run --source=src/osw_incline -m unittest discover -s tests/
3131
coverage xml
3232
3333
- name: Check coverage

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1+
### 0.0.3
2+
3+
- Fixed [Task-1369](https://dev.azure.com/TDEI-UW/TDEI/_workitems/edit/1369/).
4+
- Updated the code to handle incline less than -1 ot greater than 1.
5+
- Updated unit test cases
6+
17
### 0.0.2
8+
29
- Fixed [Task-1347](https://dev.azure.com/TDEI-UW/TDEI/_workitems/edit/1347/).
3-
- Fixed package to removing the additional keys from the geojson files.
10+
- Fixed package to removing the additional keys from the geojson files.
411
- Introduced garbage collection to free up memory.
512
- Added ability to skip the tags which are already present in the edges file.
613
- Added ability to process the incline tags in batch processing.
714

8-
915
### 0.0.1
16+
1017
- Introduces osw_inclination package which calculates the inclination of the sidewalk based on the DEM data.
1118
- Added example.py file which demonstrates how to use the package.
1219
- Added unit test cases.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ OSW-Incline includes a suite of unit tests to ensure correct functionality. You
9999
python -m unittest discover -v tests
100100

101101
# To run the unit test cases with coverage
102-
python -m coverage run --source=src -m unittest discover -v tests
102+
python -m coverage run --source=src/osw_incline -m unittest discover -v tests
103103

104104
# To generate the coverage report
105105
python -m coverage report

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ rasterio
55
numpy
66
scipy
77
jinja2
8-
coverage
8+
coverage
9+
requests

src/example.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import os
2-
3-
from jinja2.ext import debug
4-
52
from osw_incline import OSWIncline
6-
3+
from utils import download_dems, unzip_dataset, remove_unzip_dataset
74

85
PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
9-
DOWNLOADS_DIR = os.path.join(PARENT_DIR, 'downloads')
6+
DEM_DIR = os.path.join(PARENT_DIR, 'downloads/dems')
7+
ASSETS_DIR = os.path.join(PARENT_DIR, 'tests/assets')
108

119

1210
def test_incline():
13-
dem_files = [f'{DOWNLOADS_DIR}/dems/n48w123.tif']
14-
nodes_file = f'{DOWNLOADS_DIR}/geojson_renton_hth/renton_hth.nodes.geojson'
15-
edges_file = f'{DOWNLOADS_DIR}/geojson_renton_hth/renton_hth.edges.geojson'
11+
dem_files = [f'{DEM_DIR}/n48w123.tif']
12+
nodes_file = f'{ASSETS_DIR}/medium/wa.seattle.graph.nodes.geojson'
13+
edges_file = f'{ASSETS_DIR}/medium/wa.seattle.graph.edges.geojson'
1614
incline = OSWIncline(dem_files=dem_files, nodes_file=nodes_file, edges_file=edges_file, debug=True)
1715
result = incline.calculate()
1816
print(result)
1917

2018

21-
22-
2319
if __name__ == '__main__':
20+
download_dems()
21+
unzip_dataset()
2422
test_incline()
25-
23+
remove_unzip_dataset()

src/osw_incline/dem_processor.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def process(self, nodes_path, edges_path, skip_existing_tags=False, batch_proces
4242
"""
4343
if batch_processing:
4444
edges = list(self.OG.G.edges(data=True)) # Get all edges, even if fewer than batch_size
45-
self._process_in_batches(edges, dem, batch_size=1000, skip_existing_tags=skip_existing_tags)
45+
self._process_in_batches(edges, dem, batch_size=10000, skip_existing_tags=skip_existing_tags)
4646
else:
4747
"""
4848
Option 2:
@@ -57,10 +57,12 @@ def process(self, nodes_path, edges_path, skip_existing_tags=False, batch_proces
5757
if 'geometry' in d:
5858
if skip_existing_tags:
5959
if 'incline' in d and d['incline'] is not None:
60+
if -1 <= d['incline'] <= 1:
61+
del d['incline']
6062
# If incline already exists, skip
6163
continue
6264
incline = self.infer_incline(linestring=d['geometry'], dem=dem, precision=3)
63-
if incline is not None:
65+
if incline is not None and -1 <= incline <= 1:
6466
# Add incline to the edge properties
6567
d['incline'] = incline
6668
else:
@@ -81,18 +83,20 @@ def process(self, nodes_path, edges_path, skip_existing_tags=False, batch_proces
8183

8284
gc.disable()
8385

84-
def _process_in_batches(self, edges, dem, batch_size=1000, skip_existing_tags=False):
86+
def _process_in_batches(self, edges, dem, batch_size=10000, skip_existing_tags=False):
8587
# Process edges in batches
8688
for i in range(0, len(edges), batch_size):
8789
batch = edges[i:i + batch_size]
8890
for u, v, d in batch:
8991
if 'geometry' in d:
9092
if skip_existing_tags:
9193
if 'incline' in d and d['incline'] is not None:
94+
if -1 <= d['incline'] <= 1:
95+
del d['incline']
9296
# If incline already exists, skip
9397
continue
9498
incline = self.infer_incline(linestring=d['geometry'], dem=dem, precision=3)
95-
if incline is not None:
99+
if incline is not None and -1 <= incline <= 1:
96100
d['incline'] = incline
97101
# Trigger garbage collection after each batch
98102
gc.collect()

src/osw_incline/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.0.2'
1+
__version__ = '0.0.3'

src/utils/__init__.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import shutil
2+
import zipfile
3+
import requests
4+
from pathlib import Path
5+
6+
DOWNLOAD_DIR = Path(f'{Path.cwd()}/downloads')
7+
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
8+
DEM_DIR = Path(DOWNLOAD_DIR, 'dems')
9+
DEM_DIR.mkdir(parents=True, exist_ok=True)
10+
11+
ASSETS_DIR = Path(f'{Path.cwd()}/tests/assets')
12+
13+
14+
def download_dems():
15+
dem_file = 'n48w123.tif'
16+
file_path = Path(f'{DEM_DIR}/{dem_file}')
17+
18+
# Download DEM file if not present
19+
if not file_path.is_file():
20+
URL = 'https://prd-tnm.s3.amazonaws.com/StagedProducts/Elevation/13/TIFF/current/n48w123/USGS_13_n48w123.tif'
21+
with requests.get(URL, stream=True) as r:
22+
r.raise_for_status()
23+
with open(file_path, 'wb') as f:
24+
for chunk in r.iter_content(chunk_size=8192):
25+
f.write(chunk)
26+
27+
28+
def unzip_dataset():
29+
zip_path = Path(f'{ASSETS_DIR}/medium.zip')
30+
extract_to = Path(f'{ASSETS_DIR}/medium')
31+
32+
# Unzip medium.zip to the specified extraction path
33+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
34+
zip_ref.extractall(extract_to)
35+
36+
37+
def remove_unzip_dataset():
38+
extract_to = Path(f'{ASSETS_DIR}/medium')
39+
if extract_to.exists():
40+
shutil.rmtree(extract_to, ignore_errors=True)

tests/assets/medium.zip

28.7 MB
Binary file not shown.

tests/test_osw_incline.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import json
12
import unittest
23
from pathlib import Path
34
from src.osw_incline import OSWIncline
45
from src.osw_incline.logger import Logger
56
from unittest.mock import patch, MagicMock
67
from src.osw_incline.osm_graph import OSMGraph
8+
from src.utils import download_dems, unzip_dataset, remove_unzip_dataset
9+
10+
ASSETS_DIR = f'{Path.cwd()}/tests/assets'
11+
DEM_DIR = f'{Path.cwd()}/downloads/dems'
712

813

914
class TestOSWIncline(unittest.TestCase):
@@ -53,7 +58,8 @@ def test_calculate_success(self, mock_logger_info, mock_time, mock_dem_processor
5358
@patch('src.osw_incline.dem_processor.DEMProcessor.process', return_value=None)
5459
@patch('time.time', side_effect=[1, 5]) # Simulate time taken for the calculation
5560
@patch.object(Logger, 'info') # Mock the Logger to capture log calls
56-
def test_calculate_success_with_skip_existing_tags(self, mock_logger_info, mock_time, mock_dem_processor, mock_osm_graph):
61+
def test_calculate_success_with_skip_existing_tags(self, mock_logger_info, mock_time, mock_dem_processor,
62+
mock_osm_graph):
5763
result = self.osw_incline.calculate(skip_existing_tags=True)
5864

5965
# Check if the process was successful
@@ -76,7 +82,7 @@ def test_calculate_success_with_skip_existing_tags(self, mock_logger_info, mock_
7682
@patch('time.time', side_effect=[1, 5]) # Simulate time taken for the calculation
7783
@patch.object(Logger, 'info') # Mock the Logger to capture log calls
7884
def test_calculate_success_with_batch_processing(self, mock_logger_info, mock_time, mock_dem_processor,
79-
mock_osm_graph):
85+
mock_osm_graph):
8086
result = self.osw_incline.calculate(batch_processing=True)
8187

8288
# Check if the process was successful
@@ -98,8 +104,9 @@ def test_calculate_success_with_batch_processing(self, mock_logger_info, mock_ti
98104
@patch('src.osw_incline.dem_processor.DEMProcessor.process', return_value=None)
99105
@patch('time.time', side_effect=[1, 5]) # Simulate time taken for the calculation
100106
@patch.object(Logger, 'info') # Mock the Logger to capture log calls
101-
def test_calculate_success_with_batching_and_skip_existing_tags(self, mock_logger_info, mock_time, mock_dem_processor,
102-
mock_osm_graph):
107+
def test_calculate_success_with_batching_and_skip_existing_tags(self, mock_logger_info, mock_time,
108+
mock_dem_processor,
109+
mock_osm_graph):
103110
result = self.osw_incline.calculate(skip_existing_tags=True, batch_processing=True)
104111

105112
# Check if the process was successful
@@ -169,5 +176,49 @@ def test_debug_logging_on_initialization(self, mock_logger_debug):
169176
mock_logger_debug.assert_called_once_with('Debug mode is enabled')
170177

171178

179+
class TestOSWInclineIntegration(unittest.TestCase):
180+
def setUp(self):
181+
self.dem_files = [f'{DEM_DIR}/n48w123.tif']
182+
# Download DEM file if not present
183+
download_dems()
184+
# unzip medium.zip to the specified extraction path
185+
unzip_dataset()
186+
self.extract_to = Path(f'{ASSETS_DIR}/medium')
187+
self.nodes_file = f'{self.extract_to}/wa.seattle.graph.nodes.geojson'
188+
self.edges_file = f'{self.extract_to}/wa.seattle.graph.edges.geojson'
189+
190+
def tearDown(self):
191+
remove_unzip_dataset()
192+
193+
def test_entire_process(self):
194+
incline = OSWIncline(
195+
dem_files=self.dem_files,
196+
nodes_file=str(self.nodes_file),
197+
edges_file=str(self.edges_file),
198+
debug=True
199+
)
200+
result = incline.calculate()
201+
self.assertTrue(result)
202+
203+
def test_incline_tag_added(self):
204+
incline = OSWIncline(
205+
dem_files=self.dem_files,
206+
nodes_file=self.nodes_file,
207+
edges_file=self.edges_file,
208+
debug=True
209+
)
210+
result = incline.calculate()
211+
self.assertTrue(result)
212+
213+
with open(self.edges_file, 'r') as f:
214+
edges_data = json.load(f)
215+
216+
for feature in edges_data['features']:
217+
if 'incline' in feature['properties']:
218+
incline_value = feature['properties']['incline']
219+
self.assertIsInstance(incline_value, (int, float), 'Incline should be an integer or float.')
220+
self.assertTrue(-1 <= incline_value <= 1, 'Incline should be between -1 and 1.')
221+
222+
172223
if __name__ == '__main__':
173224
unittest.main()

0 commit comments

Comments
 (0)