Interesting question! I'm not aware of any other way of achieving what you want, but using PyQGIS.
Read the code below. It has some texts in it: 'lines'
, 'length'
, 'startX'
, 'startY'
, 'endX'
, 'endY'
. You can adjust those names in the script for it to work on your data. The first one is you layer name, whereas the rest corresponds to field names. I assume your line layer has those fields (after all, you want values to be written there).
Once you have adjusted your layer name and the names of the fields you want to be automatically updated, copy and paste the script into the QGIS Python console.
If everything goes well, you should be able to see that field values are automatically updated in two scenarios: 1) When new features are added, and 2) When geometries are modified.
# Initialize required variables
myLayer = QgsMapLayerRegistry.instance().mapLayersByName( 'lines' )[0]
lengthField = myLayer.fieldNameIndex( 'length' )
startXField = myLayer.fieldNameIndex( 'startX' )
startYField = myLayer.fieldNameIndex( 'startY' )
endXField = myLayer.fieldNameIndex( 'endX' )
endYField = myLayer.fieldNameIndex( 'endY' )
# Slot, updates field values
def updateFeatureAttrs( fId, geom=None ):
f = myLayer.getFeatures( QgsFeatureRequest( fId ) ).next()
if not geom:
geom = f.geometry()
myLayer.changeAttributeValue( fId, lengthField, geom.length() )
myLayer.changeAttributeValue( fId, startXField, geom.vertexAt( 0 )[0] )
myLayer.changeAttributeValue( fId, startYField, geom.vertexAt( 0 )[1] )
myLayer.changeAttributeValue( fId, endXField, geom.asPolyline()[-1][0] )
myLayer.changeAttributeValue( fId, endYField, geom.asPolyline()[-1][1] )
# Update feature attributes when new features are added or geometry changes
myLayer.featureAdded.connect( updateFeatureAttrs )
myLayer.geometryChanged.connect( updateFeatureAttrs )
This is how it works:
![Automatic updates of fields in QGIS](https://i.stack.imgur.com/z1vTc.gif)
If you have any problem while running the script, add a comment below this answer.
It might be handy for you to have this functionality already available when you open your QGIS project. If that's the case, tell me, I could post instructions to do that.
EDIT:
For this functionality to be available every time you open your QGIS project (i.e., a .qgs
file containing, among others, your line layer) you need to follow these steps:
Go to QGIS->Project->Project Properties->Macros
, check the Python macros
option, and replace the whole code with this one (adjust the values indicating your layer and field names):
from qgis.core import QgsMapLayerRegistry, QgsFeatureRequest
def openProject():
# Initialize required variables
myLayer = QgsMapLayerRegistry.instance().mapLayersByName( 'lines' )[0]
# Update feature attributes when new features are added or geometry changes
myLayer.featureAdded.connect( updateFeatureAttrs )
myLayer.geometryChanged.connect( updateFeatureAttrs )
# Slot, updates field values
def updateFeatureAttrs( fId, geom=None ):
myLayer = QgsMapLayerRegistry.instance().mapLayersByName( 'lines' )[0]
lengthField = myLayer.fieldNameIndex( 'length' )
startXField = myLayer.fieldNameIndex( 'startX' )
startYField = myLayer.fieldNameIndex( 'startY' )
endXField = myLayer.fieldNameIndex( 'endX' )
endYField = myLayer.fieldNameIndex( 'endY' )
f = myLayer.getFeatures( QgsFeatureRequest( fId ) ).next()
if not geom:
geom = f.geometry()
myLayer.changeAttributeValue( fId, lengthField, geom.length() )
myLayer.changeAttributeValue( fId, startXField, geom.vertexAt( 0 )[0] )
myLayer.changeAttributeValue( fId, startYField, geom.vertexAt( 0 )[1] )
myLayer.changeAttributeValue( fId, endXField, geom.asPolyline()[-1][0] )
myLayer.changeAttributeValue( fId, endYField, geom.asPolyline()[-1][1] )
def saveProject():
pass
def closeProject():
pass
Make sure you enable macros on your project, this way: Settings->Options->General->Enable macros: Always
.
Save your QGIS project.
Now, every time you open the .qgs
file you've just saved, the attributes of your line layer will be automatically updated when you add a new feature or modify a geometry (i.e., no need to copy anything into the QGIS Python Console anymore).
2nd EDIT:
I've just published a plugin called AutoFields to help people to solve this kind of problems. I even made a video showing how to solve your ploblem, you can watch it at:
https://vimeo.com/germap/autofields-geometric-properties
AutoFields documentation: http://geotux.tuxfamily.org/index.php/en/geo-blogs/item/333-autofields-plugin-for-qgis
I see this question had a little bit of activity recently, so I will post my eventual solution. It essentially follows the strategy of my last comment.
There are two types of nodes: those which define junctions between road segments (ie. a graph node); and intermediate nodes which define the shape of the road segment. These are dropped for routing, but I keep them for my closest-location match.
I built a kd tree index of all of these nodes - junctions and intermediates. For the latter I store information about which segment they lie on, and the fraction of the distance along the segment.
Yes this index has to be stored to disk. By using fixed length records, it is possible to search the tree using a binary-chop approach - i.e. without any pointers or internal indexing. The implementation was in C#/.NET, and a cache was also used during reading. Note that the top few levels don't take up much space, and are read the most - so they benefit a lot from the cache. Similarly, it is often the case that successive search locations are in the same part of the tree, so branches 'in that direction' will tend to be cached.
Best Answer
I made a spatial join with [min,max] between the edges layer and the nodes layer so as to have the origin node as the minimum and the end node as the maximum