예제 #1
0
    def testGetGeometry(self):
        idx = QgsSpatialIndex()
        idx2 = QgsSpatialIndex(QgsSpatialIndex.FlagStoreFeatureGeometries)
        fid = 0
        for y in range(5):
            for x in range(10, 15):
                ft = QgsFeature()
                ft.setId(fid)
                ft.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
                idx.insertFeature(ft)
                idx2.insertFeature(ft)
                fid += 1

        # not storing geometries, a keyerror should be raised
        with self.assertRaises(KeyError):
            idx.geometry(-100)
        with self.assertRaises(KeyError):
            idx.geometry(1)
        with self.assertRaises(KeyError):
            idx.geometry(2)
        with self.assertRaises(KeyError):
            idx.geometry(1000)

        self.assertEqual(idx2.geometry(1).asWkt(1), 'Point (11 0)')
        self.assertEqual(idx2.geometry(2).asWkt(1), 'Point (12 0)')
        with self.assertRaises(KeyError):
            idx2.geometry(-100)
        with self.assertRaises(KeyError):
            idx2.geometry(1000)
예제 #2
0
    def testGetGeometry(self):
        idx = QgsSpatialIndex()
        idx2 = QgsSpatialIndex(QgsSpatialIndex.FlagStoreFeatureGeometries)
        fid = 0
        for y in range(5):
            for x in range(10, 15):
                ft = QgsFeature()
                ft.setId(fid)
                ft.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
                idx.insertFeature(ft)
                idx2.insertFeature(ft)
                fid += 1

        # not storing geometries, a keyerror should be raised
        with self.assertRaises(KeyError):
            idx.geometry(-100)
        with self.assertRaises(KeyError):
            idx.geometry(1)
        with self.assertRaises(KeyError):
            idx.geometry(2)
        with self.assertRaises(KeyError):
            idx.geometry(1000)

        self.assertEqual(idx2.geometry(1).asWkt(1), 'Point (11 0)')
        self.assertEqual(idx2.geometry(2).asWkt(1), 'Point (12 0)')
        with self.assertRaises(KeyError):
            idx2.geometry(-100)
        with self.assertRaises(KeyError):
            idx2.geometry(1000)
예제 #3
0
class GsCollection:
    """Class used for managing the QgsFeature spatially.

    QgsSpatialIndex class is used to store and retrieve the features.
    """

    __slots__ = ('_spatial_index', '_dict_qgs_segment', '_id_qgs_segment')

    def __init__(self):
        """Constructor that initialize the GsCollection.

        """

        self._spatial_index = QgsSpatialIndex()
        self._dict_qgs_segment = {
        }  # Contains a reference to the original geometry
        self._id_qgs_segment = 0

    def _get_next_id_segment(self):
        """Increment the id of the segment.

        :return: Value of the next ID
        :rtype: int
        """

        self._id_qgs_segment += 1

        return self._id_qgs_segment

    def _create_rectangle(self, geom_id, qgs_geom):
        """Creates a new QgsRectangle to load in the QgsSpatialIndex.

        :param: geom_id: Integer ID of the geometry
        :param: qgs_geom: QgsGeometry to use for bounding box extraction
        :return: The feature created
        :rtype: QgsFeature
        """

        id_segment = self._get_next_id_segment()
        self._dict_qgs_segment[id_segment] = (
            geom_id, qgs_geom)  # Reference to the RbGeom ID and geometry

        return id_segment, qgs_geom.boundingBox()

    def add_features(self, rb_geoms, feedback):
        """Add a RbGeom object in the spatial index.

        For the LineString geometries. The geometry is broken into each line segment that are individually
        loaded in the QgsSpatialIndex.  This strategy accelerate the validation of the spatial constraints.

        :param: rb_geoms: List of RbGeom to load in the QgsSpatialIndex
        :feedback: QgsFeedback handle used to update the progress bar
        """

        progress_bar = ProgressBar(feedback, len(rb_geoms),
                                   "Building internal structure...")
        for val, rb_geom in enumerate(rb_geoms):
            progress_bar.set_value(val)
            qgs_rectangles = []
            if rb_geom.qgs_geom.wkbType() == QgsWkbTypes.Point:
                qgs_rectangles.append(
                    self._create_rectangle(rb_geom.id, rb_geom.qgs_geom))
            else:
                qgs_points = rb_geom.qgs_geom.constGet().points()
                for i in range(0, (len(qgs_points) - 1)):
                    qgs_geom = QgsGeometry(
                        QgsLineString(qgs_points[i], qgs_points[i + 1]))
                    qgs_rectangles.append(
                        self._create_rectangle(rb_geom.id, qgs_geom))

            for geom_id, qgs_rectangle in qgs_rectangles:
                self._spatial_index.addFeature(geom_id, qgs_rectangle)

        return

    def get_segment_intersect(self, qgs_geom_id, qgs_rectangle,
                              qgs_geom_subline):
        """Find the feature that intersects the bounding box.

        Once the line string intersecting the bounding box are found. They are separated into 2 lists.
        The first one being the line string with the same id (same line) the second one all the others line string.

        :param qgs_geom_id: ID of the line string that is being simplified
        :param qgs_rectangle: QgsRectangle used for feature intersection
        :param qgs_geom_subline: LineString used to remove line segment superimposed to this line string
        :return: Two lists  of line string segment. First: Line string with same id; Second all the others
        :rtype: tuple of 2 lists
        """

        qgs_geoms_with_itself = []
        qgs_geoms_with_others = []
        qgs_rectangle.grow(
            Epsilon.ZERO_RELATIVE *
            100.)  # Always increase the b_box to avoid degenerated b_box
        ids = self._spatial_index.intersects(qgs_rectangle)
        for geom_id in ids:
            target_qgs_geom_id, target_qgs_geom = self._dict_qgs_segment[
                geom_id]
            if target_qgs_geom_id is None:
                # Nothing to do; segment was deleted
                pass
            else:
                if target_qgs_geom_id == qgs_geom_id:
                    # Test that the segment is not part of qgs_subline
                    if not target_qgs_geom.within(qgs_geom_subline):
                        qgs_geoms_with_itself.append(target_qgs_geom)
                else:
                    qgs_geoms_with_others.append(target_qgs_geom)

        return qgs_geoms_with_itself, qgs_geoms_with_others

    def _delete_segment(self, qgs_geom_id, qgs_pnt0, qgs_pnt1):
        """Delete a line segment in the spatial index based on start/end points.

        To minimise the number of feature returned we search for a very small bounding box located in the middle
        of the line segment.  Usually only one line segment is returned.

        :param qgs_geom_id: Integer ID of the geometry
        :param qgs_pnt0 : QgsPoint start point of the target line segment.
        :param qgs_pnt1 : QgsPoint end point of the target line segment.
        """

        qgs_geom_to_delete = QgsGeometry(QgsLineString(qgs_pnt0, qgs_pnt1))
        qgs_mid_point = QgsGeometryUtils.midpoint(qgs_pnt0, qgs_pnt1)
        qgs_rectangle = qgs_mid_point.boundingBox()
        qgs_rectangle.grow(Epsilon.ZERO_RELATIVE * 100)
        deleted = False
        ids = self._spatial_index.intersects(qgs_rectangle)
        for geom_id in ids:
            target_qgs_geom_id, target_qgs_geom = self._dict_qgs_segment[
                geom_id]  # Extract id and geometry
            if qgs_geom_id == target_qgs_geom_id:
                # Only check for the same ID
                if target_qgs_geom.equals(
                        qgs_geom_to_delete):  # Check if it's the same geometry
                    deleted = True
                    self._dict_qgs_segment[geom_id] = (
                        None, None)  # Delete from the internal structure
                    break

        if not deleted:
            raise Exception(
                QgsProcessingException("Internal structure corruption..."))

        return

    def _delete_vertex(self, rb_geom, v_id_start, v_id_end):
        """Delete consecutive vertex in the line and update the spatial index.

        When a vertex in a line string is deleted.  Two line segments are deleted and one line segment is
        created in the spatial index.  Cannot delete the first/last vertex of a line string

        :param rb_geom: LineString object to update.
        :param v_id_start: start of the vertex to delete.
        :param v_id_end: end of the vertex to delete.
        """

        is_closed = rb_geom.qgs_geom.constGet().isClosed()
        v_ids_to_del = list(range(v_id_start, v_id_end + 1))
        if v_id_start == 0 and is_closed:
            # Special case for closed line where we simulate a circular array
            nbr_vertice = rb_geom.qgs_geom.constGet().numPoints()
            v_ids_to_del.insert(0, nbr_vertice - 2)
        else:
            v_ids_to_del.insert(0, v_ids_to_del[0] - 1)
        v_ids_to_del.append(v_ids_to_del[-1] + 1)

        # Delete the line segment in the spatial index
        for i in range(len(v_ids_to_del) - 1):
            qgs_pnt0 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[i])
            qgs_pnt1 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[i + 1])
            self._delete_segment(rb_geom.id, qgs_pnt0, qgs_pnt1)

        # Add the new line segment in the spatial index
        qgs_pnt0 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[0])
        qgs_pnt1 = rb_geom.qgs_geom.vertexAt(v_ids_to_del[-1])
        qgs_geom_segment = QgsGeometry(QgsLineString(qgs_pnt0, qgs_pnt1))
        geom_id, qgs_rectangle = self._create_rectangle(
            rb_geom.id, qgs_geom_segment)
        self._spatial_index.addFeature(geom_id, qgs_rectangle)

        # Delete the vertex in the line string geometry
        for v_id_to_del in reversed(range(v_id_start, v_id_end + 1)):
            rb_geom.qgs_geom.deleteVertex(v_id_to_del)
            if v_id_start == 0 and is_closed:
                # Special case for closed line where we simulate a circular array
                nbr_vertice = rb_geom.qgs_geom.constGet().numPoints()
                qgs_pnt_first = rb_geom.qgs_geom.vertexAt(0)
                rb_geom.qgs_geom.insertVertex(qgs_pnt_first, nbr_vertice - 1)
                rb_geom.qgs_geom.deleteVertex(nbr_vertice)

        return

    def delete_vertex(self, rb_geom, v_id_start, v_id_end):
        """Manage deletion of consecutives vertex.

        If v_id_start is greater than v_id_end the delete is broken into up to 3 calls

        :param rb_geom: LineString object to update.
        :param v_id_start: start of the vertex to delete.
        :param v_id_end: end of the vertex to delete.
        """

        num_points = rb_geom.qgs_geom.constGet().numPoints()
        # Manage closes line where first/last vertice are the same
        if v_id_start == num_points - 1:
            v_id_start = 0  # Last point is the same as the first vertice
        if v_id_end == -1:
            v_id_end = num_points - 2  # Preceding point the first/last vertice

        if v_id_start <= v_id_end:
            self._delete_vertex(rb_geom, v_id_start, v_id_end)
        else:
            self._delete_vertex(rb_geom, v_id_start, num_points - 2)
            self._delete_vertex(rb_geom, 0, 0)
            if v_id_end > 0:
                self._delete_vertex(rb_geom, 1, v_id_end)


#            lst_vertex_to_del = list(range(v_id_start, num_points)) + list(range(0, v_id_end+1))
#            for vertex_to_del in lst_vertex_to_del:
#                self._delete_vertex(rb_geom, vertex_to_del, vertex_to_del)

#        num_points = rb_geom.qgs_geom.constGet().numPoints()
#        lst_vertex_to_del = list(range(v_id_start, num_points)) + list(range(0, v_id_end + 1))
#        for vertex_to_del in lst_vertex_to_del:
#            self._delete_vertex(rb_geom, vertex_to_del, vertex_to_del)

    def add_vertex(self, rb_geom, bend_i, bend_j, qgs_geom_new_subline):
        """Update the line segment in the spatial index

        :param rb_geom: RbGeom line to update
        :param bend_i: Start of the bend to delete
        :param bend_j: End of the bend to delete (always bend_i + 1)
        :param qgs_geom_new_subline: New sub line string to add in the spatial index
        :return:
        """

        # Delete the base of the bend
        qgs_pnt0 = rb_geom.qgs_geom.vertexAt(bend_i)
        qgs_pnt1 = rb_geom.qgs_geom.vertexAt(bend_j)
        self._delete_segment(rb_geom.id, qgs_pnt0, qgs_pnt1)

        qgs_points = qgs_geom_new_subline.constGet().points()
        tmp_qgs_points = qgs_points[1:-1]  # Drop first/last item
        # Insert the new vertex in the QgsGeometry. Work reversely to facilitate insertion
        for qgs_point in reversed(tmp_qgs_points):
            rb_geom.qgs_geom.insertVertex(qgs_point, bend_j)

        # Add the new segment in the spatial container
        for i in range(len(qgs_points) - 1):
            qgs_geom_segment = QgsGeometry(
                QgsLineString(qgs_points[i], qgs_points[i + 1]))
            geom_id, qgs_rectangle = self._create_rectangle(
                rb_geom.id, qgs_geom_segment)
            self._spatial_index.addFeature(geom_id, qgs_rectangle)

        return

    def validate_integrity(self, rb_geoms):
        """This method is used to validate the data structure at the end of the process

        This method is executed only when requested and for debug purpose only.  It's validating the data structure
        by removing element from it the data structure is unusable after. Validate integrity must be the last
        operation before ending the program as it destroy the data structure...

        :param rb_geoms: Geometry contained in the spatial container
        :return: Flag indicating if the structure is valid. True: is valid; False: is not valid
        :rtype: Boolean
        """

        is_structure_valid = True
        # from the geometry remove all the segment in the spatial index.
        for rb_geom in rb_geoms:
            qgs_line_string = rb_geom.qgs_geom.constGet()
            if qgs_line_string.wkbType() == QgsWkbTypes.LineString:
                qgs_points = qgs_line_string.points()
                for i in range(len(qgs_points) - 1):
                    self._delete_segment(rb_geom.id, qgs_points[i],
                                         qgs_points[i + 1])

        if is_structure_valid:
            # Verify that there are no other feature in the spatial index; except for QgsPoint
            qgs_rectangle = QgsRectangle(-sys.float_info.max,
                                         -sys.float_info.max,
                                         sys.float_info.max,
                                         sys.float_info.max)
            feat_ids = self._spatial_index.intersects(qgs_rectangle)
            for feat_id in feat_ids:
                qgs_geom = self._spatial_index.geometry(feat_id)
                if qgs_geom.wkbType() == QgsWkbTypes.Point:
                    pass
                else:
                    # Error
                    is_structure_valid = False

        return is_structure_valid
예제 #4
0
class TracingPipelines(QgsTask):
    def __init__(self,
                 pipelines,
                 valves,
                 description='TracingCAJ',
                 user_distance=0.001,
                 onfinish=None,
                 debug=False):
        super().__init__(description, QgsTask.CanCancel)

        self.onfinish = onfinish
        self.debug = debug
        self.__user_distance = user_distance
        self._pipelines_features = pipelines[0]
        self._valves_features = valves[0]
        self.__list_valves = []
        self.__list_visited_pipelines = []
        self.__list_visited_pipelines_ids = []
        self.__q_list_pipelines = []
        self.__q_list_pipelines_ids = []
        self.__iterations = 0
        self.__exception = None

        # Cria os índices espaciais
        self.__idx_pipelines = None
        self.__idx_valves = None
        if self.__idx_valves is None or self.__idx_pipelines is None:
            self.__create_spatial_index()

    def run(self):
        QgsMessageLog.logMessage(f'Started task {self.description()}',
                                 'TracingCAJ', Qgis.Info)

        # Busca por redes selecionadas (necessário ser apenas uma)
        if self.debug:
            self._pipelines_features.selectByIds([4343])
            # self._pipelines_features.getFeatures(16)

        selected_pipeline = self._pipelines_features.selectedFeatures()

        if len(selected_pipeline) != 1:
            QgsMessageLog.logMessage('Selecione apenas UMA rede', 'TracingCAJ',
                                     Qgis.Info)
            return False
        else:

            self.__q_list_pipelines.append(selected_pipeline[0].geometry())
            self.__q_list_pipelines_ids.append(selected_pipeline[0].id())

            while len(self.__q_list_pipelines) > 0:
                self.__iterations += 1

                # check isCanceled() to handle cancellation
                if self.isCanceled():
                    return False

                pipeline = self.__q_list_pipelines.pop(0)
                pipeline_id = self.__q_list_pipelines_ids.pop(0)
                if pipeline_id not in self.__list_visited_pipelines_ids:
                    self.__list_visited_pipelines.append(pipeline)
                    self.__list_visited_pipelines_ids.append(pipeline_id)

                    v1 = pipeline.vertexAt(0)
                    if self.debug:
                        v2 = pipeline.vertexAt(pipeline.get()[0].childCount() -
                                               1)
                    else:
                        v2 = pipeline.vertexAt(len(pipeline.get()) - 1)

                    try:
                        self.__find_neighbors(v1)
                        self.__find_neighbors(v2)
                    except Exception as e:
                        self.__exception = e
                        return False

        return True

    def finished(self, result):
        if result:
            self._valves_features.selectByIds(self.__list_valves)
            self._pipelines_features.selectByIds(
                self.__list_visited_pipelines_ids)

            if self.onfinish:
                self.onfinish()

            QgsMessageLog.logMessage(
                f"Task {self.description()} has been executed correctly"
                f"Iterations: {self.__iterations}"
                f"Pipelines: {self.__list_visited_pipelines_ids}"
                f"Valves: {self.__list_valves}",
                level=Qgis.Success)
        else:
            if self.__exception is None:
                QgsMessageLog.logMessage(
                    f"Tracing {self.description()} not successful "
                    f"but without exception "
                    f"(probably the task was manually canceled by the user)",
                    level=Qgis.Warning)
            else:
                QgsMessageLog.logMessage(
                    f"Task {self.description()}"
                    f"Exception: {self.__exception}",
                    level=Qgis.Critical)
                raise self.__exception

    def cancel(self):
        QgsMessageLog.logMessage(
            f'TracingTrask {self.description()} was canceled', level=Qgis.Info)
        super().cancel()

    def __create_spatial_index(self):
        self.__idx_pipelines = QgsSpatialIndex(
            self._pipelines_features.getFeatures(),
            flags=QgsSpatialIndex.FlagStoreFeatureGeometries)
        self.__idx_valves = QgsSpatialIndex(
            self._valves_features.getFeatures(),
            flags=QgsSpatialIndex.FlagStoreFeatureGeometries)

    def __find_neighbors(self, point_vertex):
        reg_isvisivel = None
        reg_status = None

        # Busca pelo registro mais próximo, dentro do raio maxDistance=user_distance
        reg_nearest = self.__idx_valves.nearestNeighbor(
            point=QgsPointXY(point_vertex),
            neighbors=1,
            maxDistance=self.__user_distance)

        if len(reg_nearest) > 0:
            # visivel = 'sim' = registro visível | visivel = 'não' = registro não visível
            reg_isvisivel = str(
                list(self._valves_features.getFeatures(reg_nearest))[0]
                ['visivel'])
            # status = 0 = 'Aberto' | status = 1 = 'Fechado'
            reg_status = str(
                list(self._valves_features.getFeatures(reg_nearest))[0]
                ['status'])

        if len(reg_nearest) > 0:
            if reg_isvisivel.upper() != 'NÃO' and reg_status == '0':
                self.__list_valves.append(reg_nearest[0])
        else:
            # Busca pelas 4 redes mais próximas dentro do raio maxDistance=user_distance
            pipelines_nearest = self.__idx_pipelines.nearestNeighbor(
                point=QgsPointXY(point_vertex),
                neighbors=4,
                maxDistance=self.__user_distance)
            if len(pipelines_nearest) > 0:
                for pipeline_id in pipelines_nearest:
                    pipeline_geometry = self.__idx_pipelines.geometry(
                        pipeline_id)
                    if pipeline_id not in self.__list_visited_pipelines_ids:
                        self.__q_list_pipelines_ids.append(pipeline_id)
                        self.__q_list_pipelines.append(pipeline_geometry)