def create_canned_project() -> Tuple[Project, User]: """ Generates a canned project in the DB to help with integration tests """ test_aoi_geojson = geojson.loads(json.dumps(get_canned_json("test_aoi.json"))) task_feature = geojson.loads(json.dumps(get_canned_json("splittable_task.json"))) task_non_square_feature = geojson.loads( json.dumps(get_canned_json("non_square_task.json")) ) test_user = create_canned_user() test_project_dto = DraftProjectDTO() test_project_dto.project_name = "Test" test_project_dto.user_id = test_user.id test_project_dto.area_of_interest = test_aoi_geojson test_project = Project() test_project.create_draft_project(test_project_dto) test_project.set_project_aoi(test_project_dto) test_project.total_tasks = 2 # Setup test task test_task = Task.from_geojson_feature(1, task_feature) test_task.task_status = TaskStatus.MAPPED.value test_task.mapped_by = test_user.id test_task.is_square = True test_task2 = Task.from_geojson_feature(2, task_non_square_feature) test_task2.task_status = TaskStatus.READY.value test_task2.is_square = False test_project.tasks.append(test_task) test_project.tasks.append(test_task2) test_project.create() return test_project, test_user
def test_cant_add_task_if_feature_geometry_is_invalid(self): # Arrange invalid_feature = geojson.loads( '{"geometry": {"coordinates": [[[[-4.0237, 56.0904], [-3.9111, 56.1715],' '[-3.8122, 56.098], [-4.0237]]]], "type": "MultiPolygon"}, "properties":' '{"x": 2402, "y": 1736, "zoom": 12}, "type": "Feature"}') with self.assertRaises(InvalidGeoJson): Task.from_geojson_feature(1, invalid_feature)
def test_cant_add_task_if_feature_has_missing_properties(self): # Missing zoom invalid_properties = geojson.loads( '{"geometry": {"coordinates": [[[[-4.0237, 56.0904], [-3.9111, 56.1715],' '[-3.8122, 56.098], [-4.0237, 56.0904]]]], "type": "MultiPolygon"},' '"properties": {"x": 2402, "y": 1736}, "type": "Feature"}') with self.assertRaises(InvalidData): Task.from_geojson_feature(1, invalid_properties)
def test_cant_add_task_if_not_supplied_feature_type(self): # Arrange invalid_feature = geojson.MultiPolygon([[(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 10.33)]]) # Arrange with self.assertRaises(InvalidGeoJson): Task.from_geojson_feature(1, invalid_feature)
def split_task(split_task_dto: SplitTaskDTO) -> TaskDTOs: """ Replaces a task square with 4 smaller tasks at the next OSM tile grid zoom level Validates that task is: - locked for mapping by current user :param split_task_dto: :return: new tasks in a DTO """ # get the task to be split original_task = Task.get(split_task_dto.task_id, split_task_dto.project_id) if original_task is None: raise NotFound() original_geometry = shape.to_shape(original_task.geometry) # check its locked for mapping by the current user if TaskStatus( original_task.task_status) != TaskStatus.LOCKED_FOR_MAPPING: raise SplitServiceError( "Status must be LOCKED_FOR_MAPPING to split") if original_task.locked_by != split_task_dto.user_id: raise SplitServiceError( "Attempting to split a task owned by another user") # create new geometries from the task geometry try: new_tasks_geojson = SplitService._create_split_tasks( original_task.x, original_task.y, original_task.zoom, original_task) except Exception as e: raise SplitServiceError(f"Error splitting task{str(e)}") # create new tasks from the new geojson i = Task.get_max_task_id_for_project(split_task_dto.project_id) new_tasks = [] new_tasks_dto = [] for new_task_geojson in new_tasks_geojson: # Sanity check: ensure the new task geometry intersects the original task geometry new_geometry = shapely_shape(new_task_geojson.geometry) if not new_geometry.intersects(original_geometry): raise InvalidGeoJson( "New split task does not intersect original task") # insert new tasks into database i = i + 1 new_task = Task.from_geojson_feature(i, new_task_geojson) new_task.project_id = split_task_dto.project_id new_task.task_status = TaskStatus.READY.value new_task.create() new_task.task_history.extend(original_task.copy_task_history()) if new_task.task_history: new_task.clear_task_lock() # since we just copied the lock new_task.set_task_history(TaskAction.STATE_CHANGE, split_task_dto.user_id, None, TaskStatus.SPLIT) new_task.set_task_history(TaskAction.STATE_CHANGE, split_task_dto.user_id, None, TaskStatus.READY) new_task.task_status = TaskStatus.READY.value new_tasks.append(new_task) new_task.update() new_tasks_dto.append( new_task.as_dto_with_instructions( split_task_dto.preferred_locale)) # delete original task from the database try: original_task.delete() except Exception: db.session.rollback() # Ensure the new tasks are cleaned up for new_task in new_tasks: new_task.delete() db.session.commit() raise # update project task counts project = Project.get(split_task_dto.project_id) project.total_tasks = project.tasks.count() # update bad imagery because we may have split a bad imagery tile project.tasks_bad_imagery = project.tasks.filter( Task.task_status == TaskStatus.BADIMAGERY.value).count() project.save() # return the new tasks in a DTO task_dtos = TaskDTOs() task_dtos.tasks = new_tasks_dto return task_dtos