示例#1
0
    def _create_split_tasks(x, y, zoom, task) -> list:
        """
        function for splitting a task square geometry into 4 smaller squares
        :param geom_to_split: {geojson.Feature} the geojson feature to b split
        :return: list of {geojson.Feature}
        """
        # If the task's geometry doesn't correspond to an OSM tile identified by an
        # x, y, zoom then we need to take a different approach to splitting
        if x is None or y is None or zoom is None:
            return SplitService._create_split_tasks_from_geometry(task)

        try:
            task_geojson = False

            if task and not task.is_square:
                query = db.session.query(Task.id, Task.geometry.ST_AsGeoJSON().label('geometry')) \
                    .filter(Task.id == task.id, Task.project_id == task.project_id)

                task_geojson = geojson.loads(query[0].geometry)
            split_geoms = []
            for i in range(0, 2):
                for j in range(0, 2):
                    new_x = x * 2 + i
                    new_y = y * 2 + j
                    new_zoom = zoom + 1
                    new_square = SplitService._create_square(
                        new_x, new_y, new_zoom)
                    feature = geojson.Feature()
                    feature.geometry = new_square
                    feature_shape = shapely_shape(feature.geometry)
                    if task and not task.is_square:
                        intersection = shapely_shape(
                            task_geojson).intersection(feature_shape)
                        multipolygon = MultiPolygon([intersection])
                        feature.geometry = geojson.loads(
                            geojson.dumps(mapping(multipolygon)))

                    if task and not task.is_square:
                        is_square = False
                    else:
                        is_square = True

                    feature.properties = {
                        'x': new_x,
                        'y': new_y,
                        'zoom': new_zoom,
                        'isSquare': is_square
                    }

                    if (len(feature.geometry.coordinates) > 0):
                        split_geoms.append(feature)

            return split_geoms
        except Exception as e:
            raise SplitServiceError(
                f'unhandled error splitting tile: {str(e)}')
示例#2
0
    def process_shape(self, shape, field_map, level, version):
        properties = shape["properties"]

        fields = self.extract_fields(field_map, properties)
        fields["level"] = level
        fields["version"] = version

        if shape["geometry"] is None:
            print(f"No geometry present for {fields['code']}")
            return

        sh = shapely_shape(shape["geometry"])
        geometry = GEOSGeometry(sh.wkt)
        if type(geometry) == Polygon:
            geo_shape = MultiPolygon([geometry])
        else:
            geo_shape = geometry

        if fields["parent_code"] is None:
            process_as_root = input(f"""Geography '{fields["code"]}' does not have a parent. Load it as a root geography? (Y/N)?""")
            if not process_as_root.lower().startswith('n'):
                return self.process_root(fields, geo_shape)
            return
        else:
            self.process_node(fields, geo_shape)
示例#3
0
def plot_polygon(
    geometry: Union[Polygon, MultiPolygon],
    fill: bool = False,
    axis: Axes = None,
    show: bool = False,
    **kwargs,
) -> Axes:
    """
    Plot the given polygon.

    :param geometry: Shapely polygon (or multipolygon)
    :param axis: `pyplot` axis to plot to
    :param show:  whether to show the plot
    """

    if axis is None:
        axis = pyplot.gca()

    if 'c' not in kwargs:
        try:
            color = next(axis._get_lines.color_cycle)
        except AttributeError:
            color = 'r'
        kwargs['c'] = color

    if isinstance(geometry, dict):
        geometry = shapely_shape(geometry)

    if type(geometry) is Polygon:
        if fill:
            axis.fill(*geometry.exterior.xy, **kwargs)
            kwargs['c'] = 'w'
            for interior in geometry.interiors:
                axis.fill(*interior.xy, **kwargs)
        else:
            axis.plot(*geometry.exterior.xy, **kwargs)
            for interior in geometry.interiors:
                axis.plot(*interior.xy, **kwargs)
    elif type(geometry) is MultiPolygon:
        for polygon in geometry:
            plot_polygon(geometry=polygon,
                         axis=axis,
                         fill=fill,
                         show=False,
                         **kwargs)
    else:
        if fill:
            axis.fill(*geometry.xy, **kwargs)
        else:
            axis.plot(*geometry.xy, **kwargs)

    if show:
        pyplot.show()

    return axis
示例#4
0
def validate_item(item: Dict):
    _ITEM_SCHEMA.validate(item)

    # Should be a valid polygon
    assert "geometry" in item, "Item has no geometry"
    assert item["geometry"], "Item has blank geometry"
    with DebugContext(f"Failing shape:\n{pformat(item['geometry'])}"):
        shape = shapely_shape(item["geometry"])
        assert shape.is_valid, f"Item has invalid geometry: {explain_validity(shape)}"
        assert shape.geom_type in (
            "Polygon",
            "MultiPolygon",
        ), "Unexpected type of shape"

    assert_stac_extensions(item)
示例#5
0
    def _create_split_tasks_from_geometry(task) -> list:
        """
        Splits a task into 4 smaller tasks based purely on the task's geometry rather than
        an OSM tile identified by x, y, zoom
        :return: list of {geojson.Feature}
        """
        # Load the task's geometry and calculate its centroid and bbox
        query = db.session.query(
            Task.id,
            Task.geometry.ST_AsGeoJSON().label("geometry")).filter(
                Task.id == task.id, Task.project_id == task.project_id)
        task_geojson = geojson.loads(query[0].geometry)
        geometry = shapely_shape(task_geojson)
        centroid = geometry.centroid
        minx, miny, maxx, maxy = geometry.bounds

        # split geometry in half vertically, then split those halves in half horizontally
        split_geometries = []
        vertical_dividing_line = LineString([(centroid.x, miny),
                                             (centroid.x, maxy)])
        horizontal_dividing_line = LineString([(minx, centroid.y),
                                               (maxx, centroid.y)])

        vertical_halves = SplitService._as_halves(
            split(geometry, vertical_dividing_line), centroid, "x")
        for half in vertical_halves:
            split_geometries += SplitService._as_halves(
                split(half, horizontal_dividing_line), centroid, "y")

        # convert split geometries into GeoJSON features expected by Task
        split_features = []
        for split_geometry in split_geometries:
            feature = geojson.Feature()
            # Tasks expect multipolygons. Convert and use the database to get as GeoJSON
            multipolygon_geometry = shape.from_shape(split_geometry, 4326)
            feature.geometry = geojson.loads(
                db.engine.execute(
                    multipolygon_geometry.ST_AsGeoJSON()).scalar())
            feature.properties["x"] = None
            feature.properties["y"] = None
            feature.properties["zoom"] = None
            feature.properties["isSquare"] = False
            split_features.append(feature)
        return split_features
示例#6
0
def validate_item(item: Dict):
    validate_document(item,
                      _ITEM_SCHEMA,
                      schema_folder=_ITEM_SCHEMA_PATH.parent)

    # Should be a valid polygon
    assert "geometry" in item, "Item has no geometry"
    assert item["geometry"], "Item has blank geometry"
    with DebugContext(f"Failing shape:\n{pformat(item['geometry'])}"):
        shape = shapely_shape(item["geometry"])
        assert shape.is_valid, f"Item has invalid geometry: {explain_validity(shape)}"
        assert shape.geom_type in (
            "Polygon",
            "MultiPolygon",
        ), "Unexpected type of shape"

    # href should never be blank if present
    # -> The jsonschema enforces href as required, but it's not checking for emptiness.
    #    (and we've had empty ones in previous prototypes)
    for offset, value in research(item, lambda p, k, v: k == "href"):
        viewable_offset = "→".join(map(repr, offset))
        assert value.strip(), f"href has empty value: {repr(viewable_offset)}"
示例#7
0
 def wrapped_geom(self):
     if not self.wrapped_geojson['coordinates']:
         return GeometryCollection()
     return shapely_shape(self.wrapped_geojson)
示例#8
0
    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