@@ -25,25 +25,7 @@ def save_geojson_to_file(project_id, geometry):
2525 return output_file_path
2626
2727
28- def validate_geometries (projectId , zoomLevel , input_file_path ):
29- driver = ogr .GetDriverByName ("GeoJSON" )
30- datasource = driver .Open (input_file_path , 0 )
31-
32- try :
33- layer = datasource .GetLayer ()
34- except AttributeError :
35- logger .warning (
36- f"{ projectId } "
37- f" - validate geometry - "
38- f"Could not get layer for datasource"
39- )
40- raise CustomError (
41- "Could not get layer for datasource."
42- "Your geojson file is not correctly defined."
43- "Check if you can open the file e.g. in QGIS. "
44- )
45-
46- # check if layer is empty
28+ def check_if_layer_is_empty (projectId , layer ):
4729 if layer .GetFeatureCount () < 1 :
4830 logger .warning (
4931 f"{ projectId } "
@@ -53,91 +35,64 @@ def validate_geometries(projectId, zoomLevel, input_file_path):
5335 )
5436 raise CustomError ("Empty file. " )
5537
56- # check if more than 1 geometry is provided
57- elif layer .GetFeatureCount () > MAX_INPUT_GEOMETRIES :
38+
39+ def check_if_layer_has_too_many_geometries (projectId , multi_polygon : ogr .Geometry ):
40+ if multi_polygon .GetGeometryCount () > MAX_INPUT_GEOMETRIES :
5841 logger .warning (
5942 f"{ projectId } "
6043 f" - validate geometry - "
61- f"Input file contains more than { MAX_INPUT_GEOMETRIES } geometries. "
62- f"Make sure to provide less than { MAX_INPUT_GEOMETRIES } geometries ."
44+ f"Input file contains more than { MAX_INPUT_GEOMETRIES } individuals polygons. "
45+ f"Make sure to provide less than { MAX_INPUT_GEOMETRIES } polygons ."
6346 )
6447 raise CustomError (
6548 f"Input file contains more than { MAX_INPUT_GEOMETRIES } geometries. "
6649 "You can split up your project into two or more projects. "
6750 "This can reduce the number of input geometries. "
6851 )
6952
70- project_area = 0
71- geometry_collection = ogr .Geometry (ogr .wkbMultiPolygon )
72- # check if the input geometry is a valid polygon
73- for feature in layer :
7453
75- try :
76- feat_geom = feature .GetGeometryRef ()
77- geom_name = feat_geom .GetGeometryName ()
78- except AttributeError :
79- logger .warning (
80- f"{ projectId } "
81- f" - validate geometry - "
82- f"feature geometry is not defined. "
83- )
84- raise CustomError (
85- "At least one feature geometry is not defined."
86- "Check in your input file if all geometries are defined "
87- "and no NULL geometries exist. "
88- )
89- # add geometry to geometry collection
90- if geom_name == "MULTIPOLYGON" :
91- for singlepart_polygon in feat_geom :
92- geometry_collection .AddGeometry (singlepart_polygon )
93- if geom_name == "POLYGON" :
94- geometry_collection .AddGeometry (feat_geom )
95- if not feat_geom .IsValid ():
96- logger .warning (
97- f"{ projectId } "
98- f" - validate geometry - "
99- f"Geometry is not valid: { geom_name } . "
100- f"Tested with IsValid() ogr method. "
101- f"Probably self-intersections."
102- )
103- raise CustomError (f"Geometry is not valid: { geom_name } . " )
104-
105- # we accept only POLYGON or MULTIPOLYGON geometries
106- if geom_name != "POLYGON" and geom_name != "MULTIPOLYGON" :
107- logger .warning (
108- f"{ projectId } "
109- f" - validate geometry - "
110- f"Invalid geometry type: { geom_name } . "
111- f'Please provide "POLYGON" or "MULTIPOLYGON"'
112- )
113- raise CustomError (
114- f"Invalid geometry type: { geom_name } . "
115- "Make sure that all features in your dataset"
116- "are of type POLYGON or MULTIPOLYGON. "
117- )
118-
119- # check size of project make sure its smaller than 5,000 sqkm
120- # for doing this we transform the geometry
121- # into Mollweide projection (EPSG Code 54009)
122- source = feat_geom .GetSpatialReference ()
123- target = osr .SpatialReference ()
124- target .ImportFromProj4 (
125- "+proj=moll +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
54+ def check_if_zoom_level_is_too_high (zoomLevel ):
55+ if zoomLevel > 22 :
56+ raise CustomError (f"zoom level is too large (max: 22): { zoomLevel } ." )
57+
58+
59+ def check_if_geom_type_is_valid (projectId , geom_name ):
60+ # we accept only POLYGON or MULTIPOLYGON geometries
61+ if geom_name != "POLYGON" and geom_name != "MULTIPOLYGON" :
62+ logger .warning (
63+ f"{ projectId } "
64+ f" - validate geometry - "
65+ f"Invalid geometry type: { geom_name } . "
66+ f'Please provide "POLYGON" or "MULTIPOLYGON"'
67+ )
68+ raise CustomError (
69+ f"Invalid geometry type: { geom_name } . "
70+ "Make sure that all features in your dataset"
71+ "are of type POLYGON or MULTIPOLYGON. "
12672 )
12773
128- transform = osr .CoordinateTransformation (source , target )
129- feat_geom .Transform (transform )
130- project_area += feat_geom .GetArea () / 1000000
13174
132- # max zoom level is 22
133- if zoomLevel > 22 :
134- raise CustomError (f"zoom level is too large (max: 22): { zoomLevel } ." )
75+ def check_if_geom_is_valid (projectId , feat_geom ):
76+ if not feat_geom .IsValid ():
77+ logger .warning (
78+ f"{ projectId } "
79+ f" - validate geometry - "
80+ f"Geometry is not valid:"
81+ f"Tested with IsValid() ogr method. "
82+ f"Probably self-intersections."
83+ )
84+
85+ raise CustomError (f"Geometry is not valid. " )
86+
87+
88+ def check_if_project_area_is_too_big (projectId , project_area , zoomLevel ):
89+ """We calculate the max area based on zoom level.
90+ This is an approximation to restrict the project size
91+ in respect to the number of tasks.
92+ At zoom level 22 the max area is set to 20 square kilometers.
93+ For zoom level 18 this will result in a max area of 5,120 square kilometers.
94+ """
13595
136- # We calculate the max area based on zoom level.
137- # This is an approximation to restrict the project size
138- # in respect to the number of tasks.
139- # At zoom level 22 the max area is set to 20 square kilometers.
140- # For zoom level 18 this will result in an max area of 5,120 square kilometers.
14196 max_area = 5 * 4 ** (23 - zoomLevel )
14297
14398 if project_area > max_area :
@@ -153,12 +108,107 @@ def validate_geometries(projectId, zoomLevel, input_file_path):
153108 "You can split your project into smaller projects and resubmit."
154109 )
155110
111+
112+ def load_geojson_to_ogr (projectId , input_file_path ):
113+ driver = ogr .GetDriverByName ("GeoJSON" )
114+ datasource = driver .Open (input_file_path , 0 )
115+
116+ try :
117+ return datasource .GetLayer (), datasource
118+ except AttributeError :
119+ logger .warning (
120+ f"{ projectId } "
121+ f" - validate geometry - "
122+ f"Could not get layer for datasource"
123+ )
124+ raise CustomError (
125+ "Could not get layer for datasource."
126+ "Your geojson file is not correctly defined."
127+ "Check if you can open the file e.g. in QGIS. "
128+ )
129+
130+
131+ def get_feature_geometry (projectId , feature ):
132+ try :
133+ feat_geom = feature .GetGeometryRef ()
134+ geom_name = feat_geom .GetGeometryName ()
135+ except AttributeError :
136+ logger .warning (
137+ f"{ projectId } "
138+ f" - validate geometry - "
139+ f"feature geometry is not defined. "
140+ )
141+ raise CustomError (
142+ "At least one feature geometry is not defined."
143+ "Check in your input file if all geometries are defined "
144+ "and no NULL geometries exist. "
145+ )
146+ return feat_geom , geom_name
147+
148+
149+ def add_geom_to_multipolygon (multi_polygon , feat_geom , geom_name ):
150+ if geom_name == "MULTIPOLYGON" :
151+ for singlepart_polygon in feat_geom :
152+ multi_polygon .AddGeometry (singlepart_polygon )
153+ if geom_name == "POLYGON" :
154+ multi_polygon .AddGeometry (feat_geom )
155+ return multi_polygon
156+
157+
158+ def calculate_polygon_area_in_km (geometry ):
159+ """Calculate the area of a polygon in Mollweide projection (EPSG Code: 54009)."""
160+ source = geometry .GetSpatialReference ()
161+ target = osr .SpatialReference ()
162+ target .ImportFromProj4 (
163+ "+proj=moll +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
164+ )
165+
166+ transform = osr .CoordinateTransformation (source , target )
167+ geometry .Transform (transform )
168+ return geometry .GetArea () / 1000000
169+
170+
171+ def build_multipolygon_from_layer_geometries (projectId , layer ):
172+ """
173+ Collect all geometries from input collection into one Multipolygon,
174+ additionally get the total area covered by all polygons.
175+ """
176+ project_area = 0
177+ multi_polygon = ogr .Geometry (ogr .wkbMultiPolygon )
178+
179+ # check if the input geometry is a valid polygon
180+ for feature in layer :
181+ feat_geom , geom_name = get_feature_geometry (projectId , feature )
182+
183+ check_if_geom_type_is_valid (projectId , geom_name )
184+ check_if_geom_is_valid (projectId , feat_geom )
185+
186+ multi_polygon = add_geom_to_multipolygon (multi_polygon , feat_geom , geom_name )
187+ project_area += calculate_polygon_area_in_km (feat_geom )
188+
189+ return multi_polygon , project_area
190+
191+
192+ def validate_and_collect_geometries_to_multipolyon (projectId , zoomLevel , input_file_path ):
193+ """Validate all geometries contained in input file and collect them to a single multi polygon."""
194+ layer , datasource = load_geojson_to_ogr (projectId , input_file_path )
195+
196+ # check if inputs fit constraints
197+ check_if_layer_is_empty (projectId , layer )
198+ check_if_zoom_level_is_too_high (zoomLevel )
199+
200+ multi_polygon , project_area = build_multipolygon_from_layer_geometries (projectId , layer )
201+
202+ check_if_layer_has_too_many_geometries (projectId , multi_polygon )
203+ check_if_project_area_is_too_big (projectId , project_area , zoomLevel )
204+
156205 del datasource
157206 del layer
158207
159208 logger .info (f"{ projectId } " f" - validate geometry - " f"input geometry is correct." )
209+ return multi_polygon
160210
161- dissolved_geometry = geometry_collection .UnionCascaded ()
162- wkt_geometry_collection = dissolved_geometry .ExportToWkt ()
163211
164- return wkt_geometry_collection
212+ def multipolygon_to_wkt (multi_polygon ):
213+ dissolved_geometry = multi_polygon .UnionCascaded ()
214+ return dissolved_geometry .ExportToWkt ()
0 commit comments