I'm working on a project where I need to visualize a constantly updating line of bearing from a point, based on degree of rotation around that point. My plan is to design a line feature with an attribute table that links to a CSV which is being fed this rotation data every few seconds. I'll run a python script that creates a new line feature each time the CSV is updated, visualizing a max of 10 lines, removing the oldest line per update.
My big hurdle right now is figuring out how to make the generated lines rotate around the relevant point through maybe some sort of snapping feature.
Is this possible?
I feel like there's a much easier way to do this than the method I'm proposing, but I can't think of what it might be. This is ultimately going to be ported to a separate window to a UI outside of QGIS, so I'm worried I'm going about this the wrong way.
I've come across some similar questions that touch on what I'm getting at, but nothing that's truly addressed this specific need. I've also tried working with the Distance and Azimuth plugin, but it works in a way that wouldn't cooperate with my aforementioned method. I need the lines to snap to an independent point feature with separate attributes. Or maybe I'm wrong and this can be done.
Answer
Creating spokes radiating around a central point is certainly doable in Qgis 2.1 and Python.
Creating radial lines from a point
Here's some python code which will shows how to create radial lines, given
- the name of a point layer,
- the id of the point
- the name of a line layer to receive the 'spokes'.
It creates 360 lines, a degree apart, centered on the point, with a line length of 0.01 (degrees).
import math
from qgis.core import QgsFeature
'''
Names of layers
'''
pointLayerName = "fan points"
lineLayerName = "line points"
'''
find layer objects by name
'''
layers = QgsMapLayerRegistry.instance().mapLayers()
for name, layer in layers.iteritems():
print "%s %s" % (layer.name(),layer)
if layer.name()==lineLayerName:
lineLayer = layer
if layer.name()==pointLayerName:
pointLayer = layer
assert pointLayer!=None
assert lineLayer!=None
'''
find the point (by id) we want
'''
expr = QgsExpression( "id=1" )
it = pointLayer.getFeatures( QgsFeatureRequest( expr ) )
for f in it:
geompoint = f.geometry()
pivot = geompoint.asPoint()
break # only want first matching feature
'''
Makes spokes.
'''
lineLayer.beginEditCommand("Add spokes")
provider = lineLayer.dataProvider()
linefeatures=[]
for ang in range(0,360,1):
geomspoke = QgsGeometry.fromPolyline([QgsPoint(0.0,0.0),QgsPoint(0.0,.01)])
geomspoke.rotate(float(ang),QgsPoint(0.0,0.0))
geomspoke.translate(pivot.x(),pivot.y())
print geomspoke.exportToWkt(6)
spokefeat = QgsFeature()
spokefeat.setGeometry(geomspoke)
linefeatures.append(spokefeat)
provider.addFeatures(linefeatures)
lineLayer.commitChanges()
lineLayer.endEditCommand()
print "Done!"
Note that I've used a couple of methods which may require a fairly recent version of QGIS (they're there in 2.1 but may not be in earlier versions like 1.8)
Your use case
You should be able to modify this to read in angles from your data source to add these spokes as data comes in.
By the sounds of it you only want to keep a fixed number of lines, so a Python FIFO (first-in-first-out buffer, such as a deque) could be used to store the QgsFeatures for the spokes, and recreate the line layer for each 'frame' from the contents of this buffer. Each time you receive some data, drop the oldest and add in the newest.
Animating
Animating is more tricky. Normally I'd suggest TimeManager plugin, but that might not be suitable considering that you're going to be implementing this as a stand-alone application.
However, TimeManager does do something you need - updating and re-rendering a QGSMapCanvas, showing features according to a specific query (which features have the current time value for this frame?).
So you'd probably find the source a good starting point to see how to do this, or at least to try a proof-of-concept before coding this up standalone. (Tip: you can use unix timestamps as a time marker for each frame, and 1970-01-01 00:00:00 to correspond to a value of 0)
Hope this helps!