1
# -*- coding: utf-8 -*-99
2
"""@package OsmFeatureDW
3
This module is descendant of "OSM Feature" dockable widget and makes user able
4
to view and edit information on selected OSM feature.
6
OsmFeatureDW module shows details of selected feature - its basic info, tags and relations.
7
It provides methods for editing features' tags so that user can edit them directly on the widget.
9
There are also some identify and edit buttons on "OsmFeatureDW" - this modul implements all the methods that are called
10
after clicking on these buttons. Such methods creates (and set) map tool that coresponds to specified button.
14
from PyQt4.QtCore import *
15
from PyQt4.QtGui import *
16
from qgis.core import *
17
from qgis.gui import *
19
from ui_OsmFeatureDW import Ui_OsmFeatureDW
20
from OsmAddRelationDlg import OsmAddRelationDlg
23
# include all available osm map tools
24
from map_tools.OsmCreatePointMT import OsmCreatePointMT
25
from map_tools.OsmCreateLineMT import OsmCreateLineMT
26
from map_tools.OsmCreatePolygonMT import OsmCreatePolygonMT
27
from map_tools.OsmMoveMT import OsmMoveMT
28
from map_tools.OsmIdentifyMT import OsmIdentifyMT
32
class OsmFeatureDW(QDockWidget, Ui_OsmFeatureDW, object):
33
"""This class shows details of selected feature - its basic info, tags and relations.
35
It provides methods for editing features' tags so that user can edit them directly on the widget.
37
There are also some identify and edit buttons on "OsmFeatureDW" - this modul implements all the methods that are called
38
after clicking them. Such methods creates (and set) map tool that coresponds to specified button.
42
def __init__(self, plugin):
43
"""The constructor."""
45
QDockWidget.__init__(self, None)
47
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
53
# set icons for tool buttons (identify,move,createPoint,createLine,createPolygon)
54
self.identifyButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_identify.png"))
55
self.moveButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_move.png"))
56
self.createPointButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_createPoint.png"))
57
self.createLineButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_createLine.png"))
58
self.createPolygonButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_createPolygon.png"))
59
self.createRelationButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_createRelation.png"))
60
self.removeButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_removeFeat.png"))
61
self.deleteTagsButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_removeTag.png"))
62
self.undoButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_undo.png"))
63
self.redoButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_redo.png"))
64
self.addRelationButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_addRelation.png"))
65
self.removeRelationButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_removeRelation.png"))
66
self.editRelationButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_editRelation.png"))
67
self.urDetailsButton.setIcon(QIcon(":/plugins/osm_plugin/images/osm_urDetails.png"))
69
# initializing group of edit buttons
70
self.toolButtons=QButtonGroup(self)
71
self.dummyButton.setVisible(False)
72
self.toolButtons.addButton(self.dummyButton)
73
self.toolButtons.addButton(self.identifyButton)
74
self.toolButtons.addButton(self.moveButton)
75
self.toolButtons.addButton(self.createPointButton)
76
self.toolButtons.addButton(self.createLineButton)
77
self.toolButtons.addButton(self.createPolygonButton)
78
self.toolButtons.setExclusive(True)
80
# initializing table of feature tags
81
self.tagTable.setColumnCount(2)
82
self.tagTable.setHorizontalHeaderItem(0,QTableWidgetItem("Key"))
83
self.tagTable.setHorizontalHeaderItem(1,QTableWidgetItem("Value"))
84
self.tagTable.setSelectionMode(QAbstractItemView.ExtendedSelection)
85
self.tagTable.setSelectionBehavior(QAbstractItemView.SelectRows)
86
self.newTagLabel="<new tag here>"
87
self.relTagsTree.setSelectionMode(QAbstractItemView.NoSelection)
89
# initializing rubberbands/vertexmarkers; getting qgis settings of line width and color for rubberbands
91
qgsLineWidth=2 # use fixed width
92
qgsLineRed=settings.value( "/qgis/digitizing/line_color_red", QVariant(255) ).toInt()
93
qgsLineGreen=settings.value( "/qgis/digitizing/line_color_green", QVariant(0) ).toInt()
94
qgsLineBlue=settings.value( "/qgis/digitizing/line_color_blue", QVariant(0) ).toInt()
96
self.rubBandPol=QgsRubberBand(plugin.canvas,True)
97
self.rubBandPol.setColor(QColor(qgsLineRed[0],qgsLineGreen[0],qgsLineBlue[0]))
98
self.rubBandPol.setWidth(qgsLineWidth)
100
self.rubBand=QgsRubberBand(plugin.canvas,False)
101
self.rubBand.setColor(QColor(qgsLineRed[0],qgsLineGreen[0],qgsLineBlue[0]))
102
self.rubBand.setWidth(qgsLineWidth)
104
self.verMarker=QgsVertexMarker(plugin.canvas)
105
self.verMarker.setIconType(2)
106
self.verMarker.setIconSize(13)
107
self.verMarker.setColor(QColor(qgsLineRed[0],qgsLineGreen[0],qgsLineBlue[0]))
108
self.verMarker.setPenWidth(qgsLineWidth)
111
self.relRubBandPol=QgsRubberBand(plugin.canvas,True)
112
self.relRubBandPol.setColor(QColor(qgsLineRed[0],50,50))
113
self.relRubBandPol.setWidth(qgsLineWidth+4)
115
self.relRubBand=QgsRubberBand(plugin.canvas,False)
116
self.relRubBand.setColor(QColor(qgsLineRed[0],50,50))
117
self.relRubBand.setWidth(qgsLineWidth+4)
119
self.relVerMarker=QgsVertexMarker(plugin.canvas)
120
self.relVerMarker.setIconType(2)
121
self.relVerMarker.setIconSize(13)
122
self.relVerMarker.setColor(QColor(qgsLineRed[0],50,50))
123
self.relVerMarker.setPenWidth(qgsLineWidth)
125
# initializing inner variables
126
self.activeEditButton=self.dummyButton
127
self.__tagsLoaded=False
128
self.__relTagsLoaded=False
130
self.featureType=None
133
self.featRelMembers=[]
135
# clear all widget items
138
self.__connectWidgetSignals()
140
self.removeButton.setEnabled(False)
141
self.createRelationButton.setCheckable(False)
143
# set current tab to "Properties"
144
self.propRelBox.setCurrentIndex(0)
145
self.plugin.canvas.setFocus(Qt.OtherFocusReason)
147
# init coordinate transform
148
self.projectionChanged()
150
renderer=self.plugin.canvas.mapRenderer()
151
self.connect(renderer, SIGNAL("hasCrsTransformEnabled(bool)"), self.projectionChanged)
152
self.connect(renderer, SIGNAL("destinationSrsChanged()"), self.projectionChanged)
155
def setContentEnabled(self,flag):
157
self.featInfoBox.setEnabled(flag)
158
self.propRelBox.setEnabled(flag)
159
self.identifyButton.setEnabled(flag)
160
self.moveButton.setEnabled(flag)
161
self.createPointButton.setEnabled(flag)
162
self.createLineButton.setEnabled(flag)
163
self.createPolygonButton.setEnabled(flag)
164
self.createRelationButton.setEnabled(flag)
167
if self.plugin.undoredo.undoCounter>0:
168
self.undoButton.setEnabled(True)
169
if self.plugin.undoredo.redoCounter>0:
170
self.redoButton.setEnabled(True)
172
self.undoButton.setEnabled(False)
173
self.redoButton.setEnabled(False)
175
self.urDetailsButton.setEnabled(flag)
178
def projectionChanged(self):
179
"""Function is connected to signals from QgsMapRenderer.
180
It updates coordinate transforms.
183
renderer = self.plugin.canvas.mapRenderer()
184
if renderer.hasCrsTransformEnabled():
185
self.coordXform = QgsCoordinateTransform(renderer.destinationSrs(), QgsCoordinateReferenceSystem(4326))
187
self.coordXform = None
190
def canvasToOsmCoords(self, point):
191
"""Performs conversion from canvas to map coordinates.
193
@param point canvas coordinates to convert
195
point = self.plugin.canvas.getCoordinateTransform().toMapCoordinates( point )
197
# optional conversion from map to layer coordinates
198
if self.coordXform is not None:
199
point = self.coordXform.transform( point )
204
def __connectWidgetSignals(self):
205
"""Function connects all necessary signals to appropriate slots.
208
# signals emitted on clicking with tag and member tables
209
QObject.connect(self.identifyButton, SIGNAL("clicked()"), self.__startIdentifyingFeature)
210
QObject.connect(self.moveButton, SIGNAL("clicked()"), self.__startMovingFeature)
211
QObject.connect(self.createPointButton, SIGNAL("clicked()"), self.__startPointCreation)
212
QObject.connect(self.createLineButton, SIGNAL("clicked()"), self.__startLineCreation)
213
QObject.connect(self.createPolygonButton, SIGNAL("clicked()"), self.__startPolygonCreation)
214
QObject.connect(self.createRelationButton, SIGNAL("clicked()"), self.__createRelation)
215
QObject.connect(self.removeButton, SIGNAL("clicked()"), self.removeFeature)
216
QObject.connect(self.relListWidget, SIGNAL("currentRowChanged(int)"), self.loadRelationStuff)
217
QObject.connect(self.relMembersList, SIGNAL("currentRowChanged(int)"), self.__showRelMemberOnMap)
218
QObject.connect(self.addRelationButton, SIGNAL("clicked()"), self.createRelationWithMember)
219
QObject.connect(self.editRelationButton, SIGNAL("clicked()"), self.editSelectedRelation)
220
QObject.connect(self.removeRelationButton, SIGNAL("clicked()"), self.removeSelectedRelation)
221
QObject.connect(self.deleteTagsButton, SIGNAL("clicked()"), self.removeSelectedTags)
222
QObject.connect(self.tagTable, SIGNAL("cellChanged(int,int)"), self.__onTagsCellChanged)
223
QObject.connect(self.tagTable, SIGNAL("currentCellChanged(int,int,int,int)"), self.__onCurrentCellChanged)
224
QObject.connect(self.tagTable, SIGNAL("itemDoubleClicked(QTableWidgetItem*)"), self.__onTagsItemDoubleClicked)
225
QObject.connect(self.undoButton, SIGNAL("clicked()"), self.__undo)
226
QObject.connect(self.redoButton, SIGNAL("clicked()"), self.__redo)
227
QObject.connect(self.urDetailsButton, SIGNAL("clicked()"), self.__urDetailsChecked)
230
def databaseChanged(self,dbKey):
231
"""This function is called when current OSM database of plugin changes.
232
The OsmFeatureDW performs necessary actions and tells current map tool about the change.
234
@param dbKey key (name) of new current database
237
if self.__dlgAddRel and dbKey:
239
self.__dlgAddRel.close()
240
self.__dlgAddRel=None
241
self.setContentEnabled(True)
242
self.plugin.undoredo.setContentEnabled(True)
244
QMessageBox.information(self, self.tr("OSM Plugin")
245
,self.tr("The 'Create OSM Relation' dialog was closed automatically because current OSM database was changed."))
247
# clear the whole OSM Feature dockwidget as well as all related rubberbands and vertex markers
250
# if some mapTool is currently set tell it about database changing
252
self.mapTool.databaseChanged(dbKey)
253
self.activeEditButton=self.dummyButton
255
# and if new database is None, disable the whole dockwidget
257
self.setContentEnabled(False)
259
self.plugin.canvas.unsetMapTool(self.mapTool)
261
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
264
self.setContentEnabled(True)
268
"""Function clears all widget items.
269
It resets rubberbands, vertexmarkers, re-initializes OsmFeatureDW inner structures.
272
# clear common feature infos
273
self.typeIdLabel.setText("")
274
self.userLabel.setText("")
275
self.createdLabel.setText("")
277
# clear table with information about feature's tags
278
self.tagTable.clear()
279
self.tagTable.setEnabled(False)
280
self.tagTable.setRowCount(0)
281
self.tagTable.setColumnCount(0)
283
# clear table with info about feature's relations
284
self.relListWidget.clear()
285
self.relTagsTree.clear()
286
self.relMembersList.clear()
287
self.relTagsTree.setColumnCount(0)
289
self.relListWidget.setEnabled(False)
290
self.relTagsTree.setEnabled(False)
291
self.relMembersList.setEnabled(False)
293
# disable widget buttons
294
self.deleteTagsButton.setEnabled(False)
295
self.editRelationButton.setEnabled(False)
296
self.removeRelationButton.setEnabled(False)
297
self.addRelationButton.setEnabled(False)
298
self.removeButton.setEnabled(False)
300
# remove previous rubber bands
301
self.rubBand.reset(False)
302
self.rubBandPol.reset(True)
303
self.verMarker.setCenter(QgsPoint(-1000,-1000))
304
self.verMarker.hide()
305
self.__removeMemberMarkers()
308
self.relRubBand.reset(False)
309
self.relRubBandPol.reset(True)
310
self.relVerMarker.setCenter(QgsPoint(-1000,-1000))
311
self.relVerMarker.hide()
313
# clear member variables
314
self.__tagsLoaded=False
315
self.__relTagsLoaded=False
317
self.featureType=None
320
self.featRelMembers=[]
323
def __createRelation(self):
324
"""Function calls relation creating process.
325
Creation is started by displaying appropriate dialog.
330
self.plugin.iface.mainWindow().statusBar().showMessage("")
332
self.plugin.canvas.unsetMapTool(self.mapTool)
336
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
337
self.activeEditButton=self.dummyButton
339
self.setContentEnabled(False)
340
self.plugin.undoredo.setContentEnabled(False)
341
self.plugin.toolBar.setEnabled(False)
343
# OsmAddRelationDlg parameters: plugin, newRelationFirstMember, relationToEdit
344
self.__dlgAddRel=OsmAddRelationDlg(self.plugin, None, None)
345
self.__dlgAddRel.setWindowModality(Qt.WindowModal)
346
self.__dlgAddRel.exec_()
347
self.__dlgAddRel=None
349
self.setContentEnabled(True)
350
self.plugin.undoredo.setContentEnabled(True)
351
self.plugin.toolBar.setEnabled(True)
354
def createRelationWithMember(self):
355
"""Function calls relation creating process. Creation is started by displaying appropriate dialog.
356
Function pre-fills dialog with information on currently loaded feature.
359
self.__dlgAddRel=OsmAddRelationDlg(self.plugin, QString(self.featureType+" %1").arg(self.feature.id()), None)
363
self.plugin.iface.mainWindow().statusBar().showMessage("")
365
self.plugin.canvas.unsetMapTool(self.mapTool)
369
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
370
self.activeEditButton=self.dummyButton
372
self.setContentEnabled(False)
373
self.plugin.undoredo.setContentEnabled(False)
374
self.plugin.toolBar.setEnabled(False)
376
self.__dlgAddRel.setWindowModality(Qt.WindowModal)
377
self.__dlgAddRel.exec_()
378
self.__dlgAddRel=None
380
self.setContentEnabled(True)
381
self.plugin.undoredo.setContentEnabled(True)
382
self.plugin.toolBar.setEnabled(True)
385
def editSelectedRelation(self):
386
"""Function calls editing of a relation. Editing is started by displaying appropriate dialog.
387
Relation identifier is not passed to this function. Function has to find it out from current row
388
of appropriate list widget.
391
# show modal dialog "Edit relation"
393
QMessageBox.information(self, self.tr("OSM Feature Dock Widget"), self.tr("Choose OSM feature first."))
396
item=self.relListWidget.item(self.relListWidget.currentRow())
398
QMessageBox.information(self, self.tr("OSM Feature Dock Widget"), self.tr("Choose relation for editing first."))
401
relId=self.featRels[self.relListWidget.currentRow()]
403
self.setContentEnabled(False)
404
self.plugin.undoredo.setContentEnabled(False)
405
self.plugin.toolBar.setEnabled(False)
407
self.__dlgAddRel=OsmAddRelationDlg(self.plugin, None, relId)
408
self.__dlgAddRel.setWindowModality(Qt.WindowModal)
409
self.__dlgAddRel.exec_()
410
self.__dlgAddRel=None
412
self.setContentEnabled(True)
413
self.plugin.undoredo.setContentEnabled(True)
414
self.plugin.toolBar.setEnabled(True)
417
def __onTagsCellChanged(self,row,column):
418
"""Function is called after cellChanged(int,int) signal is emitted on table of all features' relations.
419
It means that user is changed key or value of some existing tag.
421
@param row index of row in table of tags
422
@param column index of column in table of tags
425
if not self.__tagsLoaded:
426
# this signal was emitted during table initialization,
427
# but we are interested in user actions only
430
if row<self.tagTable.rowCount()-1:
432
# changing value of tag that already exists
433
key=self.tagTable.item(row,0).text()
434
value=self.tagTable.item(row,1).text()
436
# store tag's change into database
437
self.plugin.undoredo.startAction("Change tag value.")
438
self.plugin.dbm.changeTagValue(self.feature.id(),self.featureType,key.toAscii().data(),value.toUtf8())
440
key = self.tagTable.item(row,0).text()
441
if key=="" or key==self.newTagLabel:
444
# adding new tag and setting its key
447
# only ascii keys are allowed
448
nkfd_form=unicodedata.normalize('NFKD', unicode(key.toUtf8(),'utf-8'))
449
key_only_ascii=nkfd_form.encode('ASCII', 'ignore')
451
# store it into database
452
isAlreadyDef=self.plugin.dbm.isTagDefined(self.feature.id(),self.featureType,key_only_ascii)
454
# such a key already exists for this relation
455
self.tagTable.setItem(row,0,QTableWidgetItem(self.newTagLabel))
456
QMessageBox.information(self, self.tr("OSM Feature Dock Widget")
457
,self.tr(QString("Property '").append(key_only_ascii).append("' cannot be added twice.")))
460
# well, insert new tag into database
461
self.plugin.undoredo.startAction("Insert new tag.")
462
self.plugin.dbm.insertTag(self.feature.id(),self.featureType,key_only_ascii,None)
464
self.__tagsLoaded=False
466
self.tagTable.item(row,0).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
467
self.tagTable.item(row,1).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)
468
self.tagTable.item(row,0).setText(QString(key_only_ascii))
471
self.tagTable.setRowCount(row+2)
472
self.tagTable.setItem(newLastRow,0,QTableWidgetItem(self.newTagLabel))
473
self.tagTable.setItem(newLastRow,1,QTableWidgetItem(""))
474
self.tagTable.item(newLastRow,0).setFlags(Qt.ItemIsEnabled | Qt.ItemIsEditable)
475
self.tagTable.item(newLastRow,1).setFlags(Qt.ItemIsEnabled)
477
self.__tagsLoaded=True
479
# updating feature status (from Normal to Updated)
480
if self.featureType=='Point':
481
self.plugin.dbm.changePointStatus(self.feature.id(),'N','U')
483
elif self.featureType=='Line':
484
self.plugin.dbm.changeLineStatus(self.feature.id(),'N','U')
486
elif self.featureType=='Polygon':
487
self.plugin.dbm.changePolygonStatus(self.feature.id(),'N','U')
489
elif self.featureType=='Relation':
490
self.plugin.dbm.changeRelationStatus(self.feature.id(),'N','U')
492
self.plugin.dbm.commit()
494
affected=[(self.feature.id(),self.featureType)]
495
self.plugin.undoredo.stopAction(affected)
496
self.plugin.dbm.recacheAffectedNow(affected)
498
# refresh map canvas so that changes take effect
500
self.plugin.canvas.refresh()
503
def __onTagsItemDoubleClicked(self,item):
504
"""Function is called after itemDoubleClicked(...) signal is emitted on table of feature tags.
506
It shows combobox with possible values for given item of table.
508
@param item item of table of feature tags
513
if item.row()<self.tagTable.rowCount()-1:
516
tagValues=OsmTags.suitableTagKeys(self.featureType)
520
valCombo.setEditable(True)
521
valCombo.addItems(tagValues)
522
currentComboText=self.tagTable.item(item.row(),0).text()
523
ix=valCombo.findText(currentComboText)
524
valCombo.setCurrentIndex(ix)
526
valCombo.setEditText(currentComboText)
528
self.tagTable.setCellWidget(item.row(),0,valCombo)
529
QObject.connect(valCombo, SIGNAL("currentIndexChanged(const QString &)"), self.__onTagKeySelectionChanged)
532
key=str(self.tagTable.item(item.row(),0).text())
533
tagValues=OsmTags.suitableTagValues(self.featureType,key)
538
valCombo.setEditable(True)
539
valCombo.addItems(tagValues)
540
currentComboText=self.tagTable.item(item.row(),1).text()
541
ix=valCombo.findText(currentComboText)
542
valCombo.setCurrentIndex(ix)
544
valCombo.setEditText(currentComboText)
546
self.tagTable.setCellWidget(item.row(),1,valCombo)
547
QObject.connect(valCombo, SIGNAL("currentIndexChanged(const QString &)"), self.__onTagValueSelectionChanged)
550
def __onCurrentCellChanged(self,curRow,curCol,prevRow,prevCol):
551
"""Function is called after currentCellChanged(...) signal is emitted on table of feature tags.
553
@param curRow current row index to tags table
554
@param curCol current column index to tags table
555
@param prevRow previous row index to tags table
556
@param prevCol previous column index to tags table
559
cellWidget=self.tagTable.cellWidget(prevRow,prevCol)
563
self.tagTable.removeCellWidget(prevRow,prevCol)
566
def __onTagKeySelectionChanged(self,key):
567
"""Function is called after currentIndexChanged(...) signal is emitted on combobox in 1st column of tags table.
569
@param key key selected in combobox
572
row=self.tagTable.currentRow()
573
col=self.tagTable.currentColumn()
575
self.tagTable.item(row,col).setText(key)
576
self.tagTable.removeCellWidget(row,col)
579
def __onTagValueSelectionChanged(self,value):
580
"""Function is called after currentIndexChanged(...) signal is emitted on combobox in 2nd column of tags table.
582
@param value value selected in combobox
585
row=self.tagTable.currentRow()
586
col=self.tagTable.currentColumn()
588
self.tagTable.item(row,col).setText(value)
589
self.tagTable.removeCellWidget(row,col)
593
def __startIdentifyingFeature(self):
594
"""Function prepares feature identification.
595
The appropriate map tool (OsmIdentifyMT) is set to map canvas.
598
if self.activeEditButton==self.identifyButton:
601
self.plugin.canvas.unsetMapTool(self.plugin.canvas.mapTool())
606
self.plugin.iface.mainWindow().statusBar().showMessage("")
608
self.mapTool=OsmIdentifyMT(self.plugin.canvas, self, self.plugin.dbm)
609
self.plugin.canvas.setMapTool(self.mapTool)
610
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
611
self.activeEditButton=self.identifyButton
612
self.plugin.canvas.setFocus(Qt.OtherFocusReason)
615
def __startMovingFeature(self):
616
"""Function prepares feature moving.
617
The appropriate map tool (OsmMoveMT) is set to map canvas.
620
if self.activeEditButton==self.moveButton:
625
self.plugin.iface.mainWindow().statusBar().showMessage("Snapping ON. Hold Ctrl to disable it.")
627
self.mapTool=OsmMoveMT(self.plugin)
628
self.plugin.canvas.setMapTool(self.mapTool)
629
self.plugin.canvas.setCursor(QCursor(Qt.CrossCursor))
630
self.activeEditButton=self.moveButton
631
self.plugin.canvas.setFocus(Qt.OtherFocusReason)
634
def __startPointCreation(self):
635
"""Function prepares point creating operation.
636
The appropriate map tool (OsmCreatePointMT) is set to map canvas.
639
if self.activeEditButton==self.createPointButton:
642
self.plugin.iface.mainWindow().statusBar().showMessage("Snapping ON. Hold Ctrl to disable it.")
644
self.mapTool=OsmCreatePointMT(self.plugin)
645
self.plugin.canvas.setMapTool(self.mapTool)
646
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
647
self.activeEditButton=self.createPointButton
648
self.plugin.canvas.setFocus(Qt.OtherFocusReason)
651
def __startLineCreation(self):
652
"""Function prepares line creating operation.
653
The appropriate map tool (OsmCreateLineMT) is set to map canvas.
656
if self.activeEditButton==self.createLineButton:
659
self.plugin.iface.mainWindow().statusBar().showMessage("Snapping ON. Hold Ctrl to disable it.")
661
self.mapTool=OsmCreateLineMT(self.plugin)
662
self.plugin.canvas.setMapTool(self.mapTool)
663
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
664
self.activeEditButton=self.createLineButton
665
self.plugin.canvas.setFocus(Qt.OtherFocusReason)
668
def __startPolygonCreation(self):
669
"""Function prepares polygon creating operation.
670
The appropriate map tool (OsmCreatePolygonMT) is set to map canvas.
673
if self.activeEditButton==self.createPolygonButton:
676
self.plugin.iface.mainWindow().statusBar().showMessage("Snapping ON. Hold Ctrl to disable it.")
678
self.mapTool=OsmCreatePolygonMT(self.plugin)
679
self.plugin.canvas.setMapTool(self.mapTool)
680
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
681
self.activeEditButton=self.createPolygonButton
682
self.plugin.canvas.setFocus(Qt.OtherFocusReason)
685
def removeFeature(self):
686
"""Function completely removes feature that is currently loaded on "OSM Feature" widget.
689
self.removeButton.setDown(False)
690
self.removeButton.setChecked(False)
691
self.plugin.iface.mainWindow().statusBar().showMessage("")
693
# remove object that was identified by "identify tool"
694
featId=self.feature.id()
695
featType=self.featureType
698
if featType=='Point':
699
self.plugin.undoredo.startAction("Remove point.")
700
affected=self.plugin.dbm.removePoint(featId)
702
elif featType=='Line':
703
self.plugin.undoredo.startAction("Remove line.")
704
affected=self.plugin.dbm.removeLine(featId,True) # todo: False when Ctrl pressed
706
elif featType=='Polygon':
707
self.plugin.undoredo.startAction("Remove polygon.")
708
affected=self.plugin.dbm.removePolygon(featId,True) # todo: False when Ctrl pressed
710
elif featType=='Relation':
711
self.plugin.undoredo.startAction("Remove relation.")
712
self.plugin.dbm.removeRelation(featId)
714
return # strange situation
716
self.plugin.undoredo.stopAction(affected)
717
self.plugin.dbm.recacheAffectedNow(affected)
719
# refresh map canvas so that changes take effect
720
self.plugin.canvas.refresh()
725
self.plugin.iface.mainWindow().statusBar().showMessage("")
727
self.plugin.canvas.unsetMapTool(self.mapTool)
729
self.mapTool=OsmIdentifyMT(self.plugin.canvas, self, self.plugin.dbm)
730
self.plugin.canvas.setMapTool(self.mapTool)
731
self.plugin.canvas.setCursor(QCursor(Qt.ArrowCursor))
732
self.plugin.canvas.setFocus(Qt.OtherFocusReason)
733
self.activeEditButton=self.identifyButton
734
self.activeEditButton.setChecked(True)
737
def removeSelectedTags(self):
738
"""Function completely removes all tags that are currently selected in the appropriate
739
list of the "OSM Feature" widget. More than one tag can be selected using Ctrl and clicking.
742
# remove selected tags (rows)
743
selectedItems=self.tagTable.selectedItems()
744
selectedRowsIndexes=[]
745
lastRowIndex=self.tagTable.rowCount()-1
746
self.tagTable.setCurrentCell(lastRowIndex,0)
748
for i in selectedItems:
749
if i.column()==0 and not i.row()==lastRowIndex:
750
selectedRowsIndexes.append(i.row())
752
self.plugin.undoredo.startAction("Remove tags.")
754
selectedRowsIndexes.sort()
755
selectedRowsIndexes.reverse()
757
for ix in selectedRowsIndexes:
759
key=self.tagTable.item(ix,0).text()
760
# updating feature status (from Normal to Updated)
761
if self.featureType=='Point':
762
self.plugin.dbm.changePointStatus(self.feature.id(),'N','U')
764
elif self.featureType=='Line':
765
self.plugin.dbm.changeLineStatus(self.feature.id(),'N','U')
767
elif self.featureType=='Polygon':
768
self.plugin.dbm.changePolygonStatus(self.feature.id(),'N','U')
770
elif self.featureType=='Relation':
771
self.plugin.dbm.changeRelationStatus(self.feature.id(),'N','U')
773
# perform tag removing
774
self.plugin.dbm.removeTag(self.feature.id(),self.featureType,key.toAscii().data())
776
self.tagTable.removeRow(ix)
778
# make this action permanent
779
self.plugin.dbm.commit()
780
affected=[(self.feature.id(),self.featureType)]
781
self.plugin.undoredo.stopAction(affected)
782
self.plugin.dbm.recacheAffectedNow(affected)
784
# refresh map canvas so that changes take effect
785
self.plugin.canvas.refresh()
788
def removeSelectedRelation(self):
789
"""Function completely removes all relations that are selected in the appropriate list of the "OSM Feature" widget.
790
More than one relation can be selected using Ctrl and clicking.
793
# find out id of relation which is selected
794
item=self.relListWidget.item(self.relListWidget.currentRow())
795
if not item or not self.plugin.dbm.currentKey:
798
relId=self.featRels[self.relListWidget.currentRow()]
800
self.plugin.undoredo.startAction("Remove relation.")
801
self.plugin.dbm.removeRelation(relId)
802
self.plugin.dbm.commit()
803
self.plugin.undoredo.stopAction()
805
# reload list of feature's relations
806
self.__loadFeatureRelations(self.feature.id(),self.featureType)
809
def __showRelMemberOnMap(self,ixRow):
810
"""Functions marks relation member on the map.
812
Marking is realized with simple rubberBand (in case of line/polygon) or vertexMarker (for point).
813
Relation member is given by its index to the list of all members of currently loaded relation.
815
@param ixRow index to the list of all relations on mentioned widget
818
# move rubberband to selected feature
819
self.relRubBand.reset(False)
820
self.relRubBandPol.reset(True)
821
self.relVerMarker.hide()
823
if ixRow==-1 or not self.plugin.dbm.currentKey:
825
mem=self.featRelMembers[ixRow]
826
self.showFeatureOnMap(mem[0],mem[1]) # id & type
829
def loadRelationStuff(self,ixRow):
830
"""Functions loads information on specified relation into "OSM Feature" widget.
832
Relation is given by its index to the list of all relations on mentioned widget.
833
Relation "stuff" means its basic info, tags and members.
835
@param ixRow index to the list of all relations on mentioned widget
838
if ixRow==-1 or not self.plugin.dbm.currentKey:
840
relId=self.featRels[ixRow]
842
# show all tags connected to selected relation
843
self.__loadRelationTags(relId)
845
# show all relation members on osm dock widget
846
self.__loadRelationMembers(relId)
848
# enable list of members and buttons for relation removing and editing
849
self.relMembersList.setEnabled(True)
850
self.editRelationButton.setEnabled(True)
851
self.removeRelationButton.setEnabled(True)
854
def __loadRelationMembers(self,relId):
855
"""Functions loads the list of members of specified relation.
856
Relation is given by its identifier.
858
Loading is realized into the appropriate QListWidget of "OSM Feature" dockable widget.
859
Resulting list contains info on member's identifier, type and its role in specified relation.
861
@param relId identifier of relation
864
self.relMembersList.clear()
865
# ask database manager for all relation members
866
self.featRelMembers=self.plugin.dbm.getRelationMembers(relId)
869
for i in range(0,len(self.featRelMembers)):
870
memId=self.featRelMembers[i][0]
871
memType=self.featRelMembers[i][1]
872
memRole=self.featRelMembers[i][2]
874
listRow=QString("(%1) - %2").arg(memId).arg(memType)
875
if memRole and memRole<>"":
876
listRow=listRow.append(QString(", role:%3").arg(memRole))
878
self.relMembersList.addItem(listRow)
881
def __loadRelationTags(self,relId):
882
"""Functions loads the list of tags of specified relation.
883
Relation is given by its identifier.
885
Loading is realized into the appropriate QTreeWidget of "OSM Feature" dockable widget.
886
Resulting tags table has two columns "Key","Value" for easy representing of tag pairs.
888
@param relId identifier of relation
891
self.relTagsTree.clear()
892
self.__relTagsLoaded=False
893
# ask database manager for all relation tags
894
self.featRelTags=self.plugin.dbm.getFeatureTags(relId,"Relation")
896
self.relTagsTree.setColumnCount(2)
897
self.relTagsTree.setHeaderLabels(["Key","Value"])
899
for i in range(0,len(self.featRelTags)):
900
self.relTagsTree.addTopLevelItem(QTreeWidgetItem([self.featRelTags[i][0],self.featRelTags[i][1]]))
902
self.__relTagsLoaded=True
905
def __loadFeatureInformation(self,featId,featType):
906
"""Functions shows up the basic information on feature.
907
Feature is given by its identifier and its type.
909
Info is loaded to appropriate place of the "OSM Feature" widget.
910
Basic info consists (mainly) of feature's identifier, type, owner and timestamp.
912
@param featId identifier of feature to load
913
@param featType type of feature to load - one of 'Point','Line','Polygon'
916
# asking OsmDatabaseManager for missing information
917
featUser=self.plugin.dbm.getFeatureOwner(featId,featType)
918
featCreated=self.plugin.dbm.getFeatureCreated(featId,featType) # returned as string
919
# timestamp example: "2008-02-18T15:34:14Z"
921
self.typeIdLabel.setText("")
922
self.userLabel.setText("")
923
self.createdLabel.setText("")
925
# put non-tags feature information on dock widget
927
self.typeIdLabel.setText(QString("%1 %2").arg(featType).arg(str(featId)))
929
self.userLabel.setText(QString("%1").arg(featUser))
932
DT_format_osm=Qt.ISODate
933
# "yyyy-MM-ddThh:mm:ssZ"
934
DT_format_plugin="yy/MM/dd - h:mm"
935
self.createdLabel.setText(QDateTime.fromString(featCreated,DT_format_osm).toString(DT_format_plugin))
938
def __loadFeatureTags(self,featId,featType):
939
"""Functions loads the list of tags of specified feature.
940
Feature is given by its identifier and its type.
942
Loading is realized into the appropriate QTableWidget of "OSM Feature" dockable widget.
943
Resulting tags table has two columns "Key","Value" for easy representing of tag pairs.
945
@param featId identifier of feature to load
946
@param featType type of feature to load - one of 'Point','Line','Polygon'
949
# clear table with information about feature's tags
950
self.tagTable.clear()
952
# fill tableWidget with tags of selected feature
953
tableData=self.plugin.dbm.getFeatureTags(featId,featType)
954
rowCount=len(tableData)
955
self.__tagsLoaded=False
957
self.tagTable.setRowCount(rowCount+1)
958
self.tagTable.setColumnCount(2)
959
self.tagTable.setHorizontalHeaderItem(0,QTableWidgetItem("Key"))
960
self.tagTable.setHorizontalHeaderItem(1,QTableWidgetItem("Value"))
962
for i in range(0,rowCount):
963
self.tagTable.setItem(i,0,QTableWidgetItem(QString.fromUtf8(tableData[i][0])))
964
self.tagTable.setItem(i,1,QTableWidgetItem(QString.fromUtf8(tableData[i][1])))
965
self.tagTable.item(i,0).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
966
self.tagTable.item(i,1).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)
968
self.tagTable.setItem(rowCount,0,QTableWidgetItem(self.newTagLabel))
969
self.tagTable.setItem(rowCount,1,QTableWidgetItem(""))
970
self.tagTable.item(rowCount,0).setFlags(Qt.ItemIsEnabled | Qt.ItemIsEditable)
971
self.tagTable.item(rowCount,1).setFlags(Qt.ItemIsEnabled)
972
self.__tagsLoaded=True
974
# enable tags table for editing
975
self.tagTable.setEnabled(True)
976
self.deleteTagsButton.setEnabled(True)
979
def __loadFeatureRelations(self,featId,featType):
980
"""Functions loads the list of relations of specified feature.
981
Feature is given by its identifier and its type.
983
Loading is realized into the appropriate QListWidget of "OSM Feature" dockable widget.
984
If no relation exists for specified feature, listWidget is filled with the only row with text: "<no relation>".
986
@param featId identifier of feature to load
987
@param featType type of feature to load - one of 'Point','Line','Polygon'
990
self.relTagsTree.setColumnCount(0)
991
self.relMembersList.setEnabled(False)
993
# disable widget buttons
994
self.editRelationButton.setEnabled(False)
995
self.removeRelationButton.setEnabled(False)
997
# clear all tables connected to relations
998
self.relListWidget.clear()
999
self.relTagsTree.clear()
1000
self.relMembersList.clear()
1002
# load relations for selected feature
1003
self.featRels=self.plugin.dbm.getFeatureRelations(featId,featType)
1005
for i in range(0,len(self.featRels)):
1006
self.relListWidget.addItem(self.__getRelationInfo(self.featRels[i]))
1008
if len(self.featRels)==0:
1009
self.relListWidget.addItem("<no relation>")
1010
self.addRelationButton.setEnabled(True)
1013
# enable relation tables and button for relation addition
1014
self.addRelationButton.setEnabled(True)
1015
self.relListWidget.setEnabled(True)
1016
self.relTagsTree.setEnabled(True)
1019
def reloadFeatureRelations(self):
1020
"""Functions reloads the list of relations for currently loaded feature.
1022
Loading is realized into the appropriate QListWidget of "OSM Feature" dockable widget.
1023
If no relation exists for specified feature, listWidget is filled with the only row with text: "<no relation>".
1026
self.relTagsTree.setColumnCount(0)
1027
self.relMembersList.setEnabled(False)
1029
# disable widget buttons
1030
self.editRelationButton.setEnabled(False)
1031
self.removeRelationButton.setEnabled(False)
1033
# clear all tables connected to relations
1034
self.relListWidget.clear()
1035
self.relTagsTree.clear()
1036
self.relMembersList.clear()
1038
# load relations for selected feature
1039
self.featRels=self.plugin.dbm.getFeatureRelations(self.feature.id(),self.featureType)
1041
for i in range(0,len(self.featRels)):
1042
self.relListWidget.addItem(self.__getRelationInfo(self.featRels[i]))
1044
if len(self.featRels)==0:
1045
self.relListWidget.addItem("<no relation>")
1046
self.addRelationButton.setEnabled(True)
1049
# enable relation tables and button for relation addition
1050
self.addRelationButton.setEnabled(True)
1051
self.relListWidget.setEnabled(True)
1052
self.relTagsTree.setEnabled(True)
1055
def putMarkersOnMembers(self,feat,featType):
1056
"""Function adds additional vertexMarkers to the map.
1058
Additional vertexMarker are used to provide better marking of line/polygon.
1059
In that case line/polygon geometry is marked with rubberband first, and second one vertexMarker is put
1060
on each its vertex. Such extended marking of line/polygon is used (for example) when calling loadFeature() method with
1061
"markingMode" parameter set to 2.
1063
@param featId identifier of feature to load
1064
@param featType type of feature to load - one of 'Point','Line','Polygon'
1067
if featType=='Point':
1071
if featType=='Line':
1072
pline=feat.geometry().asPolyline()
1074
elif featType=='Polygon':
1075
pline=feat.geometry().asPolygon()[0]
1077
# get qgis settings of line width and color for rubberband
1078
settings=QSettings()
1079
qgsLineWidth=2 # use fixed width
1080
qgsLineRed=settings.value("/qgis/digitizing/line_color_red",QVariant(255)).toInt()
1081
qgsLineGreen=settings.value("/qgis/digitizing/line_color_green",QVariant(0)).toInt()
1082
qgsLineBlue=settings.value("/qgis/digitizing/line_color_blue",QVariant(0)).toInt()
1084
for i in range(0,len(pline)):
1085
verMarker=QgsVertexMarker(self.plugin.canvas)
1086
verMarker.setIconType(3)
1087
verMarker.setIconSize(6)
1088
verMarker.setColor(QColor(qgsLineRed[0],qgsLineGreen[0],qgsLineBlue[0]))
1089
verMarker.setPenWidth(qgsLineWidth)
1090
verMarker.setCenter(pline[i])
1092
self.verMarkers.append(verMarker)
1095
def __removeMemberMarkers(self):
1096
"""Function removes additional vertexMarkers from the map.
1098
Additional vertexMarker are used to provide better marking of line/polygon.
1099
In that case line/polygon geometry is marked with rubberband first, and second one vertexMarker is put
1100
on each its vertex. Such extended marking of line/polygon is used (for example) when calling loadFeature() method with
1101
"markingMode" parameter set to 2.
1104
for verMarker in self.verMarkers:
1105
self.plugin.canvas.scene().removeItem(verMarker)
1110
def showFeatureOnMap(self,featId,featType):
1111
"""Function just shows up specified feature on the map canvas.
1113
Showing feature up is realized by putting rubberBand (vertexMarker) on its whole geometry.
1114
This rubberBand (vM) is the same as when using loadFeature() method with "markingMode" parameter set to 1.
1116
Feature is given by its identifier and its type. Feature data are not loaded into "OSM Feature" widget.
1117
Rubberbands (vertexMarkers) of other features are not removed from the map canvas before this action.
1119
@param featId identifier of feature to load
1120
@param featType type of feature to load - one of 'Point','Line','Polygon'
1123
# we have to know feature's geometry to be able to display it on map
1124
featGeom=self.plugin.dbm.getFeatureGeometry(featId,featType)
1126
return # nothing to show :-/
1128
if featType=='Polygon':
1129
self.relRubBandPol.setToGeometry(featGeom,self.plugin.canvas.currentLayer())
1130
elif featType=='Point':
1131
self.relVerMarker.setCenter(featGeom.asPoint())
1132
self.relVerMarker.show()
1133
elif featType=='Line':
1134
self.relRubBand.setToGeometry(featGeom,self.plugin.canvas.currentLayer())
1137
def loadFeature(self,feat,featType,markingMode=1):
1138
"""Function loads information on specified feature into "OSM Feature" widget elements.
1139
It shows up features identifier, type, osm user, timestamp, all its tags and related OSM relations.
1141
According to the value of parameter "markingMode" is marks feature on the map canvas. If "markingMode" equals to zero
1142
feature is not marked on the map. If it equals to 1, simple rubberband (or vertexMarker) is put on the feature (this is
1143
default behavior). If "markingMode" equals to 2, extended rubberband is shown on the map, especially for non-point features.
1145
@param feat QgsFeature object of feature to load
1146
@param featType type of feature to load - one of 'Point','Line','Polygon'
1147
@param markingMode not compulsory; defines quality of feature's rubberband on map
1150
if not feat or not featType:
1154
# remember which feature is loaded
1155
self.featureType=featType
1158
# move rubberband to selected feature
1159
self.rubBandPol.reset(True)
1160
self.rubBand.reset(False)
1161
self.verMarker.hide()
1162
self.__removeMemberMarkers()
1165
if self.featureType=='Polygon':
1166
self.rubBandPol.setToGeometry(feat.geometry(),self.plugin.canvas.currentLayer())
1168
self.putMarkersOnMembers(feat,featType)
1169
elif self.featureType=='Point':
1170
self.verMarker.setCenter(feat.geometry().asPoint())
1171
self.verMarker.show()
1172
elif self.featureType=='Line':
1173
self.rubBand.setToGeometry(feat.geometry(),self.plugin.canvas.currentLayer())
1175
self.putMarkersOnMembers(feat,featType)
1177
# show common feature information (id,feature originator,created,feature type,...)
1178
self.__loadFeatureInformation(feat.id(),featType)
1180
# show all tags connected to feature onto osm widget dialog
1181
self.__loadFeatureTags(feat.id(),featType)
1183
# show all relations connected to feature onto osm widget dialog
1184
self.__loadFeatureRelations(feat.id(),featType)
1186
# feature has been loaded; enable "remove feature" button
1187
self.removeButton.setEnabled(True)
1190
def __getRelationInfo(self,relId):
1191
"""Function returns brief info on specified relation. Information consists of relation identifier,
1192
its type and other important relation properties and it is returned simply in QString() object.
1194
Information are good to be shown in some list (of relations).
1196
@param relId identifier of relation
1197
@return brief info on relation concatenated in string
1200
relInfo=relType=relRoute=relBoundary=relRef=relRestr=""
1201
tags=self.plugin.dbm.getFeatureTags(relId,"Relation")
1203
for i in range(0,len(tags)):
1208
relType=value # type: route / boundary, ...
1210
relRoute=value # route: road / bicycle / foot / hiking / bus / railway / tram, ...
1211
elif key=="boundary":
1212
relBoundary=value # boundary: administrative, ...
1215
elif key=="restriction":
1218
if relType=="route":
1219
relInfo = QString("(%1) "+relRoute+" "+relType+" "+relRef).arg(relId)
1221
elif relType=="boundary":
1222
relInfo = QString("(%1) "+relBoundary+" "+relType).arg(relId)
1224
elif relType=="restriction":
1225
relInfo = QString("(%1) "+relRestr+" "+relType).arg(relId)
1228
relInfo = QString("(%1) relation of unknown type").arg(relId)
1231
relInfo = QString("(%1) "+relType).arg(relId)
1237
"""Function performs exactly one undo operation.
1240
self.plugin.undoredo.undo()
1244
"""Function performs exactly one redo operation.
1247
self.plugin.undoredo.redo()
1250
def __urDetailsChecked(self):
1251
"""Function is called after clicking on urDetailsButton checkbox.
1252
It shows or hides OSM Edit History widget.
1255
if self.urDetailsButton.isChecked():
1256
self.plugin.undoredo.show()
1257
self.urDetailsButton.setToolTip("Hide OSM Edit History")
1259
self.plugin.undoredo.hide()
1260
self.urDetailsButton.setToolTip("Show OSM Edit History")