Skip to content

Commit c1d8eb4

Browse files
authored
Merge pull request #1 from mapswipe/refactor-python-workers
Refactor python workers
2 parents aefd04e + 3b6b145 commit c1d8eb4

File tree

9 files changed

+183
-37
lines changed

9 files changed

+183
-37
lines changed

docker-compose.development.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ services:
2525
- manager_dashboard
2626

2727
postgres:
28-
container_name: postgres_mapswipe_workers
28+
container_name: postgres
2929
build:
3030
context: postgres/
3131
dockerfile: Dockerfile-dev
@@ -39,6 +39,6 @@ services:
3939
- ./postgres-data:/var/lib/postgresql/mapswipe
4040
restart: unless-stopped
4141
ports:
42-
- "5431:5432"
42+
- "${POSTGRES_PORT}:5432"
4343
networks:
4444
- postgres

mapswipe_workers/.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ repos:
44
rev: 22.3.0
55
hooks:
66
- id: black
7-
- repo: https://gitlab.com/pycqa/flake8
7+
- repo: https://github.com/PyCQA/flake8/
88
rev: 3.8.3
99
hooks:
1010
- id: flake8
1111
- repo: https://github.com/PyCQA/isort
12-
rev: 5.5.2
12+
rev: 5.12.0
1313
hooks:
1414
- id: isort

mapswipe_workers/mapswipe_workers/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import os
22

3+
from dotenv import load_dotenv
4+
5+
load_dotenv()
6+
37
FIREBASE_API_KEY = os.environ["FIREBASE_API_KEY"]
48
FIREBASE_DB = os.getenv("FIREBASE_DB", default="mapswipe")
59

mapswipe_workers/mapswipe_workers/project_types/tile_classification/project.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from mapswipe_workers.firebase.firebase import Firebase
22
from mapswipe_workers.project_types.base.project import BaseProject
3-
43
from mapswipe_workers.project_types.base.tile_server import BaseTileServer
5-
6-
from mapswipe_workers.utils.validate_input import validate_geometries
4+
from mapswipe_workers.utils.validate_input import (
5+
save_geojson_to_file,
6+
validate_geometries,
7+
)
78

89

910
class TileClassification(BaseProject):
@@ -15,9 +16,15 @@ def __init__(self, project_draft: dict):
1516
self.tileServer = vars(BaseTileServer(project_draft["tileServer"]))
1617

1718
def validate_geometries(self):
18-
wkt_geometry, self.validInputGeometries = validate_geometries(self.projectId, self.geometry, self.zoomLevel)
19+
self.validInputGeometries = save_geojson_to_file(self.projectId, self.geometry)
20+
wkt_geometry, self.validInputGeometries = validate_geometries(
21+
self.projectId, self.validInputGeometries, self.zoomLevel
22+
)
1923
return wkt_geometry
2024

25+
def create_groups(self):
26+
pass
27+
2128
def save_project_to_firebase(self, project):
2229
firebase = Firebase()
2330
firebase.save_project_to_firebase(project)
@@ -27,4 +34,6 @@ def save_groups_to_firebase(self, projectId: str, groups: list):
2734
firebase.save_groups_to_firebase(projectId, groups)
2835

2936
def save_tasks_to_firebase(self, projectId: str, tasks: list):
30-
pass
37+
pass
38+
39+

mapswipe_workers/mapswipe_workers/utils/validate_input.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
11
import json
22
import os
33

4-
from mapswipe_workers.definitions import CustomError, logger, MAX_INPUT_GEOMETRIES, DATA_PATH
54
from osgeo import ogr, osr
65

6+
from mapswipe_workers.definitions import (
7+
DATA_PATH,
8+
MAX_INPUT_GEOMETRIES,
9+
CustomError,
10+
logger,
11+
)
712

8-
def validate_geometries(projectId, geometry, zoomLevel):
9-
raw_input_file = (
10-
f"{DATA_PATH}/input_geometries/" f"raw_input_{projectId}.geojson"
13+
14+
def save_geojson_to_file(project_id, geometry):
15+
output_file_path = (
16+
f"{DATA_PATH}/input_geometries/" f"raw_input_{project_id}.geojson"
1117
)
1218
# check if a 'data' folder exists and create one if not
1319
if not os.path.isdir("{}/input_geometries".format(DATA_PATH)):
1420
os.mkdir("{}/input_geometries".format(DATA_PATH))
1521

1622
# write string to geom file
17-
with open(raw_input_file, "w") as geom_file:
23+
with open(output_file_path, "w") as geom_file:
1824
json.dump(geometry, geom_file)
25+
return output_file_path
26+
1927

28+
def validate_geometries(projectId, zoomLevel, input_file_path):
2029
driver = ogr.GetDriverByName("GeoJSON")
21-
datasource = driver.Open(raw_input_file, 0)
30+
datasource = driver.Open(input_file_path, 0)
2231

2332
try:
2433
layer = datasource.GetLayer()
@@ -147,11 +156,9 @@ def validate_geometries(projectId, geometry, zoomLevel):
147156
del datasource
148157
del layer
149158

150-
logger.info(
151-
f"{projectId}" f" - validate geometry - " f"input geometry is correct."
152-
)
159+
logger.info(f"{projectId}" f" - validate geometry - " f"input geometry is correct.")
153160

154161
dissolved_geometry = geometry_collection.UnionCascaded()
155162
wkt_geometry_collection = dissolved_geometry.ExportToWkt()
156163

157-
return wkt_geometry_collection, raw_input_file
164+
return wkt_geometry_collection

mapswipe_workers/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ sentry-sdk==0.18.0
1313
six==1.15.0
1414
slackclient==2.9.2
1515
xdg==4.0.1
16+
python-dotenv==1.0.0
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"createdBy": "test",
3+
"geometry": {"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[34.975833892822266,-15.899098066386088],[35.089302062988274,-15.899098066386088],[35.089302062988274,-15.820002241903946],[34.975833892822266,-15.820002241903946],[34.975833892822266,-15.899098066386088]]]},"properties":{}}]},
4+
"image": "",
5+
"lookFor": "buildings",
6+
"name": "test - Malawi (1)\ntest",
7+
"projectDetails": "test",
8+
"verificationNumber": 3,
9+
"groupSize": 120,
10+
"tileServer": {
11+
"name": "bing",
12+
"credits": "© 2019 Microsoft Corporation, Earthstar Geographics SIO"
13+
},
14+
"projectType": 1,
15+
"projectDraftId": "asdfghtjue"
16+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import json
2+
import os
3+
import unittest
4+
5+
from mapswipe_workers.project_types.tile_classification.project import (
6+
TileClassificationProject,
7+
)
8+
9+
10+
class TestInitProject(unittest.TestCase):
11+
def test_init_tile_classification_project(self):
12+
test_dir = os.path.dirname(os.path.abspath(__file__))
13+
path = "fixtures/tile_map_service_grid/projectDrafts/tile_classification.json"
14+
15+
with open(os.path.join(test_dir, path)) as json_file:
16+
project_draft = json.load(json_file)
17+
18+
self.assertIsNotNone(TileClassificationProject(project_draft=project_draft))
19+
20+
21+
if __name__ == "__main__":
22+
unittest.main()
Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,127 @@
1+
import json
12
import os
23
import unittest
3-
import json
4+
5+
from osgeo import ogr
46

57
from mapswipe_workers.definitions import CustomError
6-
from mapswipe_workers.utils.validate_input import validate_geometries
8+
from mapswipe_workers.utils.validate_input import (
9+
save_geojson_to_file,
10+
validate_geometries,
11+
)
712

813

9-
class TestValidateGeometries(unittest.TestCase):
10-
def test_multiple_geom_validation(self):
11-
pass
14+
def get_project_draft(path):
15+
# pre steps for outputting result of function
16+
test_dir = os.path.dirname(os.path.abspath(__file__))
17+
with open(os.path.join(test_dir, path)) as json_file:
18+
project_draft = json.load(json_file)
19+
20+
path_to_geometries = save_geojson_to_file(1, project_draft["geometry"])
21+
return project_draft, path_to_geometries
1222

13-
# todo: all the other tests
1423

24+
class TestValidateGeometries(unittest.TestCase):
1525
def test_area_is_too_large(self):
1626
"""Test if validate_geometries throws an error
1727
if the provided geojson covers a too large area."""
1828

1929
path = (
2030
"fixtures/tile_map_service_grid/projects/projectDraft_area_too_large.json"
2131
)
22-
test_dir = os.path.dirname(os.path.abspath(__file__))
23-
with open(os.path.join(test_dir, path)) as json_file:
24-
project_draft = json.load(json_file)
25-
geometry = project_draft["geometry"]
32+
project_draft, path_to_geometries = get_project_draft(path)
33+
self.assertRaises(CustomError, validate_geometries, 1, 18, path_to_geometries)
34+
35+
def test_broken_geojson_string(self):
36+
"""Test if validate_geometries throws an error
37+
if the provided geojson string is broken.
38+
This means we can't create the geo data layer with ogr."""
39+
40+
path = (
41+
"fixtures/tile_map_service_grid/projects/projectDraft_broken_geojson.json"
42+
)
43+
project_draft, path_to_geometries = get_project_draft(path)
44+
self.assertRaises(CustomError, validate_geometries, 1, 18, path_to_geometries)
45+
46+
def test_feature_is_none(self):
47+
"""Test if validate_geometries throws an error
48+
if the provided geojson contains a not defined feature."""
49+
50+
path = (
51+
"fixtures/tile_map_service_grid/projects/projectDraft_feature_is_none.json"
52+
)
53+
project_draft, path_to_geometries = get_project_draft(path)
54+
self.assertRaises(CustomError, validate_geometries, 1, 18, path_to_geometries)
55+
56+
def test_no_features(self):
57+
"""Test if validate_geometries throws an error
58+
if the provided geojson contains no features."""
59+
60+
path = "fixtures/tile_map_service_grid/projects/projectDraft_no_features.json"
61+
project_draft, path_to_geometries = get_project_draft(path)
62+
self.assertRaises(CustomError, validate_geometries, 1, 18, path_to_geometries)
63+
64+
def test_single_geom_validation(self):
65+
path = "fixtures/completeness/projectDraft_single.json"
66+
project_draft, path_to_geometries = get_project_draft(path)
67+
68+
path = "fixtures/completeness/single_polygon.geojson"
69+
driver = ogr.GetDriverByName("GeoJSON")
70+
datasource = driver.Open(path, 0)
71+
72+
geometry_collection = ogr.Geometry(ogr.wkbMultiPolygon)
73+
# Get the data layer
74+
layer = datasource.GetLayer()
75+
for feature in layer:
76+
feat_geom = feature.GetGeometryRef()
77+
geom_name = feat_geom.GetGeometryName()
78+
# add geometry to geometry collection
79+
if geom_name == "MULTIPOLYGON":
80+
for multi in feat_geom:
81+
geometry_collection.AddGeometry(multi)
82+
if geom_name == "POLYGON":
83+
geometry_collection.AddGeometry(feat_geom)
84+
85+
dissolved_geometry = geometry_collection.UnionCascaded()
86+
wkt_geometry_collection = dissolved_geometry.ExportToWkt()
87+
88+
# results coming from the validate_geometries function
89+
wkt = validate_geometries(1, 18, path_to_geometries)
90+
# Test that sequence first contains the same elements as second
91+
self.assertCountEqual(wkt, wkt_geometry_collection)
92+
93+
def test_multiple_geom_validation(self):
94+
path = "fixtures/completeness/projectDraft_overlappingGeom.json"
95+
project_draft, path_to_geometries = get_project_draft(path)
2696

27-
self.assertRaises(CustomError, validate_geometries, 1, geometry, 18)
97+
# prepare data that is expected
98+
path = "fixtures/completeness/overlappingGeoms.geojson"
99+
driver = ogr.GetDriverByName("GeoJSON")
100+
datasource = driver.Open(path, 0)
28101

29-
"""
30-
project = create_project(path)
102+
geometry_collection = ogr.Geometry(ogr.wkbMultiPolygon)
103+
# Get the data layer
104+
layer = datasource.GetLayer()
105+
for feature in layer:
106+
feat_geom = feature.GetGeometryRef()
107+
geom_name = feat_geom.GetGeometryName()
108+
# add geometry to geometry collection
109+
# check if input geom is multipolygon or polygon
110+
if geom_name == "MULTIPOLYGON":
111+
for multi in feat_geom:
112+
geometry_collection.AddGeometry(multi)
113+
# apply union function if multiple geoms of polygons overlap
114+
dissolved_geometry = geometry_collection.UnionCascaded()
115+
wkt_geometry_collection = dissolved_geometry.ExportToWkt()
116+
if geom_name == "POLYGON":
117+
geometry_collection.AddGeometry(feat_geom)
118+
wkt_geometry_collection = geometry_collection.ExportToWkt()
31119

120+
# results coming from the validate_geometries function
121+
wkt = validate_geometries(1, 18, path_to_geometries)
122+
# Test that sequence first contains the same elements as second
123+
self.assertEqual(wkt, wkt_geometry_collection)
32124

33-
34-
# we expect that the function raises a CustomError due to the fact
35-
# that the area is too large
36-
self.assertRaises(CustomError, project.validate_geometries)
37-
"""
38125

39126
if __name__ == "__main__":
40127
unittest.main()

0 commit comments

Comments
 (0)