Draw line to next feature with distance as label (dynamic)
Clash Royale CLAN TAG#URR8PPP
up vote
2
down vote
favorite
I would like to dynamically draw the shortest line to the next object (Geometry Generator?).
Example: I have a network-layer (streets) and a point-layer. I know the distance from every point to the closes street (static, by using v.distance or one of the plugins NNjoin or refFunctions) but I would like to have a dynamically drawn line to the next street with the distance as label. It's important to have it dynamic, so if I move a point, the line and distance should update.
Since I use GeoPackage a SQL-Solution would also be nice.
In the screenshot below you can see my static solution:
qgis labeling sql qgis-3 geometry-generator
add a comment |
up vote
2
down vote
favorite
I would like to dynamically draw the shortest line to the next object (Geometry Generator?).
Example: I have a network-layer (streets) and a point-layer. I know the distance from every point to the closes street (static, by using v.distance or one of the plugins NNjoin or refFunctions) but I would like to have a dynamically drawn line to the next street with the distance as label. It's important to have it dynamic, so if I move a point, the line and distance should update.
Since I use GeoPackage a SQL-Solution would also be nice.
In the screenshot below you can see my static solution:
qgis labeling sql qgis-3 geometry-generator
Sounds like something that Attribute Assistant for ArcMap would be able to do although I don't know how specifically. But it can be set up to use geometry changes as a trigger to update attributes. I've been looking for a while to see if there's a similar tool available for QGIS but I haven't had any luck yet. Sorry not much help.
– TeddyTedTed
2 days ago
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I would like to dynamically draw the shortest line to the next object (Geometry Generator?).
Example: I have a network-layer (streets) and a point-layer. I know the distance from every point to the closes street (static, by using v.distance or one of the plugins NNjoin or refFunctions) but I would like to have a dynamically drawn line to the next street with the distance as label. It's important to have it dynamic, so if I move a point, the line and distance should update.
Since I use GeoPackage a SQL-Solution would also be nice.
In the screenshot below you can see my static solution:
qgis labeling sql qgis-3 geometry-generator
I would like to dynamically draw the shortest line to the next object (Geometry Generator?).
Example: I have a network-layer (streets) and a point-layer. I know the distance from every point to the closes street (static, by using v.distance or one of the plugins NNjoin or refFunctions) but I would like to have a dynamically drawn line to the next street with the distance as label. It's important to have it dynamic, so if I move a point, the line and distance should update.
Since I use GeoPackage a SQL-Solution would also be nice.
In the screenshot below you can see my static solution:
qgis labeling sql qgis-3 geometry-generator
qgis labeling sql qgis-3 geometry-generator
edited 2 days ago
asked 2 days ago
MAP
2,36523566
2,36523566
Sounds like something that Attribute Assistant for ArcMap would be able to do although I don't know how specifically. But it can be set up to use geometry changes as a trigger to update attributes. I've been looking for a while to see if there's a similar tool available for QGIS but I haven't had any luck yet. Sorry not much help.
– TeddyTedTed
2 days ago
add a comment |
Sounds like something that Attribute Assistant for ArcMap would be able to do although I don't know how specifically. But it can be set up to use geometry changes as a trigger to update attributes. I've been looking for a while to see if there's a similar tool available for QGIS but I haven't had any luck yet. Sorry not much help.
– TeddyTedTed
2 days ago
Sounds like something that Attribute Assistant for ArcMap would be able to do although I don't know how specifically. But it can be set up to use geometry changes as a trigger to update attributes. I've been looking for a while to see if there's a similar tool available for QGIS but I haven't had any luck yet. Sorry not much help.
– TeddyTedTed
2 days ago
Sounds like something that Attribute Assistant for ArcMap would be able to do although I don't know how specifically. But it can be set up to use geometry changes as a trigger to update attributes. I've been looking for a while to see if there's a similar tool available for QGIS but I haven't had any luck yet. Sorry not much help.
– TeddyTedTed
2 days ago
add a comment |
2 Answers
2
active
oldest
votes
up vote
1
down vote
accepted
You can do this with geometry generator in QGIS 3.x using the collect
aggregate within the aggregate()
function to extract the collected geometry of another layer and use it in an expression.
Here are the expressions I used to generate this example - the line layer was called Line
; you just need to replace that with the name of your line layer.
Geometry generator (on point layer):
shortest_line($geometry,aggregate('Line','collect',$geometry))
Label:
round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)
Label placement (Label -> Placement -> Data defined):
Coordinate X - x(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Coordinate Y -y(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Rotation - line_interpolate_angle(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2)+90
Label alignment horizontal - 'Center'
add a comment |
up vote
3
down vote
Here are two Python solution:
Rubber Band approach:
- searches for the nearest point on the road
- creates a rubber band
labels the rubber band
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
def removeCanvasItems():
canvas_items = [ i for i in iface.mapCanvas().scene().items() if issubclass(type(i), QgsRubberBand) or issubclass(type(i), QgsTextAnnotationItem) or issubclass(type(i), QgsVertexMarker)]
if canvas_items:
for item in canvas_items:
if item in iface.mapCanvas().scene().items():
iface.mapCanvas().scene().removeItem(item)
def nearest_road():
#removeCanvasItems() #removes the old rubberbands, calling this causes a mini dump
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
r_polyline = QgsRubberBand(iface.mapCanvas(), False)
r_polyline.setToGeometry(QgsGeometry.fromPolyline(points), None)
r_polyline.setWidth(2)
r_polyline.setColor(QColor(0,255,0,255))
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
symbol = QgsMarkerSymbolV2()
symbol.setSize(0)
lbltext = QTextDocument(str(round(length,2)) + 'm')
label = QgsTextAnnotationItem(iface.mapCanvas())
label.setMapPosition(seg.geometry().interpolate(length/2.0).asPoint())
label.setDocument(lbltext)
label.setFrameSize(QSizeF(lbltext.size().width(),lbltext.size().height()))
label.setFrameBorderWidth(0)
label.setFrameColor(QColor("#ff4b00"))
label.setFrameBackgroundColor(QColor("#ff4b00"))
label.setMarkerSymbol(symbol)
nearest_road() #calling the function the first time
p_lyr.geometryChanged.connect(nearest_road) # creates new rubberband when point is moved
p_lyr.featureAdded.connect(nearest_road) # creates new rubberband when a new point is created
When I want to remove the old rubber bands while adding a new feature or move an existing feature, I get a mini dump and QGIS crashes.
Vector Layer approach:
Instead of rubber bands you could use a layer, which stores the lines and the distance. In my case I have a shapefile with two attributes: id (int), and distance(double). With this layer you can better label and style your features.
The function removeFeatures()
, removes all features from the distance layer. Better way is to get the ID of the feature that is moved and deletes only this line from the provider. I think I will update this soon.
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
d_lyr = QgsMapLayerRegistry.instance().mapLayersByName('distance')[0]
prov = d_lyr.dataProvider()
def removeFeatures():
with edit(d_lyr):
listOfIds = [feat.id() for feat in d_lyr.getFeatures()]
d_lyr.deleteFeatures( listOfIds )
def nearest_road():
removeFeatures()
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
seg.setAttributes([1,seg.geometry().length()])
prov.addFeatures([seg])
d_lyr.updateExtents()
d_lyr.triggerRepaint()
d_lyr.updateFields()
nearest_road()
p_lyr.geometryChanged.connect(nearest_road)
p_lyr.featureAdded.connect(nearest_road)
Great work! Very nice! But I wasn't looking for a python solution. And that code is not working with PyQt5 of QGIS3.x. But this should help QGIS 2.x users in many cases. Thumbs up!
– MAP
2 days ago
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
You can do this with geometry generator in QGIS 3.x using the collect
aggregate within the aggregate()
function to extract the collected geometry of another layer and use it in an expression.
Here are the expressions I used to generate this example - the line layer was called Line
; you just need to replace that with the name of your line layer.
Geometry generator (on point layer):
shortest_line($geometry,aggregate('Line','collect',$geometry))
Label:
round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)
Label placement (Label -> Placement -> Data defined):
Coordinate X - x(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Coordinate Y -y(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Rotation - line_interpolate_angle(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2)+90
Label alignment horizontal - 'Center'
add a comment |
up vote
1
down vote
accepted
You can do this with geometry generator in QGIS 3.x using the collect
aggregate within the aggregate()
function to extract the collected geometry of another layer and use it in an expression.
Here are the expressions I used to generate this example - the line layer was called Line
; you just need to replace that with the name of your line layer.
Geometry generator (on point layer):
shortest_line($geometry,aggregate('Line','collect',$geometry))
Label:
round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)
Label placement (Label -> Placement -> Data defined):
Coordinate X - x(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Coordinate Y -y(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Rotation - line_interpolate_angle(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2)+90
Label alignment horizontal - 'Center'
add a comment |
up vote
1
down vote
accepted
up vote
1
down vote
accepted
You can do this with geometry generator in QGIS 3.x using the collect
aggregate within the aggregate()
function to extract the collected geometry of another layer and use it in an expression.
Here are the expressions I used to generate this example - the line layer was called Line
; you just need to replace that with the name of your line layer.
Geometry generator (on point layer):
shortest_line($geometry,aggregate('Line','collect',$geometry))
Label:
round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)
Label placement (Label -> Placement -> Data defined):
Coordinate X - x(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Coordinate Y -y(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Rotation - line_interpolate_angle(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2)+90
Label alignment horizontal - 'Center'
You can do this with geometry generator in QGIS 3.x using the collect
aggregate within the aggregate()
function to extract the collected geometry of another layer and use it in an expression.
Here are the expressions I used to generate this example - the line layer was called Line
; you just need to replace that with the name of your line layer.
Geometry generator (on point layer):
shortest_line($geometry,aggregate('Line','collect',$geometry))
Label:
round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)
Label placement (Label -> Placement -> Data defined):
Coordinate X - x(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Coordinate Y -y(line_interpolate_point(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2))
Rotation - line_interpolate_angle(shortest_line($geometry,aggregate('Line','collect',$geometry)),round(length(shortest_line($geometry,aggregate('Line','collect',$geometry))),2)/2)+90
Label alignment horizontal - 'Center'
answered yesterday
she_weeds
1,012313
1,012313
add a comment |
add a comment |
up vote
3
down vote
Here are two Python solution:
Rubber Band approach:
- searches for the nearest point on the road
- creates a rubber band
labels the rubber band
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
def removeCanvasItems():
canvas_items = [ i for i in iface.mapCanvas().scene().items() if issubclass(type(i), QgsRubberBand) or issubclass(type(i), QgsTextAnnotationItem) or issubclass(type(i), QgsVertexMarker)]
if canvas_items:
for item in canvas_items:
if item in iface.mapCanvas().scene().items():
iface.mapCanvas().scene().removeItem(item)
def nearest_road():
#removeCanvasItems() #removes the old rubberbands, calling this causes a mini dump
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
r_polyline = QgsRubberBand(iface.mapCanvas(), False)
r_polyline.setToGeometry(QgsGeometry.fromPolyline(points), None)
r_polyline.setWidth(2)
r_polyline.setColor(QColor(0,255,0,255))
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
symbol = QgsMarkerSymbolV2()
symbol.setSize(0)
lbltext = QTextDocument(str(round(length,2)) + 'm')
label = QgsTextAnnotationItem(iface.mapCanvas())
label.setMapPosition(seg.geometry().interpolate(length/2.0).asPoint())
label.setDocument(lbltext)
label.setFrameSize(QSizeF(lbltext.size().width(),lbltext.size().height()))
label.setFrameBorderWidth(0)
label.setFrameColor(QColor("#ff4b00"))
label.setFrameBackgroundColor(QColor("#ff4b00"))
label.setMarkerSymbol(symbol)
nearest_road() #calling the function the first time
p_lyr.geometryChanged.connect(nearest_road) # creates new rubberband when point is moved
p_lyr.featureAdded.connect(nearest_road) # creates new rubberband when a new point is created
When I want to remove the old rubber bands while adding a new feature or move an existing feature, I get a mini dump and QGIS crashes.
Vector Layer approach:
Instead of rubber bands you could use a layer, which stores the lines and the distance. In my case I have a shapefile with two attributes: id (int), and distance(double). With this layer you can better label and style your features.
The function removeFeatures()
, removes all features from the distance layer. Better way is to get the ID of the feature that is moved and deletes only this line from the provider. I think I will update this soon.
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
d_lyr = QgsMapLayerRegistry.instance().mapLayersByName('distance')[0]
prov = d_lyr.dataProvider()
def removeFeatures():
with edit(d_lyr):
listOfIds = [feat.id() for feat in d_lyr.getFeatures()]
d_lyr.deleteFeatures( listOfIds )
def nearest_road():
removeFeatures()
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
seg.setAttributes([1,seg.geometry().length()])
prov.addFeatures([seg])
d_lyr.updateExtents()
d_lyr.triggerRepaint()
d_lyr.updateFields()
nearest_road()
p_lyr.geometryChanged.connect(nearest_road)
p_lyr.featureAdded.connect(nearest_road)
Great work! Very nice! But I wasn't looking for a python solution. And that code is not working with PyQt5 of QGIS3.x. But this should help QGIS 2.x users in many cases. Thumbs up!
– MAP
2 days ago
add a comment |
up vote
3
down vote
Here are two Python solution:
Rubber Band approach:
- searches for the nearest point on the road
- creates a rubber band
labels the rubber band
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
def removeCanvasItems():
canvas_items = [ i for i in iface.mapCanvas().scene().items() if issubclass(type(i), QgsRubberBand) or issubclass(type(i), QgsTextAnnotationItem) or issubclass(type(i), QgsVertexMarker)]
if canvas_items:
for item in canvas_items:
if item in iface.mapCanvas().scene().items():
iface.mapCanvas().scene().removeItem(item)
def nearest_road():
#removeCanvasItems() #removes the old rubberbands, calling this causes a mini dump
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
r_polyline = QgsRubberBand(iface.mapCanvas(), False)
r_polyline.setToGeometry(QgsGeometry.fromPolyline(points), None)
r_polyline.setWidth(2)
r_polyline.setColor(QColor(0,255,0,255))
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
symbol = QgsMarkerSymbolV2()
symbol.setSize(0)
lbltext = QTextDocument(str(round(length,2)) + 'm')
label = QgsTextAnnotationItem(iface.mapCanvas())
label.setMapPosition(seg.geometry().interpolate(length/2.0).asPoint())
label.setDocument(lbltext)
label.setFrameSize(QSizeF(lbltext.size().width(),lbltext.size().height()))
label.setFrameBorderWidth(0)
label.setFrameColor(QColor("#ff4b00"))
label.setFrameBackgroundColor(QColor("#ff4b00"))
label.setMarkerSymbol(symbol)
nearest_road() #calling the function the first time
p_lyr.geometryChanged.connect(nearest_road) # creates new rubberband when point is moved
p_lyr.featureAdded.connect(nearest_road) # creates new rubberband when a new point is created
When I want to remove the old rubber bands while adding a new feature or move an existing feature, I get a mini dump and QGIS crashes.
Vector Layer approach:
Instead of rubber bands you could use a layer, which stores the lines and the distance. In my case I have a shapefile with two attributes: id (int), and distance(double). With this layer you can better label and style your features.
The function removeFeatures()
, removes all features from the distance layer. Better way is to get the ID of the feature that is moved and deletes only this line from the provider. I think I will update this soon.
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
d_lyr = QgsMapLayerRegistry.instance().mapLayersByName('distance')[0]
prov = d_lyr.dataProvider()
def removeFeatures():
with edit(d_lyr):
listOfIds = [feat.id() for feat in d_lyr.getFeatures()]
d_lyr.deleteFeatures( listOfIds )
def nearest_road():
removeFeatures()
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
seg.setAttributes([1,seg.geometry().length()])
prov.addFeatures([seg])
d_lyr.updateExtents()
d_lyr.triggerRepaint()
d_lyr.updateFields()
nearest_road()
p_lyr.geometryChanged.connect(nearest_road)
p_lyr.featureAdded.connect(nearest_road)
Great work! Very nice! But I wasn't looking for a python solution. And that code is not working with PyQt5 of QGIS3.x. But this should help QGIS 2.x users in many cases. Thumbs up!
– MAP
2 days ago
add a comment |
up vote
3
down vote
up vote
3
down vote
Here are two Python solution:
Rubber Band approach:
- searches for the nearest point on the road
- creates a rubber band
labels the rubber band
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
def removeCanvasItems():
canvas_items = [ i for i in iface.mapCanvas().scene().items() if issubclass(type(i), QgsRubberBand) or issubclass(type(i), QgsTextAnnotationItem) or issubclass(type(i), QgsVertexMarker)]
if canvas_items:
for item in canvas_items:
if item in iface.mapCanvas().scene().items():
iface.mapCanvas().scene().removeItem(item)
def nearest_road():
#removeCanvasItems() #removes the old rubberbands, calling this causes a mini dump
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
r_polyline = QgsRubberBand(iface.mapCanvas(), False)
r_polyline.setToGeometry(QgsGeometry.fromPolyline(points), None)
r_polyline.setWidth(2)
r_polyline.setColor(QColor(0,255,0,255))
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
symbol = QgsMarkerSymbolV2()
symbol.setSize(0)
lbltext = QTextDocument(str(round(length,2)) + 'm')
label = QgsTextAnnotationItem(iface.mapCanvas())
label.setMapPosition(seg.geometry().interpolate(length/2.0).asPoint())
label.setDocument(lbltext)
label.setFrameSize(QSizeF(lbltext.size().width(),lbltext.size().height()))
label.setFrameBorderWidth(0)
label.setFrameColor(QColor("#ff4b00"))
label.setFrameBackgroundColor(QColor("#ff4b00"))
label.setMarkerSymbol(symbol)
nearest_road() #calling the function the first time
p_lyr.geometryChanged.connect(nearest_road) # creates new rubberband when point is moved
p_lyr.featureAdded.connect(nearest_road) # creates new rubberband when a new point is created
When I want to remove the old rubber bands while adding a new feature or move an existing feature, I get a mini dump and QGIS crashes.
Vector Layer approach:
Instead of rubber bands you could use a layer, which stores the lines and the distance. In my case I have a shapefile with two attributes: id (int), and distance(double). With this layer you can better label and style your features.
The function removeFeatures()
, removes all features from the distance layer. Better way is to get the ID of the feature that is moved and deletes only this line from the provider. I think I will update this soon.
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
d_lyr = QgsMapLayerRegistry.instance().mapLayersByName('distance')[0]
prov = d_lyr.dataProvider()
def removeFeatures():
with edit(d_lyr):
listOfIds = [feat.id() for feat in d_lyr.getFeatures()]
d_lyr.deleteFeatures( listOfIds )
def nearest_road():
removeFeatures()
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
seg.setAttributes([1,seg.geometry().length()])
prov.addFeatures([seg])
d_lyr.updateExtents()
d_lyr.triggerRepaint()
d_lyr.updateFields()
nearest_road()
p_lyr.geometryChanged.connect(nearest_road)
p_lyr.featureAdded.connect(nearest_road)
Here are two Python solution:
Rubber Band approach:
- searches for the nearest point on the road
- creates a rubber band
labels the rubber band
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
def removeCanvasItems():
canvas_items = [ i for i in iface.mapCanvas().scene().items() if issubclass(type(i), QgsRubberBand) or issubclass(type(i), QgsTextAnnotationItem) or issubclass(type(i), QgsVertexMarker)]
if canvas_items:
for item in canvas_items:
if item in iface.mapCanvas().scene().items():
iface.mapCanvas().scene().removeItem(item)
def nearest_road():
#removeCanvasItems() #removes the old rubberbands, calling this causes a mini dump
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
r_polyline = QgsRubberBand(iface.mapCanvas(), False)
r_polyline.setToGeometry(QgsGeometry.fromPolyline(points), None)
r_polyline.setWidth(2)
r_polyline.setColor(QColor(0,255,0,255))
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
symbol = QgsMarkerSymbolV2()
symbol.setSize(0)
lbltext = QTextDocument(str(round(length,2)) + 'm')
label = QgsTextAnnotationItem(iface.mapCanvas())
label.setMapPosition(seg.geometry().interpolate(length/2.0).asPoint())
label.setDocument(lbltext)
label.setFrameSize(QSizeF(lbltext.size().width(),lbltext.size().height()))
label.setFrameBorderWidth(0)
label.setFrameColor(QColor("#ff4b00"))
label.setFrameBackgroundColor(QColor("#ff4b00"))
label.setMarkerSymbol(symbol)
nearest_road() #calling the function the first time
p_lyr.geometryChanged.connect(nearest_road) # creates new rubberband when point is moved
p_lyr.featureAdded.connect(nearest_road) # creates new rubberband when a new point is created
When I want to remove the old rubber bands while adding a new feature or move an existing feature, I get a mini dump and QGIS crashes.
Vector Layer approach:
Instead of rubber bands you could use a layer, which stores the lines and the distance. In my case I have a shapefile with two attributes: id (int), and distance(double). With this layer you can better label and style your features.
The function removeFeatures()
, removes all features from the distance layer. Better way is to get the ID of the feature that is moved and deletes only this line from the provider. I think I will update this soon.
from qgis.gui import *
from qgis.utils import *
from qgis.core import *
from PyQt4.QtGui import *
from PyQt4.QtCore import *
p_lyr = QgsMapLayerRegistry.instance().mapLayersByName('points')[0]
l_lyr = QgsMapLayerRegistry.instance().mapLayersByName('roads')[0]
lines = [feature for feature in l_lyr.getFeatures()]
d_lyr = QgsMapLayerRegistry.instance().mapLayersByName('distance')[0]
prov = d_lyr.dataProvider()
def removeFeatures():
with edit(d_lyr):
listOfIds = [feat.id() for feat in d_lyr.getFeatures()]
d_lyr.deleteFeatures( listOfIds )
def nearest_road():
removeFeatures()
for point in p_lyr.getFeatures():
minDistPoint = min([l.geometry().closestSegmentWithContext(QgsPoint(point.geometry().asPoint())) for l in lines])[1]
points = [QgsPoint(point.geometry().asPoint()), QgsPoint(minDistPoint[0], minDistPoint[1])]
seg = QgsFeature()
seg.setGeometry(QgsGeometry.fromPolyline(points))
geom = seg.geometry()
length = seg.geometry().length()
seg.setAttributes([1,seg.geometry().length()])
prov.addFeatures([seg])
d_lyr.updateExtents()
d_lyr.triggerRepaint()
d_lyr.updateFields()
nearest_road()
p_lyr.geometryChanged.connect(nearest_road)
p_lyr.featureAdded.connect(nearest_road)
edited 2 days ago
answered 2 days ago
Stefan
2,55411638
2,55411638
Great work! Very nice! But I wasn't looking for a python solution. And that code is not working with PyQt5 of QGIS3.x. But this should help QGIS 2.x users in many cases. Thumbs up!
– MAP
2 days ago
add a comment |
Great work! Very nice! But I wasn't looking for a python solution. And that code is not working with PyQt5 of QGIS3.x. But this should help QGIS 2.x users in many cases. Thumbs up!
– MAP
2 days ago
Great work! Very nice! But I wasn't looking for a python solution. And that code is not working with PyQt5 of QGIS3.x. But this should help QGIS 2.x users in many cases. Thumbs up!
– MAP
2 days ago
Great work! Very nice! But I wasn't looking for a python solution. And that code is not working with PyQt5 of QGIS3.x. But this should help QGIS 2.x users in many cases. Thumbs up!
– MAP
2 days ago
add a comment |
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fgis.stackexchange.com%2fquestions%2f302886%2fdraw-line-to-next-feature-with-distance-as-label-dynamic%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Sounds like something that Attribute Assistant for ArcMap would be able to do although I don't know how specifically. But it can be set up to use geometry changes as a trigger to update attributes. I've been looking for a while to see if there's a similar tool available for QGIS but I haven't had any luck yet. Sorry not much help.
– TeddyTedTed
2 days ago