def find_line_distz(self): # Get start and end nodes zs and deltazs from table pipe_geom = self.pipe_ft.geometry() (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes(self.params, self.pipe_ft.geometry()) start_node_z = start_node_ft.attribute(Junction.field_name_elev) if start_node_z == NULL: start_node_z = 0 start_node_deltaz = start_node_ft.attribute(Junction.field_name_delta_z) end_node_z = end_node_ft.attribute(Junction.field_name_elev) if end_node_z == NULL: end_node_z = 0 end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z) total_dist = 0 dist_z = OrderedDict() pipe_geom_v2 = pipe_geom.geometry() dist_z[0] = start_node_z + start_node_deltaz # Interpolate deltaZs for remaining vertices for p in range(1, pipe_geom_v2.vertexCount(0, 0)): vertex = pipe_geom_v2.vertexAt(QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex)) vertex_prev = pipe_geom_v2.vertexAt(QgsVertexId(0, 0, p - 1, QgsVertexId.SegmentVertex)) total_dist += math.sqrt((vertex.x() - vertex_prev.x()) ** 2 + (vertex.y() - vertex_prev.y()) ** 2) # Interpolate delta z for vertex using distance from nodes and delta z of nodes z = (total_dist / self.pipe_ft.geometry().length() * (end_node_z - start_node_z)) + start_node_z # z = raster_utils.read_layer_val_from_coord(self.params.dem_rlay, QgsPoint(vertex.x(), vertex.y())) delta_z = (total_dist / self.pipe_ft.geometry().length() * (end_node_deltaz - start_node_deltaz)) + start_node_deltaz dist_z[total_dist] = z + delta_z return dist_z
def addGeometry(self, geom): """ Add geometry into the index :param geom:QgsAbstractGeometryV2 :return: """ for iPart in xrange(geom.partCount()): for iRing in xrange(geom.ringCount(iPart)): nVerts = geom.vertexCount(iPart, iRing) if isinstance(geom, QgsMultiPolygonV2): nVerts -= 1 elif isinstance(geom, QgsPolygonV2): nVerts -= 1 elif isinstance(geom, QgsCircularStringV2): nVerts -= 1 for iVert in xrange(nVerts): idx = CoordIdx( geom, QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex)) idx1 = CoordIdx( geom, QgsVertexId(iPart, iRing, iVert + 1, QgsVertexId.SegmentVertex)) self.coordIdxs.append(idx) self.coordIdxs.append(idx1) self.addPoint(idx) if iVert < nVerts - 1: self.addSegment(idx, idx1)
def find_line_distz3D(self): pipe_geom_v2 = self.pipe_ft.geometry().get() # Find start and end nodes (needed to know delta z) (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes( self.params, self.pipe_ft.geometry()) start_node_deltaz = start_node_ft.attribute( Junction.field_name_delta_z) end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z) total_dist = 0 dist_z = OrderedDict() dist_z[0] = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, 0, QgsVertexId.SegmentVertex)).z() #+ start_node_deltaz for p in range(1, pipe_geom_v2.vertexCount(0, 0)): vertex = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex)) vertex_prev = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, p - 1, QgsVertexId.SegmentVertex)) total_dist += math.sqrt((vertex.x() - vertex_prev.x())**2 + (vertex.y() - vertex_prev.y())**2) # Interpolate delta z for vertex using distance from nodes and delta z of nodes delta_z = ( total_dist / self.pipe_ft.geometry().length() * (end_node_deltaz - start_node_deltaz)) + start_node_deltaz dist_z[total_dist] = vertex.z() #+ delta_z return dist_z
def test_move_operation(self): operation = QgsAnnotationItemEditOperationMoveNode( 'item id', QgsVertexId(1, 2, 3), QgsPoint(4, 5), QgsPoint(6, 7)) self.assertEqual(operation.itemId(), 'item id') self.assertEqual(operation.nodeId(), QgsVertexId(1, 2, 3)) self.assertEqual(operation.before(), QgsPoint(4, 5)) self.assertEqual(operation.after(), QgsPoint(6, 7))
def find_distance(self, pipe_geom_v2, vertex_nr): total_dist = 0 for p in range(1, vertex_nr + 1): vertex = pipe_geom_v2.vertexAt(QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex)) vertex_prev = pipe_geom_v2.vertexAt(QgsVertexId(0, 0, p - 1, QgsVertexId.SegmentVertex)) total_dist += math.sqrt((vertex.x() - vertex_prev.x()) ** 2 + (vertex.y() - vertex_prev.y()) ** 2) return total_dist
def test_apply_edit(self): """ Test applying edits to a layer """ layer = QgsAnnotationLayer( 'test', QgsAnnotationLayer.LayerOptions( QgsProject.instance().transformContext())) self.assertTrue(layer.isValid()) polygon_item_id = layer.addItem( QgsAnnotationPolygonItem( QgsPolygon( QgsLineString([ QgsPoint(12, 13), QgsPoint(14, 13), QgsPoint(14, 15), QgsPoint(12, 13) ])))) linestring_item_id = layer.addItem( QgsAnnotationLineItem( QgsLineString( [QgsPoint(11, 13), QgsPoint(12, 13), QgsPoint(12, 15)]))) marker_item_id = layer.addItem( QgsAnnotationMarkerItem(QgsPoint(12, 13))) rc = QgsRenderContext() self.assertCountEqual( layer.itemsInBounds(QgsRectangle(1, 1, 20, 20), rc), [polygon_item_id, linestring_item_id, marker_item_id]) # can't apply a move to an item which doesn't exist in the layer self.assertEqual( layer.applyEdit( QgsAnnotationItemEditOperationMoveNode('xxx', QgsVertexId(0, 0, 2), QgsPoint(14, 15), QgsPoint(19, 15))), Qgis.AnnotationItemEditOperationResult.Invalid) # apply move to polygon self.assertEqual( layer.applyEdit( QgsAnnotationItemEditOperationMoveNode(polygon_item_id, QgsVertexId(0, 0, 2), QgsPoint(14, 15), QgsPoint(19, 15))), Qgis.AnnotationItemEditOperationResult.Success) self.assertEqual( layer.item(polygon_item_id).geometry().asWkt(), 'Polygon ((12 13, 14 13, 19 15, 12 13))') # ensure that spatial index was updated self.assertCountEqual( layer.itemsInBounds(QgsRectangle(18, 1, 20, 16), rc), [polygon_item_id])
def test_basic(self): node = QgsAnnotationItemNode(QgsVertexId(0, 0, 1), QgsPointXY(1, 2), Qgis.AnnotationItemNodeType.VertexHandle) self.assertEqual(node.point(), QgsPointXY(1, 2)) self.assertEqual(node.id(), QgsVertexId(0, 0, 1)) node.setPoint(QgsPointXY(3, 4)) self.assertEqual(node.point(), QgsPointXY(3, 4)) self.assertEqual(node.type(), Qgis.AnnotationItemNodeType.VertexHandle)
def testQgsVertexId(self): v = QgsVertexId() self.assertEqual(v.__repr__(), '<QgsVertexId: -1,-1,-1>') v = QgsVertexId(1, 2, 3) self.assertEqual(v.__repr__(), '<QgsVertexId: 1,2,3>') v = QgsVertexId(1, 2, 3, _type=QgsVertexId.CurveVertex) self.assertEqual(v.__repr__(), '<QgsVertexId: 1,2,3 CurveVertex>')
def test_apply_delete_node_edit(self): item = QgsAnnotationPolygonItem( QgsPolygon( QgsLineString([ QgsPoint(12, 13), QgsPoint(14, 13), QgsPoint(14, 15), QgsPoint(14.5, 15.5), QgsPoint(14.5, 16.5), QgsPoint(14.5, 17.5), QgsPoint(12, 13) ]))) self.assertEqual( item.geometry().asWkt(), 'Polygon ((12 13, 14 13, 14 15, 14.5 15.5, 14.5 16.5, 14.5 17.5, 12 13))' ) self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationDeleteNode( '', QgsVertexId(0, 0, 1), QgsPoint(14, 13))), Qgis.AnnotationItemEditOperationResult.Success) self.assertEqual( item.geometry().asWkt(), 'Polygon ((12 13, 14 15, 14.5 15.5, 14.5 16.5, 14.5 17.5, 12 13))') self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationDeleteNode( '', QgsVertexId(0, 0, 2), QgsPoint(14.5, 15.5))), Qgis.AnnotationItemEditOperationResult.Success) self.assertEqual( item.geometry().asWkt(), 'Polygon ((12 13, 14 15, 14.5 16.5, 14.5 17.5, 12 13))') self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationDeleteNode( '', QgsVertexId(0, 0, 7), QgsPoint(14, 15))), Qgis.AnnotationItemEditOperationResult.Invalid) self.assertEqual( item.geometry().asWkt(), 'Polygon ((12 13, 14 15, 14.5 16.5, 14.5 17.5, 12 13))') self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationDeleteNode( '', QgsVertexId(0, 0, 0), QgsPoint(12, 13))), Qgis.AnnotationItemEditOperationResult.Success) self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationDeleteNode( '', QgsVertexId(0, 0, 0), QgsPoint(12, 13))), Qgis.AnnotationItemEditOperationResult.ItemCleared) self.assertEqual(item.geometry().asWkt(), 'Polygon EMPTY')
def test_equality(self): node = QgsAnnotationItemNode(QgsVertexId(0, 0, 1), QgsPointXY(1, 2), Qgis.AnnotationItemNodeType.VertexHandle) node2 = QgsAnnotationItemNode(QgsVertexId(0, 0, 1), QgsPointXY(1, 2), Qgis.AnnotationItemNodeType.VertexHandle) self.assertEqual(node, node2) node2.setPoint(QgsPointXY(3, 4)) self.assertNotEqual(node, node2) node = QgsAnnotationItemNode(QgsVertexId(0, 0, 1), QgsPointXY(1, 2), Qgis.AnnotationItemNodeType.VertexHandle) node2 = QgsAnnotationItemNode(QgsVertexId(0, 0, 2), QgsPointXY(1, 2), Qgis.AnnotationItemNodeType.VertexHandle) self.assertNotEqual(node, node2)
def set_feature(self, feature: Optional[QgsFeature]): """ Sets the feature to show in the model """ self.beginResetModel() self.feature = feature self.vertices = [] self.has_z = self.feature.geometry().constGet().is3D( ) if self.feature is not None and self.feature.hasGeometry() else True self.has_m = self.feature.geometry().constGet().isMeasure( ) if self.feature is not None and self.feature.hasGeometry() else True if self.feature is not None and self.feature.hasGeometry(): geom = self.feature.geometry().constGet() vid = QgsVertexId() while True: ok, vertex = geom.nextVertex(vid) if ok: self.vertices.append((vid, vertex)) else: break self.endResetModel()
def createFeature(self, feedback, feature_id, type, geometries, class_field=None): attrs = [feature_id] if class_field is not None: attrs.append(class_field) multi_point = QgsMultiPoint() for g in geometries: if feedback.isCanceled(): break vid = QgsVertexId() while True: if feedback.isCanceled(): break found, point = g.constGet().nextVertex(vid) if found: multi_point.addGeometry(point) else: break geometry = QgsGeometry(multi_point) output_geometry = None if type == 0: # envelope rect = geometry.boundingBox() output_geometry = QgsGeometry.fromRect(rect) attrs.append(rect.width()) attrs.append(rect.height()) attrs.append(rect.area()) attrs.append(rect.perimeter()) elif type == 1: # oriented rect output_geometry, area, angle, width, height = geometry.orientedMinimumBoundingBox( ) attrs.append(width) attrs.append(height) attrs.append(angle) attrs.append(area) attrs.append(2 * width + 2 * height) elif type == 2: # circle output_geometry, center, radius = geometry.minimalEnclosingCircle( segments=72) attrs.append(radius) attrs.append(math.pi * radius * radius) elif type == 3: # convex hull output_geometry = geometry.convexHull() attrs.append(output_geometry.constGet().area()) attrs.append(output_geometry.constGet().perimeter()) f = QgsFeature() f.setAttributes(attrs) f.setGeometry(output_geometry) return f
def __polygonVertexId(self, polygon_v2): """ To get the id of the selected vertex from a polygon :param polygon_v2: the polygon as polygonV2 :return: id as QgsVertexId """ eR = polygon_v2.exteriorRing() if self.__selectedVertex < eR.numPoints(): return QgsVertexId(0, 0, self.__selectedVertex, 1) else: sel = self.__selectedVertex - eR.numPoints() for num in xrange(polygon_v2.numInteriorRings()): iR = polygon_v2.interiorRing(num) if sel < iR.numPoints(): return QgsVertexId(0, num + 1, sel, 1) sel -= iR.numPoints()
def average_linestrings(line1: QgsLineString, line2: QgsLineString, weight: float = 1) -> QgsLineString: """ Averages two linestring geometries """ g1 = line1.clone() # project points from g2 onto g1 for n in range(line2.numPoints()): vertex = line2.pointN(n) _, pt, after, _ = g1.closestSegment(vertex) g1.insertVertex(QgsVertexId(0, 0, after.vertex), pt) # iterate through vertices in g1 out = [] for n in range(g1.numPoints()): vertex = g1.pointN(n) _, pt, after, _ = line2.closestSegment(vertex) # average pts x = (vertex.x() * weight + pt.x()) / (weight + 1) y = (vertex.y() * weight + pt.y()) / (weight + 1) out.append(QgsPoint(x, y)) return QgsLineString(out)
def test_nodes(self): """ Test nodes for item """ item = QgsAnnotationLineItem( QgsLineString( [QgsPoint(12, 13), QgsPoint(14, 13), QgsPoint(14, 15)])) self.assertEqual(item.nodes(), [ QgsAnnotationItemNode(QgsVertexId(0, 0, 0), QgsPointXY(12, 13), Qgis.AnnotationItemNodeType.VertexHandle), QgsAnnotationItemNode(QgsVertexId(0, 0, 1), QgsPointXY(14, 13), Qgis.AnnotationItemNodeType.VertexHandle), QgsAnnotationItemNode(QgsVertexId(0, 0, 2), QgsPointXY(14, 15), Qgis.AnnotationItemNodeType.VertexHandle) ])
def polygonVertexId(polygon_v2, vertex_id): """ To get the id of the selected vertex from a polygon :param polygon_v2: the polygon as polygonV2 :param vertex_id: selected vertex :return: id as QgsVertexId """ eR = polygon_v2.exteriorRing() if vertex_id < eR.numPoints(): return QgsVertexId(0, 0, vertex_id, 1) else: sel = vertex_id - eR.numPoints() for num in range(polygon_v2.numInteriorRings()): iR = polygon_v2.interiorRing(num) if sel < iR.numPoints(): return QgsVertexId(0, num + 1, sel, 1) sel -= iR.numPoints() return QgsVertexId()
def polyLineSize(self, geom, iPart, iRing): """ Gets the number of vertexes :param geom: QgsAbstractGeometryV2 :param iPart: int :param iRing: int :return: """ nVerts = geom.vertexCount(iPart, iRing) if isinstance(geom, QgsMultiPolygonV2) or isinstance( geom, QgsPolygonV2) or isinstance(geom, QgsCircularStringV2): front = geom.vertexAt( QgsVertexId(iPart, iRing, 0, QgsVertexId.SegmentVertex)) back = geom.vertexAt( QgsVertexId(iPart, iRing, nVerts - 1, QgsVertexId.SegmentVertex)) if front == back: return nVerts - 1 return nVerts
def run(self): segment_data = '' try: features = self.axial_layer.getFeatures() # I leave all the if clauses outside the for loop to gain some speed vid = QgsVertexId() if self.id != '': if self.weight != '': for f in features: nr = 0 while f.geometry().vertexIdFromVertexNr(nr + 1, vid): segment_data += str(f.attribute(self.id)) + "\t" segment_data += str(f.geometry().vertexAt(nr).x()) + "\t" + \ str(f.geometry().vertexAt(nr).y()) + "\t" segment_data += str(f.geometry().vertexAt(nr + 1).x()) + "\t" + \ str(f.geometry().vertexAt(nr + 1).y()) + "\t" segment_data += str(f.attribute(self.weight)) + "\n" nr += 1 else: for f in features: nr = 0 while f.geometry().vertexIdFromVertexNr(nr + 1, vid): segment_data += str(f.attribute(self.id)) + "\t" segment_data += str(f.geometry().vertexAt(nr).x()) + "\t" + \ str(f.geometry().vertexAt(nr).y()) + "\t" segment_data += str(f.geometry().vertexAt(nr + 1).x()) + "\t" + \ str(f.geometry().vertexAt(nr + 1).y()) + "\n" nr += 1 else: if self.weight != '': for f in features: nr = 0 while f.geometry().vertexIdFromVertexNr(nr + 1, vid): segment_data += str(f.id()) + "\t" segment_data += str(f.geometry().vertexAt(nr).x()) + "\t" + \ str(f.geometry().vertexAt(nr).y()) + "\t" segment_data += str(f.geometry().vertexAt(nr + 1).x()) + "\t" + \ str(f.geometry().vertexAt(nr + 1).y()) + "\t" segment_data += str(f.attribute(self.weight)) + "\n" nr += 1 else: for f in features: nr = 0 while f.geometry().vertexIdFromVertexNr(nr + 1, vid): segment_data += str(f.id()) + "\t" segment_data += str(f.geometry().vertexAt(nr).x()) + "\t" + \ str(f.geometry().vertexAt(nr).y()) + "\t" segment_data += str(f.geometry().vertexAt(nr + 1).x()) + "\t" + \ str(f.geometry().vertexAt(nr + 1).y()) + "\n" nr += 1 self.status.emit('Model exported for analysis.') self.result.emit(segment_data) except: self.error.emit('Exporting segment map failed.')
def btn_ok_clicked(self): new_zs = self.static_canvas.pipe_line.get_ydata() pipe_geom_v2 = self.pipe_ft.geometry().get() for p in range(pipe_geom_v2.vertexCount(0, 0)): vertex_id = QgsVertexId(0, 0, p, QgsVertexId.SegmentVertex) vertex = pipe_geom_v2.vertexAt(vertex_id) new_pos_pt = QgsPoint(vertex.x(), vertex.y()) new_pos_pt.addZValue(new_zs[p]) LinkHandler.move_link_vertex(self.params, self.params.pipes_vlay, self.pipe_ft, new_pos_pt, p) # Update delta z for nodes (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes( self.params, self.pipe_ft.geometry()) start_node_elev = start_node_ft.attribute(Junction.field_name_elev) # end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z) end_node_elev = end_node_ft.attribute(Junction.field_name_elev) start_node_new_deltaz = new_zs[0] - start_node_elev end_node_new_deltaz = new_zs[-1] - end_node_elev start_node_ft.setAttribute( start_node_ft.fieldNameIndex(Junction.field_name_delta_z), start_node_new_deltaz) end_node_ft.setAttribute( end_node_ft.fieldNameIndex(Junction.field_name_delta_z), end_node_new_deltaz) # Update start node elevation attribute start_node_lay = NetworkUtils.find_node_layer(self.params, start_node_ft.geometry()) vector_utils.update_attribute(start_node_lay, start_node_ft, Junction.field_name_delta_z, float(start_node_new_deltaz)) # Update end node elevation attribute end_node_lay = NetworkUtils.find_node_layer(self.params, end_node_ft.geometry()) vector_utils.update_attribute(end_node_lay, end_node_ft, Junction.field_name_delta_z, float(end_node_new_deltaz)) # Update pipe length # Calculate 3D length pipe_geom_2 = self.pipe_ft.geometry() if self.params.dem_rlay is not None: length_3d = LinkHandler.calc_3d_length(self.params, pipe_geom_2) else: length_3d = pipe_geom_2.length() vector_utils.update_attribute(self.params.pipes_vlay, self.pipe_ft, Pipe.field_name_length, length_3d) self.setVisible(False)
def motion_notify_callback(self, event): if self.parent.toolbar._active is not None: return if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return vertices = self.pipe_patch.get_path().vertices x, y = vertices[self._ind][0], event.ydata if self._ind != 0 and self._ind != len(vertices) - 1: return vertices[self._ind] = x, y pipe_geom_v2 = self.pipe_ft.geometry().get() # Interpolate remaining vertices for v in range(1, len(vertices) - 1): distance = self.find_distance(self.pipe_ft.geometry().get(), v) vertex = pipe_geom_v2.vertexAt( QgsVertexId(0, 0, v, QgsVertexId.SegmentVertex)) z = raster_utils.read_layer_val_from_coord( self.params.dem_rlay, QgsPoint(vertex.x(), vertex.y())) # Inteporlate deltaZs (start_node_ft, end_node_ft) = NetworkUtils.find_start_end_nodes( self.params, self.pipe_ft.geometry()) start_node_elev = start_node_ft.attribute(Junction.field_name_elev) # end_node_deltaz = end_node_ft.attribute(Junction.field_name_delta_z) end_node_elev = end_node_ft.attribute(Junction.field_name_elev) start_node_deltaz = vertices[0][1] - start_node_elev end_node_deltaz = vertices[-1][1] - end_node_elev delta_z = ( distance / self.pipe_ft.geometry().length() * (end_node_deltaz - start_node_deltaz)) + start_node_deltaz # z = (distance / self.pipe_ft.geometry().length() * (vertices[-1][1] - vertices[0][1])) + vertices[0][1] vertices[v] = vertices[v][0], z + delta_z self.pipe_line.set_data(list(zip(*vertices))) self.restore_region(self.background) self.axes.draw_artist(self.pipe_patch) self.axes.draw_artist(self.pipe_line) self.blit(self.axes.bbox)
def test_nodes(self): """ Test nodes for item """ item = QgsAnnotationPolygonItem( QgsPolygon( QgsLineString([ QgsPoint(12, 13), QgsPoint(14, 13), QgsPoint(14, 15), QgsPoint(12, 13) ]))) # nodes shouldn't form a closed ring self.assertEqual(item.nodes(), [ QgsAnnotationItemNode(QgsVertexId(0, 0, 0), QgsPointXY(12, 13), Qgis.AnnotationItemNodeType.VertexHandle), QgsAnnotationItemNode(QgsVertexId(0, 0, 1), QgsPointXY(14, 13), Qgis.AnnotationItemNodeType.VertexHandle), QgsAnnotationItemNode(QgsVertexId(0, 0, 2), QgsPointXY(14, 15), Qgis.AnnotationItemNodeType.VertexHandle) ])
def _vertex_id(part, ring, vertex): vertex_id = QgsVertexId(part, ring, vertex, QgsVertexId.SegmentVertex) if not vertex_id.IsValid(): vertex_id = QgsVertexId(part, ring, vertex, QgsVertexId.CurveVertex) if not vertex_id.IsValid(): raise ValueError("Invalid vertex addressing.") return vertex_id
def test_apply_move_node_edit(self): item = QgsAnnotationPolygonItem( QgsPolygon( QgsLineString([ QgsPoint(12, 13), QgsPoint(14, 13), QgsPoint(14, 15), QgsPoint(12, 13) ]))) self.assertEqual(item.geometry().asWkt(), 'Polygon ((12 13, 14 13, 14 15, 12 13))') self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 1), QgsPoint(14, 13), QgsPoint(17, 18))), Qgis.AnnotationItemEditOperationResult.Success) self.assertEqual(item.geometry().asWkt(), 'Polygon ((12 13, 17 18, 14 15, 12 13))') self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 3), QgsPoint(12, 13), QgsPoint(19, 20))), Qgis.AnnotationItemEditOperationResult.Success) self.assertEqual(item.geometry().asWkt(), 'Polygon ((19 20, 17 18, 14 15, 19 20))') self.assertEqual( item.applyEdit( QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 4), QgsPoint(14, 15), QgsPoint(19, 20))), Qgis.AnnotationItemEditOperationResult.Invalid) self.assertEqual(item.geometry().asWkt(), 'Polygon ((19 20, 17 18, 14 15, 19 20))')
def test_transient_move_operation(self): item = QgsAnnotationLineItem( QgsLineString( [QgsPoint(12, 13), QgsPoint(14, 13), QgsPoint(14, 15)])) self.assertEqual(item.geometry().asWkt(), 'LineString (12 13, 14 13, 14 15)') res = item.transientEditResults( QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 1), QgsPoint(14, 13), QgsPoint(17, 18))) self.assertEqual(res.representativeGeometry().asWkt(), 'LineString (12 13, 17 18, 14 15)')
def _get_uncommon_vertices(_common_vertices, geometry: QgsGeometry): res = [] const_geom = geometry.constGet() vid = QgsVertexId() vertex_no = 1 while True: ok, vertex = const_geom.nextVertex(vid) if ok: if not QgsPointXY(vertex) in _common_vertices: res.append(vertex_no) vertex_no += 1 else: break return res
def __ok(self, withVertex, withPoint): line_v2, curved = GeometryV2.asLineV2( self.__selectedFeature.geometry()) vertex_v2 = QgsPointV2() vertex_id = QgsVertexId() line_v2.closestSegment(QgsPointV2(self.__mapPoint), vertex_v2, vertex_id, 0) x0 = line_v2.xAt(vertex_id.vertex - 1) y0 = line_v2.yAt(vertex_id.vertex - 1) d0 = Finder.sqrDistForCoords(x0, vertex_v2.x(), y0, vertex_v2.y()) x1 = line_v2.xAt(vertex_id.vertex) y1 = line_v2.yAt(vertex_id.vertex) d1 = Finder.sqrDistForCoords(x1, vertex_v2.x(), y1, vertex_v2.y()) z0 = line_v2.zAt(vertex_id.vertex - 1) z1 = line_v2.zAt(vertex_id.vertex) vertex_v2.addZValue((d0 * z1 + d1 * z0) / (d0 + d1)) if withPoint: pt_feat = QgsFeature(self.__layer.pendingFields()) pt_feat.setGeometry(QgsGeometry(vertex_v2)) if self.__layer.editFormConfig().suppress( ) == QgsEditFormConfig.SuppressOn: self.__layer.addFeature(pt_feat) else: self.__iface.openFeatureForm(self.__layer, pt_feat) if withVertex: line_v2.insertVertex(vertex_id, vertex_v2) self.__lastLayer.changeGeometry(self.__selectedFeature.id(), QgsGeometry(line_v2)) self.__lastLayer.removeSelection() self.__rubber.reset() self.__lastFeatureId = None self.__selectedFeature = None self.__isEditing = False
def processAlgorithm( self, # pylint: disable=missing-function-docstring,too-many-statements,too-many-branches,too-many-locals parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) threshold = self.parameterAsDouble(parameters, self.THRESHOLD, context) fields = self.parameterAsFields(parameters, self.FIELDS, context) field_indices = [source.fields().lookupField(f) for f in fields] index = QgsSpatialIndex() roads = {} total = 10.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() for current, feature in enumerate(features): if feedback.isCanceled(): break if feature.geometry().isMultipart(): if feature.geometry().constGet().numGeometries() > 1: raise QgsProcessingException( self.tr('Only single-part geometries are supported')) part1 = feature.geometry().constGet().geometryN(0).clone() feature.setGeometry(part1) index.addFeature(feature) roads[feature.id()] = feature feedback.setProgress(int(current * total)) collapsed = {} processed = set() total = 85.0 / len(roads) current = 0 for _id, f in roads.items(): if feedback.isCanceled(): break current += 1 feedback.setProgress(10 + current * total) if _id in processed: continue box = f.geometry().boundingBox() box.grow(threshold) similar_candidates = index.intersects(box) if not similar_candidates: collapsed[_id] = f processed.add(_id) continue candidate = f.geometry() candidate_attrs = [f.attributes()[i] for i in field_indices] parts = [] for t in similar_candidates: if t == _id: continue other = roads[t] other_attrs = [other.attributes()[i] for i in field_indices] if other_attrs != candidate_attrs: continue dist = candidate.hausdorffDistance(other.geometry()) if dist < threshold: parts.append(t) if len(parts) == 0: collapsed[_id] = f continue # todo fix this if len(parts) > 1: continue assert len(parts) == 1, len(parts) other = roads[parts[0]].geometry() averaged = QgsGeometry( GeometryUtils.average_linestrings(candidate.constGet(), other.constGet())) # reconnect touching lines bbox = candidate.boundingBox() bbox.combineExtentWith(other.boundingBox()) touching_candidates = index.intersects(bbox) for touching_candidate in touching_candidates: if touching_candidate in (_id, parts[0]): continue # print(touching_candidate) touching_candidate_geom = roads[touching_candidate].geometry() # either the start or end of touching_candidate_geom touches candidate start = QgsGeometry( touching_candidate_geom.constGet().startPoint()) end = QgsGeometry( touching_candidate_geom.constGet().endPoint()) moved_start = False moved_end = False for cc in [candidate, other]: # if start.touches(cc): start_line = start.shortestLine(cc) if start_line.length() < 0.00000001: # start touches, move to touch averaged line averaged_line = start.shortestLine(averaged) new_start = averaged_line.constGet().endPoint() touching_candidate_geom.get().moveVertex( QgsVertexId(0, 0, 0), new_start) # print('moved start') moved_start = True continue end_line = end.shortestLine(cc) if end_line.length() < 0.00000001: # endtouches, move to touch averaged line averaged_line = end.shortestLine(averaged) new_end = averaged_line.constGet().endPoint() touching_candidate_geom.get().moveVertex( QgsVertexId( 0, 0, touching_candidate_geom.constGet().numPoints() - 1), new_end) # print('moved end') moved_end = True # break index.deleteFeature(roads[touching_candidate]) if moved_start and moved_end: if touching_candidate in collapsed: del collapsed[touching_candidate] processed.add(touching_candidate) else: roads[touching_candidate].setGeometry( touching_candidate_geom) index.addFeature(roads[touching_candidate]) if touching_candidate in collapsed: collapsed[touching_candidate].setGeometry( touching_candidate_geom) index.deleteFeature(f) index.deleteFeature(roads[parts[0]]) ff = QgsFeature(roads[parts[0]]) ff.setGeometry(averaged) index.addFeature(ff) roads[ff.id()] = ff ff = QgsFeature(f) ff.setGeometry(averaged) index.addFeature(ff) roads[_id] = ff collapsed[_id] = ff processed.add(_id) processed.add(parts[0]) total = 5.0 / len(processed) current = 0 for _, f in collapsed.items(): if feedback.isCanceled(): break sink.addFeature(f, QgsFeatureSink.FastInsert) current += 1 feedback.setProgress(95 + int(current * total)) return {self.OUTPUT: dest_id}
def snapGeometry(self, geometry, snapTolerance, mode=PreferNodes): """ Snaps a QgsGeometry in the reference layer :param geometry: QgsGeometry :param snapTolerance: float :param mode: DsgGeometrySnapper.PreferNodes or DsgGeometrySnapper.PreferClosest :return: """ center = QgsPointV2(geometry.boundingBox().center()) # Get potential reference features and construct snap index refGeometries = [] searchBounds = geometry.boundingBox() searchBounds.grow(snapTolerance) # filter by bounding box to get candidates refFeatureIds = self.index.intersects(searchBounds) # End here in case we don't find candidates if len(refFeatureIds) == 0: return geometry # speeding up the process to consider only intersecting geometries refFeatureRequest = QgsFeatureRequest().setFilterFids(refFeatureIds) for refFeature in self.referenceLayer.getFeatures(refFeatureRequest): refGeometry = refFeature.geometry() segments = self.breakQgsGeometryIntoSegments(refGeometry) # testing intersection for segment in segments: if segment.intersects(searchBounds): refGeometries.append(segment) # End here in case we don't find geometries if len(refGeometries) == 0: return geometry # building geometry index refDict, index = self.buildReferenceIndex(refGeometries) refSnapIndex = DsgSnapIndex(center, 10 * snapTolerance) for geom in refGeometries: refSnapIndex.addGeometry(geom.geometry()) # Snap geometries subjGeom = geometry.geometry().clone() subjPointFlags = [] # Pass 1: snap vertices of subject geometry to reference vertices for iPart in xrange(subjGeom.partCount()): subjPointFlags.append([]) for iRing in xrange(subjGeom.ringCount(iPart)): subjPointFlags[iPart].append([]) for iVert in xrange(self.polyLineSize(subjGeom, iPart, iRing)): vidx = QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex) p = QgsPointV2(subjGeom.vertexAt(vidx)) pF = QgsPoint(p.toQPointF()) snapPoint, snapSegment = refSnapIndex.getSnapItem( p, snapTolerance) success = snapPoint or snapSegment if not success: subjPointFlags[iPart][iRing].append( DsgGeometrySnapper.Unsnapped) else: if mode == DsgGeometrySnapper.PreferNodes: # Prefer snapping to point if snapPoint: subjGeom.moveVertex(vidx, snapPoint.getSnapPoint(p)) subjPointFlags[iPart][iRing].append( DsgGeometrySnapper.SnappedToRefNode) elif snapSegment: subjGeom.moveVertex( vidx, snapSegment.getSnapPoint(p)) subjPointFlags[iPart][iRing].append( DsgGeometrySnapper.SnappedToRefSegment) elif mode == DsgGeometrySnapper.PreferClosest: nodeSnap = None segmentSnap = None distanceNode = sys.float_info.max distanceSegment = sys.float_info.max if snapPoint: nodeSnap = snapPoint.getSnapPoint(p) nodeSnapF = QgsPoint(nodeSnap.toQPointF()) distanceNode = nodeSnapF.sqrDist(pF) if snapSegment: segmentSnap = snapSegment.getSnapPoint(p) segmentSnapF = QgsPoint( segmentSnap.toQPointF()) distanceSegment = segmentSnapF.sqrDist(pF) if snapPoint and (distanceNode < distanceSegment): subjGeom.moveVertex(vidx, nodeSnap) subjPointFlags[iPart][iRing].append( DsgGeometrySnapper.SnappedToRefNode) elif snapSegment: subjGeom.moveVertex(vidx, segmentSnap) subjPointFlags[iPart][iRing].append( DsgGeometrySnapper.SnappedToRefSegment) #nothing more to do for points if isinstance(subjGeom, QgsPointV2): return QgsGeometry(subjGeom) # SnapIndex for subject feature subjSnapIndex = DsgSnapIndex(center, 10 * snapTolerance) subjSnapIndex.addGeometry(subjGeom) origSubjGeom = subjGeom.clone() origSubjSnapIndex = DsgSnapIndex(center, 10 * snapTolerance) origSubjSnapIndex.addGeometry(origSubjGeom) # Pass 2: add missing vertices to subject geometry for refGeom in refGeometries: for iPart in xrange(refGeom.geometry().partCount()): for iRing in xrange(refGeom.geometry().ringCount(iPart)): for iVert in xrange( self.polyLineSize(refGeom.geometry(), iPart, iRing)): point = refGeom.geometry().vertexAt( QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex)) # QgsPoint used to calculate squared distance pointF = QgsPoint(point.toQPointF()) snapPoint, snapSegment = subjSnapIndex.getSnapItem( point, snapTolerance) success = snapPoint or snapSegment if success: # Snap to segment, unless a subject point was already snapped to the reference point if snapPoint and (QgsPoint( snapPoint.getSnapPoint(point).toQPointF()). sqrDist(pointF) < 1E-16): continue elif snapSegment: # Look if there is a closer reference segment, if so, ignore this point pProj = snapSegment.getSnapPoint(point) pProjF = QgsPoint(pProj.toQPointF()) closest = refSnapIndex.getClosestSnapToPoint( point, pProj) closestF = QgsPoint(closest.toQPointF()) if pProjF.sqrDist(pointF) > pProjF.sqrDist( closestF): continue # If we are too far away from the original geometry, do nothing if not origSubjSnapIndex.getSnapItem( point, snapTolerance): continue idx = snapSegment.idxFrom subjGeom.insertVertex( QgsVertexId(idx.vidx.part, idx.vidx.ring, idx.vidx.vertex + 1, QgsVertexId.SegmentVertex), point) subjPointFlags[idx.vidx.part][ idx.vidx.ring].insert( idx.vidx.vertex + 1, DsgGeometrySnapper.SnappedToRefNode) subjSnapIndex = DsgSnapIndex( center, 10 * snapTolerance) subjSnapIndex.addGeometry(subjGeom) # Pass 3: remove superfluous vertices: all vertices which are snapped to a segment and not preceded or succeeded by an unsnapped vertex for iPart in xrange(subjGeom.partCount()): for iRing in xrange(subjGeom.ringCount(iPart)): ringIsClosed = subjGeom.vertexAt( QgsVertexId( iPart, iRing, 0, QgsVertexId.SegmentVertex)) == subjGeom.vertexAt( QgsVertexId(iPart, iRing, subjGeom.vertexCount(iPart, iRing) - 1, QgsVertexId.SegmentVertex)) nVerts = self.polyLineSize(subjGeom, iPart, iRing) iVert = 0 while iVert < nVerts: iPrev = (iVert - 1 + nVerts) % nVerts iNext = (iVert + 1) % nVerts pMid = subjGeom.vertexAt( QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex)) pPrev = subjGeom.vertexAt( QgsVertexId(iPart, iRing, iPrev, QgsVertexId.SegmentVertex)) pNext = subjGeom.vertexAt( QgsVertexId(iPart, iRing, iNext, QgsVertexId.SegmentVertex)) pointOnSeg = self.projPointOnSegment(pMid, pPrev, pNext) pointOnSegF = QgsPoint(pointOnSeg.toQPointF()) pMidF = QgsPoint(pMid.toQPointF()) dist = pointOnSegF.sqrDist(pMidF) if subjPointFlags[iPart][iRing][iVert] == DsgGeometrySnapper.SnappedToRefSegment \ and subjPointFlags[iPart][iRing][iPrev] != DsgGeometrySnapper.Unsnapped \ and subjPointFlags[iPart][iRing][iNext] != DsgGeometrySnapper.Unsnapped \ and dist < 1E-12: if (ringIsClosed and nVerts > 3) or (not ringIsClosed and nVerts > 2): subjGeom.deleteVertex( QgsVertexId(iPart, iRing, iVert, QgsVertexId.SegmentVertex)) del subjPointFlags[iPart][iRing][iVert] iVert -= 1 nVerts -= 1 else: # Don't delete vertices if this would result in a degenerate geometry break iVert += 1 return QgsGeometry(subjGeom)
def processAlgorithm( self, # pylint: disable=missing-function-docstring,too-many-statements,too-many-branches,too-many-locals parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, source.fields(), QgsWkbTypes.LineString, source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) roundabout_expression_string = self.parameterAsExpression( parameters, self.EXPRESSION, context) # step 1 - find all roundabouts exp = QgsExpression(roundabout_expression_string) expression_context = self.createExpressionContext( parameters, context, source) exp.prepare(expression_context) roundabouts = [] not_roundabouts = {} not_roundabout_index = QgsSpatialIndex() total = 10.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() _id = 1 for current, feature in enumerate(features): if feedback.isCanceled(): break def add_feature(f, _id, geom, is_roundabout): output_feature = QgsFeature(f) output_feature.setGeometry(geom) output_feature.setId(_id) if is_roundabout: roundabouts.append(output_feature) else: not_roundabouts[output_feature.id()] = output_feature not_roundabout_index.addFeature(output_feature) expression_context.setFeature(feature) is_roundabout = exp.evaluate(expression_context) if not feature.geometry().wkbType() == QgsWkbTypes.LineString: geom = feature.geometry() for p in geom.parts(): add_feature(feature, _id, QgsGeometry(p.clone()), is_roundabout) _id += 1 else: add_feature(feature, _id, feature.geometry(), is_roundabout) _id += 1 # Update the progress bar feedback.setProgress(int(current * total)) feedback.pushInfo( self.tr('Found {} roundabout parts'.format(len(roundabouts)))) feedback.pushInfo( self.tr('Found {} not roundabouts'.format(len(not_roundabouts)))) if feedback.isCanceled(): return {self.OUTPUT: dest_id} all_roundabouts = QgsGeometry.unaryUnion( [r.geometry() for r in roundabouts]) feedback.setProgress(20) all_roundabouts = all_roundabouts.mergeLines() feedback.setProgress(25) total = 70.0 / all_roundabouts.constGet().numGeometries( ) if all_roundabouts.isMultipart() else 1 for current, roundabout in enumerate(all_roundabouts.parts()): touching = not_roundabout_index.intersects( roundabout.boundingBox()) if not touching: continue if feedback.isCanceled(): break roundabout_engine = QgsGeometry.createGeometryEngine(roundabout) roundabout_engine.prepareGeometry() roundabout_geom = QgsGeometry(roundabout.clone()) roundabout_centroid = roundabout_geom.centroid() other_points = [] # find all touching roads, and move the touching part to the centroid for t in touching: touching_geom = not_roundabouts[t].geometry() touching_road = touching_geom.constGet().clone() if not roundabout_engine.touches(touching_road): # print('not touching!!') continue # work out if start or end of line touched the roundabout nearest = roundabout_geom.nearestPoint(touching_geom) _, v = touching_geom.closestVertexWithContext( nearest.asPoint()) if v == 0: # started at roundabout other_points.append((touching_road.endPoint(), True, t)) else: # ended at roundabout other_points.append((touching_road.startPoint(), False, t)) if not other_points: continue # see if any incoming segments originate at the same place ("V" patterns) averaged = set() for point1, started_at_roundabout1, id1 in other_points: if id1 in averaged: continue if feedback.isCanceled(): break parts_to_average = [id1] for point2, _, id2 in other_points: if id2 == id1: continue if point2 != point1: # todo tolerance? continue parts_to_average.append(id2) if len(parts_to_average) == 1: # not a <O pattern, just a round coming straight to the roundabout line = not_roundabouts[id1].geometry().constGet().clone() if started_at_roundabout1: # extend start of line to roundabout centroid line.moveVertex(QgsVertexId(0, 0, 0), roundabout_centroid.constGet()) else: # extend end of line to roundabout centroid line.moveVertex( QgsVertexId(0, 0, line.numPoints() - 1), roundabout_centroid.constGet()) not_roundabout_index.deleteFeature( not_roundabouts[parts_to_average[0]]) not_roundabouts[parts_to_average[0]].setGeometry( QgsGeometry(line)) not_roundabout_index.addFeature( not_roundabouts[parts_to_average[0]]) elif len(parts_to_average) == 2: # <O pattern src_part, other_part = parts_to_average # pylint: disable=unbalanced-tuple-unpacking averaged.add(src_part) averaged.add(other_part) averaged_line = GeometryUtils.average_linestrings( not_roundabouts[src_part].geometry().constGet(), not_roundabouts[other_part].geometry().constGet()) if started_at_roundabout1: # extend start of line to roundabout centroid averaged_line.moveVertex( QgsVertexId(0, 0, 0), roundabout_centroid.constGet()) else: # extend end of line to roundabout centroid averaged_line.moveVertex( QgsVertexId(0, 0, averaged_line.numPoints() - 1), roundabout_centroid.constGet()) not_roundabout_index.deleteFeature( not_roundabouts[src_part]) not_roundabouts[src_part].setGeometry( QgsGeometry(averaged_line)) not_roundabout_index.addFeature(not_roundabouts[src_part]) not_roundabout_index.deleteFeature( not_roundabouts[other_part]) del not_roundabouts[other_part] feedback.setProgress(25 + int(current * total)) total = 5.0 / len(not_roundabouts) current = 0 for _, f in not_roundabouts.items(): if feedback.isCanceled(): break sink.addFeature(f, QgsFeatureSink.FastInsert) current += 1 feedback.setProgress(95 + int(current * total)) return {self.OUTPUT: dest_id}
def fromQgsGeometry(cls, geometry, z_func, transform_func, centroid=True, drop_z=False, ccw2d=False, use_z_func_cache=False, use_earcut=False): geom = cls() if z_func: if use_z_func_cache: cache = FunctionCacheXY(z_func) z_func = cache.func else: z_func = lambda x, y: 0 if drop_z: g = geometry.get() g.dropZValue() else: g = geometry.constGet() if centroid: if drop_z: zf = z_func else: # use z coordinate of first vertex (until QgsAbstractGeometry supports z coordinate of centroid) zf = lambda x, y: g.vertexAt(QgsVertexId(0, 0, 0)).z() + z_func(pt.x(), pt.y()) pt = geometry.centroid().asPoint() geom.centroids.append(transform_func(pt.x(), pt.y(), zf(pt.x(), pt.y()))) # vertex transform function if drop_z: v_func = lambda x, y, z: transform_func(x, y, z_func(x, y)) else: v_func = lambda x, y, z: transform_func(x, y, z + z_func(x, y)) # triangulation if use_earcut: vertices = [] for poly in cls.nestedPointList(g): if len(poly) == 1 and len(poly[0]) == 4: vertices.extend([v_func(pt.x(), pt.y(), pt.z()) for pt in poly[0][0:3]]) else: bnds = [[[pt.x(), pt.y(), pt.z()] for pt in bnd] for bnd in poly] data = earcut.flatten(bnds) v = data["vertices"] triangles = earcut.earcut(v, data["holes"], 3) vertices.extend([v_func(v[3 * i], v[3 * i + 1], v[3 * i + 2]) for i in triangles]) else: tes = QgsTessellator(0, 0, False) addPolygon = tes.addPolygon for poly in cls.singleGeometries(g): addPolygon(poly, 0) # mp = tes.asMultiPolygon() # not available data = tes.data() # [x0, z0, -y0, x1, z1, -y1, ...] vertices = [v_func(x, -my, z) for x, z, my in [data[i:i + 3] for i in range(0, len(data), 3)]] if ccw2d: # orient triangles to counter-clockwise order tris = [] for v0, v1, v2 in [vertices[i:i + 3] for i in range(0, len(vertices), 3)]: if GeometryUtils.isClockwise([v0, v1, v2, v0]): tris.append([v0, v2, v1]) else: tris.append([v0, v1, v2]) geom.triangles = tris else: # use original vertex order geom.triangles = [vertices[i:i + 3] for i in range(0, len(vertices), 3)] return geom