diff --git a/DMS2DD.ods b/DMS2DD.ods index e23a105..2da7a71 100644 Binary files a/DMS2DD.ods and b/DMS2DD.ods differ diff --git a/PyQt4Dialogs.py b/PyQt4Dialogs.py index b9a78a6..f78c879 100644 --- a/PyQt4Dialogs.py +++ b/PyQt4Dialogs.py @@ -24,40 +24,145 @@ from PyQt4Widgets import XQPushButton, XQDialogButtonBox from database import * -try: - _fromUtf8 = QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s +def _fromUtf8(s): + return s # All dialogs using selector tool have a captured function # All dialogs have a getReturn function +class dlg_DatabaseConnection(QDialog): + """ This dialog enables the user to choose a database connection + defined through DB Manager + """ + + def __init__(self): + # initialize QDialog class + super(dlg_DatabaseConnection, self).__init__(None, Qt.WindowStaysOnTopHint) + # initialize ui + self.conn = None + self.setupUi() + self.populateDatabaseChoices() + + def getDatabaseConnection(self): + return self.conn + + def populateDatabaseChoices(self): + """ Populate database connection choices + """ + settings = QSettings() + settings.beginGroup('PostgreSQL/connections') + self.cmbbx_conn.addItem(_fromUtf8("")) + self.cmbbx_conn.setItemText(0, QApplication.translate( + "dlg_DatabaseConnection", + "", + None + )) + for i,db in enumerate(settings.childGroups()): + self.cmbbx_conn.addItem(_fromUtf8("")) + self.cmbbx_conn.setItemText(i + 1, QApplication.translate( + "dlg_DatabaseConnection", + db, + None + )) + + def testDatabaseConnection(self): + """ Test database connection has necessary tables + """ + conn = str(self.cmbbx_conn.currentText()) + if not bool(conn.replace(" ", "")): + QMessageBox.information(self, "Database Connection", "Please select a database connection") + else: + self.conn = conn + self.accept() + + def setupUi(self): + """ Initialize ui + """ + # define ui widgets + self.setObjectName(_fromUtf8("dlg_DatabaseConnection")) + self.resize(370, 200) + self.setCursor(QCursor(Qt.ArrowCursor)) + self.setModal(True) + self.gridLayout = QGridLayout(self) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.verticalLayout = QVBoxLayout() + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.lbl_instr = QLabel(self) + self.lbl_instr.setWordWrap(True) + self.lbl_instr.setObjectName(_fromUtf8("lbl_instr")) + self.verticalLayout.addWidget(self.lbl_instr) + self.formLayout = QFormLayout() + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.lbl_conn = QLabel(self) + self.lbl_conn.setObjectName(_fromUtf8("lbl_conn")) + self.formLayout.setWidget(0, QFormLayout.LabelRole, self.lbl_conn) + self.cmbbx_conn = QComboBox(self) + self.cmbbx_conn.setObjectName(_fromUtf8("cmbbx_conn")) + self.formLayout.setWidget(0, QFormLayout.FieldRole, self.cmbbx_conn) + self.verticalLayout.addLayout(self.formLayout) + self.lbl_aside = QLabel(self) + self.lbl_aside.setWordWrap(True) + self.lbl_aside.setObjectName(_fromUtf8("lbl_aside")) + self.verticalLayout.addWidget(self.lbl_aside) + self.btnbx_options = XQDialogButtonBox(self) + self.btnbx_options.setOrientation(Qt.Horizontal) + self.btnbx_options.setStandardButtons(XQDialogButtonBox.Cancel|XQDialogButtonBox.Ok) + self.btnbx_options.setObjectName(_fromUtf8("btnbx_options")) + self.verticalLayout.addWidget(self.btnbx_options) + self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) + # translate ui widgets' text + self.setWindowTitle(QApplication.translate( + "dlg_DatabaseConnection", + "Database Connection", + None, + QApplication.UnicodeUTF8 + )) + self.lbl_conn.setText(QApplication.translate( + "dlg_DatabaseConnection", + "Connection: ", + None + )) + self.lbl_aside.setText(QApplication.translate( + "dlg_DatabaseConnection", + "*If your database connection cannot be found above then please define it through the PostGIS Connection Manager.", + None + )) + self.lbl_instr.setText(QApplication.translate( + "dlg_DatabaseConnection", + "A database connection has not yet been selected or is no longer valid. Please select a database connection.", + None + )) + # connect ui widgets + self.btnbx_options.accepted.connect(self.testDatabaseConnection) + self.btnbx_options.rejected.connect(self.reject) + QMetaObject.connectSlotsByName(self) + + class dlg_Selector(QDialog): - """ This dialog enables the selection of single features on a vector layer by means of the feature selector tool defined in qgisToolbox + """ This dialog enables the selection of single features on a + vector layer by means of the feature selector tool defined in + qgisToolbox """ - def __init__(self, db, iface, layer, query, obj={"NAME":"NONAME", "PURPOSE":"NOPURPOSE", "ACTION":"NOACTION"}, preserve = False, parent = None): + def __init__(self, db, iface, requiredLayer, mode, query, preserve = False, parent = None): # initialize QDialog class super(dlg_Selector, self).__init__(parent, Qt.WindowStaysOnTopHint) # initialize ui - self.setupUi(obj) - # initialize instance variables + self.setupUi(requiredLayer, mode) self.db = db self.iface = iface - self.layer = layer + self.layer = requiredLayer + self.mode = mode self.query = query - self.obj = obj self.preserve = preserve self.confirmed = False self.featID = None # initialize selector tool - self.selector = featureSelector(iface, layer, True, self) + self.selector = featureSelector(iface, requiredLayer.layer, True, self) # save qgis tool self.tool = self.selector.parentTool - def getReturn(self): - """ Return intended variable(s) after the dialog has been accepted - """ + def getFeatureId(self): return self.featID def executeOption(self, button): @@ -66,23 +171,31 @@ def executeOption(self, button): if self.btnbx_options.standardButton(button) == QDialogButtonBox.Ok: # check that a feature has been selected if self.featID is None: - QMessageBox.information(self, "No %s Selected" %(self.obj["NAME"].title(),), "Please select a %s." %(self.obj["NAME"].lower(),)) + QMessageBox.information( + self, + "No %s Selected" %(self.layer.name.title(),), + "Please select a %s." %(self.layer.name.lower(),) + ) return # check confirmation if not self.confirmed: - QMessageBox.information(self, "No Confirmation", "Please tick the confimation check box.") + QMessageBox.information( + self, + "No Confirmation", + "Please tick the confimation check box." + ) return # reset qgis tool self.iface.mapCanvas().setMapTool(self.tool) # remove selection if needed - if not self.preserve: self.layer.removeSelection() + if not self.preserve: self.layer.layer.removeSelection() # accept dialog self.accept() else: # reset qgis tool self.iface.mapCanvas().setMapTool(self.tool) # remove selection - self.layer.removeSelection() + self.layer.layer.removeSelection() # reject dialog self.reject() @@ -93,7 +206,7 @@ def captured(self, selected): self.selector.disableCapturing() # update dialog self.featID = selected[0] - self.lnedt_featID.setText(str(self.db.query(self.query["SELECT"], (self.featID,))[0][0])) + self.lnedt_featID.setText(str(self.db.query(self.query, (self.featID,))[0][0])) self.pshbtn_re.setEnabled(True) self.chkbx_confirm.setEnabled(True) @@ -116,7 +229,7 @@ def confirm(self, state): self.pshbtn_re.setEnabled(not bool(state)) self.confirmed = bool(state) - def setupUi(self, obj): + def setupUi(self, layer, mode): """ Initialize ui """ # define ui widgets @@ -137,7 +250,7 @@ def setupUi(self, obj): self.formLayout.setObjectName(_fromUtf8("formLayout")) self.lbl_featID = QLabel(self.widget) self.lbl_featID.setObjectName(_fromUtf8("lbl_featID")) - self.formLayout.setWidget(0, QFormLayout.LabelRole, self.lbl_featID) + self.formLayout.setWidget(0, QFormLayout.LabelRole, self.lbl_featID) self.lnedt_featID = QLineEdit(self.widget) self.lnedt_featID.setEnabled(False) self.lnedt_featID.setObjectName(_fromUtf8("lnedt_featID")) @@ -159,32 +272,40 @@ def setupUi(self, obj): self.verticalLayout.addWidget(self.btnbx_options) self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) # translate ui widgets' text - self.setWindowTitle(QApplication.translate("dlg_Selector", "%s %s" %(obj["NAME"].title(), obj["PURPOSE"].title()), None, QApplication.UnicodeUTF8)) - self.lbl_featID.setText(QApplication.translate("dlg_Selector", "%s ID" %(obj["NAME"].title(),), None, QApplication.UnicodeUTF8)) - self.pshbtn_re.setText(QApplication.translate("dlg_Selector", "Re-select", None, QApplication.UnicodeUTF8)) - self.chkbx_confirm.setText(QApplication.translate("dlg_Selector", "I am sure I want to %s this %s" %(obj["ACTION"].lower(), obj["NAME"].lower()), None, QApplication.UnicodeUTF8)) + self.setWindowTitle(QApplication.translate("dlg_Selector", + "%s %s" %(layer.name.title(), mode.actor.title()), + None, + QApplication.UnicodeUTF8)) + self.lbl_featID.setText(QApplication.translate("dlg_Selector", + "%s ID" %(layer.name.title(),), + None, + QApplication.UnicodeUTF8)) + self.pshbtn_re.setText(QApplication.translate("dlg_Selector", + "Re-select", + None, + QApplication.UnicodeUTF8)) + self.chkbx_confirm.setText(QApplication.translate("dlg_Selector", + "I am sure I want to %s this %s" %(mode.action.lower(), layer.name.lower()), + None, + QApplication.UnicodeUTF8)) # connect ui widgets self.pshbtn_re.clicked.connect(self.reselect) self.chkbx_confirm.stateChanged.connect(self.confirm) self.btnbx_options.clicked.connect(self.executeOption) QMetaObject.connectSlotsByName(self) + class dlg_Manager(QDialog): """ This dialog enables the user to select an option with regards to managing a vector layer """ - def __init__(self, obj={"NAME":"NONAME",}, parent = None): - # initialize QDialog class + def __init__(self, requiredLayer, parent=None): super(dlg_Manager, self).__init__(parent, Qt.WindowStaysOnTopHint) - # initialize ui - self.setupUi(obj) - # initialize instance variables - self.obj = obj + self.setupUi(requiredLayer) + self.layer = requiredLayer self.option = None - def getReturn(self): - """ Return intended variable(s) after the dialog has been accepted - """ + def getOption(self): return self.option def executeOption(self, button): @@ -205,7 +326,7 @@ def executeOption(self, button): # reject dialog self.reject() - def setupUi(self, obj): + def setupUi(self, layer): """ Initialize ui """ # define ui widgets @@ -240,66 +361,257 @@ def setupUi(self, obj): self.vrtlyt.addWidget(self.btnbx_options) self.mainlyt.addLayout(self.vrtlyt, 0, 0, 1, 1) # translate ui widgets' text - self.setWindowTitle(QApplication.translate("dlg_Manager", "%s Manager" %(obj["NAME"].title(),), None, QApplication.UnicodeUTF8)) - self.rdbtn_add.setText(QApplication.translate("dlg_Manager", "Create New %s" %(obj["NAME"].title(),), None, QApplication.UnicodeUTF8)) - self.rdbtn_edit.setText(QApplication.translate("dlg_Manager", "Edit Existing %s" %(obj["NAME"].title(),), None, QApplication.UnicodeUTF8)) - self.rdbtn_del.setText(QApplication.translate("dlg_Manager", "Delete Existing %s" %(obj["NAME"].title(),), None, QApplication.UnicodeUTF8)) + self.setWindowTitle(QApplication.translate( + "dlg_Manager", + "%s Manager" %(layer.name.title(),), + None, + QApplication.UnicodeUTF8 + )) + self.rdbtn_add.setText(QApplication.translate( + "dlg_Manager", + "Create New %s" %(layer.name.title(),), + None, + QApplication.UnicodeUTF8 + )) + self.rdbtn_edit.setText(QApplication.translate( + "dlg_Manager", + "Edit Existing %s" %(layer.name.title(),), + None, + QApplication.UnicodeUTF8 + )) + self.rdbtn_del.setText(QApplication.translate( + "dlg_Manager", + "Delete Existing %s" %(layer.name.title(),), + None, + QApplication.UnicodeUTF8 + )) + # connect ui widgets + self.btnbx_options.clicked.connect(self.executeOption) + QMetaObject.connectSlotsByName(self) + + +class dlg_FormBeacon(QDialog): + """ This dialog enables a user to define and modify a beacon + """ + + def __init__(self, db, query, fields, values=[], parent = None): + # initialize QDialog class + super(dlg_FormBeacon, self).__init__(parent, Qt.WindowStaysOnTopHint) + # initialize ui + self.setupUi(fields) + # initialize instance variables + self.db = db + self.query = query + self.fields = fields + self.values_old = {} + self.values_new = {} + self.colours = { + "REQUIRED":"background-color: rgba(255, 107, 107, 150);", + "TYPE":"background-color: rgba(107, 107, 255, 150);", + "UNIQUE":"background-color: rgba(107, 255, 107, 150);" + } + # populate form if values are given + if bool(values): + self.populateForm(values) + + def getValues(self): + """ Return intended variable(s) after the dialog has been accepted + """ + return (self.values_old, self.values_new) + + def populateForm(self, values): + """ Populate form with given values + """ + for index,v in enumerate(values): + if v is not None: self.lnedts[index].setText(str(v)) + self.values_old[self.fields[index].name] = v + + def executeOption(self, button): + """ Perform validation and close the dialog + """ + if self.btnbx_options.standardButton(button) == QDialogButtonBox.Save: + values_new = {} + # check required fields + valid = True + for lnedt in self.lnedts: + if bool(lnedt.property("REQUIRED")): + if str(lnedt.text()).strip() is "": + lnedt.setStyleSheet(self.colours["REQUIRED"]) + valid = False + else: lnedt.setStyleSheet("") + if not valid: + QMessageBox.information( + self, + "Empty Required Fields", + "Please ensure that all required fields are completed." + ) + return + # check correct field types + valid = True + for index,lnedt in enumerate(self.lnedts): + try: + if str(lnedt.text()).strip() is not "": + cast = self.fields[index].type + tmp = cast(str(lnedt.text()).strip()) + values_new[self.fields[index].name] = tmp + lnedt.setStyleSheet("") + else: + values_new[self.fields[index].name] = None + except Exception as e: + lnedt.setStyleSheet(self.colours["TYPE"]) + valid = False + if not valid: + QMessageBox.information( + self, + "Invalid Field Types", + "Please ensure that fields are completed with valid types." + ) + return + # check unique fields + valid = True + for index,lnedt in enumerate(self.lnedts): + if str(lnedt.text()).strip() is "": continue + if bool(lnedt.property("UNIQUE")): + if self.fields[index].name in self.values_old.keys() and values_new[self.fields[index].name] == self.values_old[self.fields[index].name]: + lnedt.setStyleSheet("") + elif bool(int(self.db.query(self.query %(self.fields[index].name, "%s"), (values_new[self.fields[index].name],))[0][0])): + lnedt.setStyleSheet(self.colours["UNIQUE"]) + valid = False + else: lnedt.setStyleSheet("") + if not valid: + QMessageBox.information( + self, + "Fields Not Unique", + "Please ensure that fields are given unique values." + ) + return + # save values + self.values_new = values_new + # accept dialog + self.accept() + else: + # reject dialog + self.reject() + + def setupUi(self, fields): + """ Initialize ui + """ + # define ui widgets + self.setObjectName(_fromUtf8("dlg_FormBeacon")) + self.setCursor(QCursor(Qt.ArrowCursor)) + self.setModal(True) + self.gridLayout = QGridLayout(self) + self.gridLayout.setSizeConstraint(QLayout.SetFixedSize) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.verticalLayout = QVBoxLayout() + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.formLayout = QFormLayout() + self.formLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.lbls = [] + self.lnedts = [] + # define form fields dynamically from the database schema + for index, f in enumerate(fields): + lbl = QLabel(self) + lbl.setObjectName(_fromUtf8("lbl_%s" %(f.name,))) + self.formLayout.setWidget(index, QFormLayout.LabelRole, lbl) + self.lbls.append(lbl) + lnedt = QLineEdit(self) + lnedt.setProperty("REQUIRED", f.required) + lnedt.setProperty("UNIQUE", f.unique) + lnedt.setObjectName(_fromUtf8("lnedt_%s" %(f.name,))) + self.formLayout.setWidget(index, QFormLayout.FieldRole, lnedt) + self.lnedts.append(lnedt) + lbl.setText(QApplication.translate("dlg_FormBeacon", + ("*" if bool(self.lnedts[index].property("REQUIRED")) else "") + f.name.title(), + None, + QApplication.UnicodeUTF8)) + lnedt.setProperty("TYPE", QApplication.translate("dlg_FormBeacon", str(f.type), None, QApplication.UnicodeUTF8)) + self.verticalLayout.addLayout(self.formLayout) + self.line_1 = QFrame(self) + self.line_1.setFrameShape(QFrame.HLine) + self.line_1.setFrameShadow(QFrame.Sunken) + self.line_1.setObjectName(_fromUtf8("line_1")) + self.verticalLayout.addWidget(self.line_1) + self.label = QLabel(self) + self.label.setObjectName(_fromUtf8("label")) + self.verticalLayout.addWidget(self.label) + self.line_2 = QFrame(self) + self.line_2.setFrameShape(QFrame.HLine) + self.line_2.setFrameShadow(QFrame.Sunken) + self.line_2.setObjectName(_fromUtf8("line_2")) + self.verticalLayout.addWidget(self.line_2) + self.btnbx_options = QDialogButtonBox(self) + self.btnbx_options.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Save) + self.btnbx_options.setObjectName(_fromUtf8("btnbx_options")) + self.btnbx_options.setCursor(QCursor(Qt.PointingHandCursor)) + self.verticalLayout.addWidget(self.btnbx_options) + self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) + # translate ui widgets' text + self.setWindowTitle(QApplication.translate("dlg_FormBeacon", "Beacon Form", None, QApplication.UnicodeUTF8)) + self.label.setText(QApplication.translate("dlg_FormBeacon", "

*Required Field

", None, QApplication.UnicodeUTF8)) # connect ui widgets self.btnbx_options.clicked.connect(self.executeOption) QMetaObject.connectSlotsByName(self) + class dlg_FormParcel(QDialog): """ This dialog enables a user to define and modify a parcel """ - - def __init__(self, db, iface, layers, layersDict, autocomplete = [], values = {}, parent = None): + + def __init__(self, db, iface, requiredLayers, SQL_BEACONS, SQL_PARCELS, autocomplete=[], data={}, parent=None): # initialize QDialog class super(dlg_FormParcel, self).__init__(parent, Qt.WindowStaysOnTopHint) # initialize ui - self.setupUi(autocomplete, layersDict) - # initialize instance variables + self.setupUi(autocomplete) self.db = db self.iface = iface - self.layers = layers - self.layersDict = layersDict + self.layers = requiredLayers + self.SQL_BEACONS = SQL_BEACONS + self.SQL_PARCELS = SQL_PARCELS self.autocomplete = autocomplete self.values_old = {} self.values_new = {} + self.sequence = [] self.new_accepted = False # initialize selector tool - self.selector = featureSelector(iface, layers["BEACONS"], False, self) + self.selector = featureSelector(iface, requiredLayers[0].layer, False, self) # save qgis tool self.tool = self.selector.parentTool # populate form if values are given - if bool(values): - self.populateForm(values) + if bool(data): + self.populateForm(data) self.pshbtn_reset.setEnabled(True) - def getReturn(self): - """ Return intended variable(s) after the dialog has been accepted - """ + def getValues(self): return (self.values_old, self.values_new) - def populateForm(self, values): + def populateForm(self, data): """ Populte form with values given """ # get values - checker = lambda d, v: d[v] if v in d.keys() else None - feat_id = checker(values, "parcel_id") - feat_sequence = checker(values, "sequence") + checker = lambda d, k: d[k] if k in d.keys() else None + feat_id = checker(data, "parcel_id") + feat_sequence = checker(data, "sequence") # use values if bool(feat_id): # populate parcel_id - self.values_old["parcel_id"] = self.db.query(self.layersDict["PARCELS"]["SQL"]["SELECT"], (feat_id,))[0][0] + self.values_old["parcel_id"] = self.db.query( + self.SQL_PARCELS["SELECT"], (feat_id,) + )[0][0] self.lnedt_parcelID.setText(str(self.values_old["parcel_id"])) - self.highlightFeature(self.layers["PARCELS"], feat_id) + self.highlightFeature(self.layers[1].layer, feat_id) if bool(feat_sequence): # populate sequence + self.sequence = [] self.values_old["sequence"] = [] - for beacon_id in feat_sequence: - self.values_old["sequence"].append(self.db.query(self.layersDict["BEACONS"]["SQL"]["SELECT"], (beacon_id,))[0][0]) - self.lstwdg_sequence.addItem(QString(str(self.values_old["sequence"][-1]))) - self.highlightFeatures(self.layers["BEACONS"], feat_sequence) + for id in feat_sequence: + beacon_id = str(self.db.query(self.SQL_BEACONS["SELECT"], (id,))[0][0]) + self.sequence.append(beacon_id) + self.values_old["sequence"].append(beacon_id) + self.lstwdg_sequence.addItem(beacon_id.replace("\n","")) + self.highlightFeatures(self.layers[0].layer, feat_sequence) + self.selector.selected = feat_sequence # update selector selection self.selector.selected = feat_sequence @@ -312,7 +624,12 @@ def highlightFeatures(self, layer, features): """ Highlight multiple features on a vector layer """ layer.setSelectedFeatures(features) - + + def captured(self, selected): + """ Notify the dialog of a feature selection and disable selecting + """ + pass + def executeOption(self, button): """ Perform validation and close the dialog """ @@ -320,66 +637,100 @@ def executeOption(self, button): parcel_id = str(self.lnedt_parcelID.text()).strip() # check that parcel id exists if parcel_id == "": - QMessageBox.information(self, "Invalid Parcel ID", "Please enter a parcel ID.") + QMessageBox.information( + self, + "Invalid Parcel ID", + "Please enter a parcel ID." + ) + return + # check that parcel id is an int + try: + int(parcel_id) + except ValueError: + QMessageBox.information( + self, + "Invalid Parcel ID", + "Please enter a number for the parcel ID." + ) return # check that parcel id is valid (i.e. current, unique, available) if "parcel_id" in self.values_old.keys() and str(self.values_old["parcel_id"]) == parcel_id: pass - elif not bool(int(self.db.query(self.layersDict["PARCELS"]["SQL"]["UNIQUE"], (parcel_id,))[0][0])): - if not self.new_accepted and QMessageBox.question(self, 'Confirm New Parcel ID', "Are you sure you want to create a new parcel ID?", QMessageBox.Yes, QMessageBox.No) == QMessageBox.No: + elif not bool(self.db.query( + self.SQL_PARCELS["UNIQUE"], (int(parcel_id),) + )[0][0]): + if not self.new_accepted and QMessageBox.question( + self, + 'Confirm New Parcel ID', + "Are you sure you want to create a new parcel ID?", + QMessageBox.Yes, + QMessageBox.No + ) == QMessageBox.No: return self.new_accepted = True else: - if not bool(self.db.query(self.layersDict["PARCELS"]["SQL"]["AVAILABLE"], (parcel_id,))[0][0]): - QMessageBox.information(self, "Duplicated Parcel ID", "Please enter a unique or available parcel ID.") + if not bool(self.db.query( + self.SQL_PARCELS["AVAILABLE"], + (parcel_id,) + )[0][0]): + QMessageBox.information( + self, + "Duplicated Parcel ID", + "Please enter a unique or available parcel ID." + ) return # check that at least 3 beacons exist within the sequence if len(self.selector.selected) < 3: - QMessageBox.information(self, "Too Few Beacons", "Please ensure that there are at least 3 beacons listed in the sequence.") + QMessageBox.information( + self, + "Too Few Beacons", + "Please ensure that there are at least 3 beacons listed in the sequence." + ) return # save parcel id self.values_new["parcel_id"] = parcel_id # save sequence - sequence = [] - for i in self.selector.selected: - sequence.append(self.db.query(self.layersDict["BEACONS"]["SQL"]["SELECT"], (i,))[0][0]) - self.values_new["sequence"] = sequence + self.values_new["sequence"] = self.sequence # reset qgis tool self.iface.mapCanvas().setMapTool(self.tool) # remove selection - for l in self.layers.values(): l.removeSelection() + for l in self.layers: l.layer.removeSelection() # accept dialog self.accept() else: # reset qgis tool self.iface.mapCanvas().setMapTool(self.tool) # remove selection - for l in self.layers.values(): l.removeSelection() + for l in self.layers: l.layer.removeSelection() # accept dialog self.reject() - def captured(self, selected): - """ Notify the dialog of a feature selection and disable selecting - """ - # clear sequence - self.lstwdg_sequence.clear() - # create sequence - for i in selected: - self.lstwdg_sequence.addItem(QString(str(self.db.query(self.layersDict["BEACONS"]["SQL"]["SELECT"], (i,))[0][0]))) - def newBeacon(self): """ Define a new beacon on the fly to be added to the parcel sequence """ # disable self self.setEnabled(False) - # present beacon form - data = self.db.getSchema(self.layersDict["BEACONS"]["TABLE"], [self.layersDict["BEACONS"]["GEOM"], self.layersDict["BEACONS"]["PKEY"]]) - frm = dlg_FormBeacon(self.db, data, self.layersDict["BEACONS"]["SQL"], parent = self) + # get fields + fields = self.db.getSchema( + self.layers[0].table, [ + self.layers[0].geometry_column, + self.layers[0].primary_key + ]) + # display form + frm = dlg_FormBeacon( + self.db, + self.SQL_BEACONS["UNIQUE"], + fields + ) frm.show() frm_ret = frm.exec_() if bool(frm_ret): - # save new beacon - id = self.db.query(self.layersDict["BEACONS"]["SQL"]["INSERT"].format(fields = ", ".join([f["NAME"] for f in data]), values = ", ".join(["%s" for f in data])), [frm.getReturn()[1][f["NAME"]] for f in data])[0][0] + # add beacon to database + values_old, values_new = frm.getValues() + id = self.db.query( + self.SQL_BEACONS["INSERT"].format(fields = ", ".join(sorted(values_new.keys())), values = ", ".join(["%s" for k in values_new.keys()])), [values_new[k] for k in sorted(values_new.keys())])[0][0] + self.iface.mapCanvas().refresh() + self.highlightFeature(self.layers[0].layer, id) self.selector.appendSelection(id) # enable self self.setEnabled(True) @@ -402,22 +753,29 @@ def stopSeq(self): self.selector.disableCapturing() # perform button stuffs self.pshbtn_stop.setEnabled(False) + self.pshbtn_new.setEnabled(False) + self.lstwdg_sequence.clear() + self.sequence = [] + for i in self.selector.selected: + beacon_id = str(self.db.query(self.SQL_BEACONS["SELECT"], (i,))[0][0]) + self.sequence.append(beacon_id) + self.lstwdg_sequence.addItem(beacon_id.replace("\n","")) self.pshbtn_start.setEnabled(True) self.pshbtn_reset.setEnabled(True) - self.pshbtn_new.setEnabled(False) def resetSeq(self): """ Reset captured sequence """ # clear selection self.selector.clearSelection() + self.sequence = [] # clear sequence self.lstwdg_sequence.clear() # perform button stuffs self.pshbtn_reset.setEnabled(False) self.pshbtn_start.setEnabled(True) - def setupUi(self, autocomplete, layersDict): + def setupUi(self, autocomplete): """ Initialize ui """ # define ui widgets @@ -490,13 +848,20 @@ def setupUi(self, autocomplete, layersDict): self.verticalLayout_2.addWidget(self.btnbx_options) self.gridLayout.addLayout(self.verticalLayout_2, 0, 0, 1, 1) # translate ui widgets' text - self.setWindowTitle(QApplication.translate("dlg_FormParcel", "Parcel Form", None, QApplication.UnicodeUTF8)) - self.lbl_parcelID.setText(QApplication.translate("dlg_FormParcel", "Parcel ID", None, QApplication.UnicodeUTF8)) - self.lbl_sequence.setText(QApplication.translate("dlg_FormParcel", "Beacon Sequence", None, QApplication.UnicodeUTF8)) - self.pshbtn_new.setText(QApplication.translate("dlg_FormParcel", "New Beacon", None, QApplication.UnicodeUTF8)) - self.pshbtn_start.setText(QApplication.translate("dlg_FormParcel", "Start", None, QApplication.UnicodeUTF8)) - self.pshbtn_stop.setText(QApplication.translate("dlg_FormParcel", "Stop", None, QApplication.UnicodeUTF8)) - self.pshbtn_reset.setText(QApplication.translate("dlg_FormParcel", "Reset", None, QApplication.UnicodeUTF8)) + self.setWindowTitle(QApplication.translate("dlg_FormParcel", + "Parcel Form", None, QApplication.UnicodeUTF8)) + self.lbl_parcelID.setText(QApplication.translate("dlg_FormParcel", + "Parcel ID", None, QApplication.UnicodeUTF8)) + self.lbl_sequence.setText(QApplication.translate("dlg_FormParcel", + "Beacon Sequence", None, QApplication.UnicodeUTF8)) + self.pshbtn_new.setText(QApplication.translate("dlg_FormParcel", + "New Beacon", None, QApplication.UnicodeUTF8)) + self.pshbtn_start.setText(QApplication.translate("dlg_FormParcel", + "Start", None, QApplication.UnicodeUTF8)) + self.pshbtn_stop.setText(QApplication.translate("dlg_FormParcel", + "Stop", None, QApplication.UnicodeUTF8)) + self.pshbtn_reset.setText(QApplication.translate("dlg_FormParcel", + "Reset", None, QApplication.UnicodeUTF8)) # connect ui widgets self.pshbtn_new.clicked.connect(self.newBeacon) self.pshbtn_start.clicked.connect(self.startSeq) @@ -505,287 +870,27 @@ def setupUi(self, autocomplete, layersDict): self.btnbx_options.clicked.connect(self.executeOption) QMetaObject.connectSlotsByName(self) -class dlg_FormBeacon(QDialog): - """ This dialog enables a user to define and modify a beacon - """ - - def __init__(self, db, data, query, values = [], parent = None): - # initialize QDialog class - super(dlg_FormBeacon, self).__init__(parent, Qt.WindowStaysOnTopHint) - # initialize ui - self.setupUi(data) - # initialize instance variables - self.db = db - self.data = data - self.query = query - self.values_old = {} - self.values_new = {} - self.colours = { - "REQUIRED":"background-color: rgba(255, 107, 107, 150);", - "TYPE":"background-color: rgba(107, 107, 255, 150);", - "UNIQUE":"background-color: rgba(107, 255, 107, 150);" - } - # populate form if values are given - if bool(values): - self.populateForm(values) - - def getReturn(self): - """ Return intended variable(s) after the dialog has been accepted - """ - return (self.values_old, self.values_new) - - def populateForm(self, values): - """ Populate form with given values - """ - for index,value in enumerate(values): - if value is not None: self.lnedts[index].setText(str(value)) - self.values_old[self.data[index]["NAME"]] = value - - def executeOption(self, button): - """ Perform validation and close the dialog - """ - if self.btnbx_options.standardButton(button) == QDialogButtonBox.Save: - values_new = {} - # check required fields - valid = True - for lnedt in self.lnedts: - if bool(lnedt.property("REQUIRED").toBool()): - if str(lnedt.text()).strip() is "": - lnedt.setStyleSheet(self.colours["REQUIRED"]) - valid = False - else: lnedt.setStyleSheet("") - if not valid: - QMessageBox.information(self, "Empty Required Fields", "Please ensure that all required fields are completed.") - return - # check correct field types - valid = True - for index,lnedt in enumerate(self.lnedts): - try: - if str(lnedt.text()).strip() is not "": - cast = self.data[index]["TYPE"] - tmp = cast(str(lnedt.text())) - values_new[self.data[index]["NAME"]] = tmp - lnedt.setStyleSheet("") - else: - values_new[self.data[index]["NAME"]] = None - except Exception as e: - lnedt.setStyleSheet(self.colours["TYPE"]) - valid = False - if not valid: - QMessageBox.information(self, "Invalid Field Types", "Please ensure that fields are completed with valid types.") - return - # check unique fields - valid = True - for index,lnedt in enumerate(self.lnedts): - if str(lnedt.text()).strip() is "": continue - if bool(lnedt.property("UNIQUE").toBool()): - if self.data[index]["NAME"] in self.values_old.keys() and values_new[self.data[index]["NAME"]] == self.values_old[self.data[index]["NAME"]]: - lnedt.setStyleSheet("") - elif bool(int(self.db.query(self.query["UNIQUE"] %(self.data[index]["NAME"], "%s"), (values_new[self.data[index]["NAME"]],))[0][0])): - lnedt.setStyleSheet(self.colours["UNIQUE"]) - valid = False - else: lnedt.setStyleSheet("") - if not valid: - QMessageBox.information(self, "Fields Not Unique", "Please ensure that fields are given unique values.") - return - # save values - self.values_new = values_new - # accept dialog - self.accept() - else: - # reject dialog - self.reject() - - def setupUi(self, data): - """ Initialize ui - """ - # define ui widgets - self.setObjectName(_fromUtf8("dlg_FormBeacon")) - self.setCursor(QCursor(Qt.ArrowCursor)) - self.setModal(True) - self.gridLayout = QGridLayout(self) - self.gridLayout.setSizeConstraint(QLayout.SetFixedSize) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.verticalLayout = QVBoxLayout() - self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) - self.formLayout = QFormLayout() - self.formLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) - self.formLayout.setObjectName(_fromUtf8("formLayout")) - self.data = data - self.lbls = [] - self.lnedts = [] - # define form fields dynamically from the database schema - for index,field in enumerate(self.data): - lbl = QLabel(self) - lbl.setObjectName(_fromUtf8("lbl_%s" %(field["NAME"],))) - self.formLayout.setWidget(index, QFormLayout.LabelRole, lbl) - self.lbls.append(lbl) - lnedt = QLineEdit(self) - lnedt.setProperty("REQUIRED", field["REQUIRED"]) - lnedt.setProperty("UNIQUE", field["UNIQUE"]) - lnedt.setObjectName(_fromUtf8("lnedt_%s" %(field["NAME"],))) - self.formLayout.setWidget(index, QFormLayout.FieldRole, lnedt) - self.lnedts.append(lnedt) - lbl.setText(QApplication.translate("dlg_FormBeacon", ("*" if bool(self.lnedts[index].property("REQUIRED").toBool()) else "") + field["NAME"].title(), None, QApplication.UnicodeUTF8)) - lnedt.setProperty("TYPE", QApplication.translate("dlg_FormBeacon", str(field["TYPE"]), None, QApplication.UnicodeUTF8)) - self.verticalLayout.addLayout(self.formLayout) - self.line_1 = QFrame(self) - self.line_1.setFrameShape(QFrame.HLine) - self.line_1.setFrameShadow(QFrame.Sunken) - self.line_1.setObjectName(_fromUtf8("line_1")) - self.verticalLayout.addWidget(self.line_1) - self.label = QLabel(self) - self.label.setObjectName(_fromUtf8("label")) - self.verticalLayout.addWidget(self.label) - self.line_2 = QFrame(self) - self.line_2.setFrameShape(QFrame.HLine) - self.line_2.setFrameShadow(QFrame.Sunken) - self.line_2.setObjectName(_fromUtf8("line_2")) - self.verticalLayout.addWidget(self.line_2) - self.btnbx_options = QDialogButtonBox(self) - self.btnbx_options.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Save) - self.btnbx_options.setObjectName(_fromUtf8("btnbx_options")) - self.btnbx_options.setCursor(QCursor(Qt.PointingHandCursor)) - self.verticalLayout.addWidget(self.btnbx_options) - self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) - # translate ui widgets' text - self.setWindowTitle(QApplication.translate("dlg_FormBeacon", "Beacon Form", None, QApplication.UnicodeUTF8)) - self.label.setText(QApplication.translate("dlg_FormBeacon", "

*Required Field

", None, QApplication.UnicodeUTF8)) - # connect ui widgets - self.btnbx_options.clicked.connect(self.executeOption) - QMetaObject.connectSlotsByName(self) - -class dlg_FormDatabase(QDialog): - """ This dialog enables the user to define the database connection parameters - """ - - def __init__(self, parent = None): - # initialize QDialog class - super(dlg_FormDatabase, self).__init__(parent, Qt.WindowStaysOnTopHint) - # initialize ui - self.setupUi() - # initialize instance variables - self.save = False - self.db = None - self.params = {} - self.colours = { - "EMPTY":"background-color: rgba(255, 107, 107, 150);", - } - - def getReturn(self): - """ Return intended variable(s) after the dialog has been accepted - """ - return (self.save, self.db, self.params) - - def executeOption(self, button): - """ Perform validation and close the dialog - """ - if self.btnbx_options.standardButton(button) == QDialogButtonBox.Ok: - # validate fields - params = {} - valid = True - for lnedt in self.findChildren(QLineEdit): - if str(lnedt.text()).strip() is "": - lnedt.setStyleSheet(self.colours["EMPTY"]) - valid = False - else: - lnedt.setStyleSheet("") - params[str(lnedt.property("KEY").toString())] = str(lnedt.text()) - if not valid: - QMessageBox.information(self, "Empty Database Fields", "Please ensure that all database fields are completed.") - return - # test connection - db = None - try: - import database - db = database.manager(params) - except Exception: - QMessageBox.information(self, "Invalid Database Settings", "Please ensure that the supplied database settings are correct.") - return - # save db - self.db = db - # save parameters - self.params = params - # accept dialog - self.accept() - else: - # reject dialog - self.reject() - - def saveConnection(self, state): - """ Select option to save database settings - """ - self.save = bool(state) - - def setupUi(self): - """ Initialize ui - """ - # define ui widgets - fields = ["HOST","PORT","NAME","USER","PASSWORD"] - self.setObjectName(_fromUtf8("dlg_FormDatabase")) - self.setCursor(QCursor(Qt.ArrowCursor)) - self.setModal(True) - self.gridLayout = QGridLayout(self) - self.gridLayout.setSizeConstraint(QLayout.SetFixedSize) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.verticalLayout = QVBoxLayout() - self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) - self.formLayout = QFormLayout() - self.formLayout.setObjectName(_fromUtf8("formLayout")) - self.lbls = [] - self.lnedts = [] - # define form fields dynamically from above list - for index, field in enumerate(fields): - lbl = QLabel(self) - lbl.setObjectName(_fromUtf8("lbl_%s" %(field.lower(),))) - lbl.setText(QApplication.translate("dlg_FormDatabase", field.title(), None, QApplication.UnicodeUTF8)) - self.formLayout.setWidget(index, QFormLayout.LabelRole, lbl) - lnedt = QLineEdit(self) - lnedt.setObjectName(_fromUtf8("lnedt_%s" %(field.lower(),))) - lnedt.setProperty("KEY", field) - if field == "PASSWORD": lnedt.setEchoMode(QLineEdit.Password) - self.formLayout.setWidget(index, QFormLayout.FieldRole, lnedt) - self.lbls.append(lbl) - self.lnedts.append(lnedt) - self.verticalLayout.addLayout(self.formLayout) - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.chkbx_save = QCheckBox(self) - self.chkbx_save.setObjectName(_fromUtf8("chkbx_save")) - self.chkbx_save.setCursor(QCursor(Qt.PointingHandCursor)) - self.horizontalLayout.addWidget(self.chkbx_save) - self.verticalLayout.addLayout(self.horizontalLayout) - self.btnbx_options = QDialogButtonBox(self) - self.btnbx_options.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) - self.btnbx_options.setObjectName(_fromUtf8("btnbx_options")) - self.btnbx_options.setCursor(QCursor(Qt.PointingHandCursor)) - self.verticalLayout.addWidget(self.btnbx_options) - self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) - # translate ui widgets' text - self.setWindowTitle(QApplication.translate("dlg_FormDatabase", "Database Settings", None, QApplication.UnicodeUTF8)) - self.chkbx_save.setText(QApplication.translate("dlg_FormDatabase", "Save Connection", None, QApplication.UnicodeUTF8)) - # connect ui widgets - self.chkbx_save.stateChanged.connect(self.saveConnection) - self.btnbx_options.clicked.connect(self.executeOption) - QMetaObject.connectSlotsByName(self) class dlg_FormBearDist(QDialog): """ This dialog enables the user to define bearings and distances """ - def __init__(self, db, beardistSql, beaconSql, beaconSchema, parent = None): + def __init__(self, db, SQL_BEARDIST, SQL_BEACONS, requiredLayers, parent = None): # initialize QDialog class super(dlg_FormBearDist, self).__init__(parent, Qt.WindowStaysOnTopHint) # initialize ui self.setupUi() - # initialize instance variables self.db = db - self.beardistSql = beardistSql - self.beaconSql = beaconSql - self.beaconSchema = beaconSchema + self.SQL_BEARDIST = SQL_BEARDIST + self.SQL_BEACONS = SQL_BEACONS + self.layers = requiredLayers self.auto = { - "SURVEYPLAN":self.db.query(self.beardistSql["AUTO_SURVEYPLAN"])[0][0], - "REFERENCEBEACON":self.db.query(self.beardistSql["AUTO_REFERENCEBEACON"])[0][0], + "SURVEYPLAN":self.db.query( + SQL_BEARDIST["AUTO_SURVEYPLAN"] + )[0][0], + "REFERENCEBEACON":self.db.query( + SQL_BEARDIST["AUTO_REFERENCEBEACON"] + )[0][0], "FROMBEACON":[] } self.surveyPlan = None @@ -824,6 +929,9 @@ def setCurrentItem(self, index, clear=False, enabled=False): def initItemSurveyPlan(self, forward=True): """ Initialize form elements for the survey plan item """ + if not forward: + if not self.confirmBack(): + return # update autocompletion model = QStringListModel() model.setStringList(self.auto["SURVEYPLAN"]) @@ -842,11 +950,16 @@ def checkItemSurveyPlan(self, forward): # check direction if forward: # check that a server plan number was specified - if str(self.lnedt_plan.text()).strip() is "": - QMessageBox.information(self, "Empty Survey Plan Number", "Please enter a surver plan number.") + surveyPlan = str(self.lnedt_plan.text()).strip() + if surveyPlan is "": + QMessageBox.information( + self, + "Empty Survey Plan Number", + "Please enter a surver plan number." + ) return # set survey plan number - self.surveyPlan = str(self.lnedt_plan.text()) + self.surveyPlan = surveyPlan # display next toolbox item self.initItemReferenceBeacon() else: pass @@ -854,6 +967,9 @@ def checkItemSurveyPlan(self, forward): def initItemReferenceBeacon(self, forward=True): """ Initialize form elements for the reference beacon item """ + if not forward: + if not self.confirmBack(): + return # update autocompletion model = QStringListModel() model.setStringList(self.auto["REFERENCEBEACON"]) @@ -869,7 +985,10 @@ def initItemReferenceBeacon(self, forward=True): if self.surveyPlan in self.auto["SURVEYPLAN"]: # update item contents self.lnedt_ref.setEnabled(False) - self.lnedt_ref.setText(str(self.db.query(self.beardistSql["EXIST_REFERENCEBEACON"], (self.surveyPlan,))[0][0])) + self.lnedt_ref.setText(str(self.db.query( + self.SQL_BEARDIST["EXIST_REFERENCEBEACON"], + (self.surveyPlan,) + )[0][0])) else: # update item contents self.lnedt_ref.setEnabled(True) @@ -883,10 +1002,14 @@ def checkItemReferenceBeacon(self, forward): # check direction if forward: # check that a reference beacon was specified - if str(self.lnedt_ref.text()).strip() is "": - QMessageBox.information(self, "Empty Reference Beacon", "Please enter a reference beacon.") + referenceBeacon = str(self.lnedt_ref.text()).strip() + if referenceBeacon is "": + QMessageBox.information( + self, + "Empty Reference Beacon", + "Please enter a reference beacon." + ) return - referenceBeacon = str(self.lnedt_ref.text()) # check if reference beacon exists if referenceBeacon in self.auto["REFERENCEBEACON"]: # set reference beacon @@ -897,15 +1020,31 @@ def checkItemReferenceBeacon(self, forward): # disable self self.setEnabled(False) # present beacon form - column_index = self.db.query(self.beardistSql["INDEX_REFERENCEBEACON"])[0][0] - frm = dlg_FormBeacon(self.db, self.beaconSchema, self.beaconSql, parent = self) + column_index = self.db.query( + self.SQL_BEARDIST["INDEX_REFERENCEBEACON"] + )[0][0] + # get fields + fields = self.db.getSchema( + self.layers[0].table, [ + self.layers[0].geometry_column, + self.layers[0].primary_key + ]) + # display form + frm = dlg_FormBeacon( + self.db, + self.SQL_BEACONS["UNIQUE"], + fields, + parent = self + ) frm.lnedts[column_index].setText(referenceBeacon) frm.lnedts[column_index].setEnabled(False) frm.show() frm_ret = frm.exec_() if bool(frm_ret): - # save new beacon - self.db.query(self.beaconSql["INSERT"].format(fields = ", ".join([f["NAME"] for f in self.beaconSchema]), values = ", ".join(["%s" for f in self.beaconSchema])), [frm.getReturn()[1][f["NAME"]] for f in self.beaconSchema])[0][0] + # add beacon to database + values_old, values_new = frm.getValues() + self.db.query( + self.SQL_BEACONS["INSERT"].format(fields = ", ".join(sorted(values_new.keys())), values = ", ".join(["%s" for k in values_new.keys()])), [values_new[k] for k in sorted(values_new.keys())]) # set reference beacon self.referenceBeacon = referenceBeacon self.auto["REFERENCEBEACON"].append(referenceBeacon) @@ -936,7 +1075,10 @@ def initItemBearDistChain(self, forward=True): # check if survey plan number is predefined if self.surveyPlan in self.auto["SURVEYPLAN"]: # get defined bearings and distances - records = self.db.query(self.beardistSql["EXIST_BEARDISTCHAINS"], (self.surveyPlan,)) + records = self.db.query( + self.SQL_BEARDIST["EXIST_BEARDISTCHAINS"], + (self.surveyPlan,) + ) if records not in [None, []]: for oid,link in enumerate(records): self.beardistChain.append([list(link), "NULL", oid]) @@ -953,13 +1095,25 @@ def checkItemBearDistChain(self, forward): # check direction if forward: if not bool(self.surveyPlan): - QMessageBox.information(self, "No Survey Plan", "Please specify a survey plan number") + QMessageBox.information( + self, + "No Survey Plan", + "Please specify a survey plan number" + ) return if not bool(self.referenceBeacon): - QMessageBox.information(self, "No Reference Beacon", "Please specify a reference beacon") + QMessageBox.information( + self, + "No Reference Beacon", + "Please specify a reference beacon" + ) return if not bool(self.beardistChain): - QMessageBox.information(self, "No Bearing and Distance Chain", "Please capture bearings and distances") + QMessageBox.information( + self, + "No Bearing and Distance Chain", + "Please capture bearings and distances" + ) return self.accept() else: @@ -1007,17 +1161,29 @@ def getSelectedIndex(self, action): items = self.lst_chain.selectedItems() # check list is non-empty if len(items) == 0: - QMessageBox.information(self, "No Link Selected", "Please select a link to edit") + QMessageBox.information( + self, + "No Link Selected", + "Please select a link to edit" + ) return None # check list does not contain more than one item if len(items) > 1: - QMessageBox.information(self, "Too Many Links Selected", "Please select only one link to edit") + QMessageBox.information( + self, + "Too Many Links Selected", + "Please select only one link to edit" + ) return None # get item index index = self.lst_chain.row(items[0]) # check that index is of an end link if not bool(self.isEndLink(index)): - if QMessageBox.question(self, "Non End Link Selected", "The link you selected is not at the end of a chain. Are you sure you want to %s this link?" %(action.lower(),), QMessageBox.Yes, QMessageBox.No) == QMessageBox.No: + if QMessageBox.question( + self, + "Non End Link Selected", + "The link you selected is not at the end of a chain. If you {action} this link it will {action} all links that depend on this one. Are you sure you want to {action} this link?".format(action=action.lower()), + QMessageBox.Yes, QMessageBox.No) == QMessageBox.No: return None # return index return index @@ -1031,7 +1197,7 @@ def updateBearDistChainDependants(self): # populate dependants for link in self.beardistChain: #QMessageBox.information(self,QString(','.join(link[0][:4]))) - self.lst_chain.addItem(QString(self.beardistStr %tuple(link[0][:4]))) + self.lst_chain.addItem(self.beardistStr %tuple(link[0][:4])) self.auto["FROMBEACON"].append(link[0][3]) self.auto["FROMBEACON"].sort() @@ -1039,15 +1205,20 @@ def addLink(self): """ Add a link to the beardist chain """ while True: - dlg = dlg_FormBearDistLink(self.auto["FROMBEACON"], parent = self) + dlg = dlg_FormBearDistLink( + self.db, + self.auto["FROMBEACON"], + self.SQL_BEACONS["UNIQUE"], + parent = self + ) dlg.show() dlg_ret = dlg.exec_() if bool(dlg_ret): - values = dlg.getReturn() + values = dlg.getValues() self.beardistChain.append([values, "INSERT", None]) self.updateBearDistChainDependants() else: break - if len(self.beardistChain) == 1: + if len(self.beardistChain) >= 1: self.pshbtn_chain_finish.setEnabled(True) self.pshbtn_chain_edt.setEnabled(True) self.pshbtn_chain_del.setEnabled(True) @@ -1060,12 +1231,17 @@ def editLink(self): # check selection if index is not None: # display dialog - dlg = dlg_FormBearDistLink(self.auto["FROMBEACON"], values = self.beardistChain[index][0], parent = self) + dlg = dlg_FormBearDistLink( + self.db, + self.auto["FROMBEACON"], + self.SQL_BEACONS["UNIQUE"], + values = self.beardistChain[index][0], + parent = self) if self.isLastAnchorLink(index): dlg.lnedts[2].setEnabled(False) dlg.show() dlg_ret = dlg.exec_() if bool(dlg_ret): - values = dlg.getReturn() + values = dlg.getValues() # check if anything was changed if values == self.beardistChain[index][0]: return # check if reference beacon can be found @@ -1095,7 +1271,11 @@ def deleteLink(self): if index is not None: # prevent last link to use reference beacon from being deleted if self.isLastAnchorLink(index): - QMessageBox.warning(self, "Last Link To Reference Beacon", "Cannot remove last link to reference beacon") + QMessageBox.warning( + self, + "Last Link To Reference Beacon", + "Cannot remove last link to reference beacon" + ) return # recursively delete dependant links self.deleteLinkDependants(self.beardistChain[index][0][3]) @@ -1250,20 +1430,79 @@ def setupUi(self): self.btnbx_options.setObjectName(_fromUtf8("btnbx_options")) self.grdlyt_dlg.addWidget(self.btnbx_options, 1, 0, 1, 1) # translate ui widgets' text - self.setWindowTitle(QApplication.translate("dlg_FormBearDist", "Bearings and Distances Form", None, QApplication.UnicodeUTF8)) - self.lbl_plan.setText(QApplication.translate("dlg_FormBearDist", "Survey Plan", None, QApplication.UnicodeUTF8)) - self.pshbtn_plan_next.setText(QApplication.translate("dlg_FormBearDist", "Next", None, QApplication.UnicodeUTF8)) - self.tlbx.setItemText(self.tlbx.indexOf(self.itm_plan), QApplication.translate("dlg_FormBearDist", "Step 1: Define Survey Plan", None, QApplication.UnicodeUTF8)) - self.lbl_ref.setText(QApplication.translate("dlg_FormBearDist", "Reference Beacon", None, QApplication.UnicodeUTF8)) - self.pshbtn_ref_back.setText(QApplication.translate("dlg_FormBearDist", "Back", None, QApplication.UnicodeUTF8)) - self.pshbtn_ref_next.setText(QApplication.translate("dlg_FormBearDist", "Next", None, QApplication.UnicodeUTF8)) - self.tlbx.setItemText(self.tlbx.indexOf(self.itm_ref), QApplication.translate("dlg_FormBearDist", "Step 2: Define Reference Beacon", None, QApplication.UnicodeUTF8)) - self.pshbtn_chain_add.setText(QApplication.translate("dlg_FormBearDist", "Add Link", None, QApplication.UnicodeUTF8)) - self.pshbtn_chain_edt.setText(QApplication.translate("dlg_FormBearDist", "Edit Link", None, QApplication.UnicodeUTF8)) - self.pshbtn_chain_del.setText(QApplication.translate("dlg_FormBearDist", "Delete Link", None, QApplication.UnicodeUTF8)) - self.pshbtn_chain_back.setText(QApplication.translate("dlg_FormBearDist", "Back", None, QApplication.UnicodeUTF8)) - self.pshbtn_chain_finish.setText(QApplication.translate("dlg_FormBearDist", "Finish", None, QApplication.UnicodeUTF8)) - self.tlbx.setItemText(self.tlbx.indexOf(self.itm_chain), QApplication.translate("dlg_FormBearDist", "Step 3: Define Bearings and Distances Chain", None, QApplication.UnicodeUTF8)) + self.setWindowTitle(QApplication.translate( + "dlg_FormBearDist", + "Bearings and Distances Form", + None, + QApplication.UnicodeUTF8)) + self.lbl_plan.setText(QApplication.translate( + "dlg_FormBearDist", + "Survey Plan", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_plan_next.setText(QApplication.translate( + "dlg_FormBearDist", + "Next", + None, + QApplication.UnicodeUTF8)) + self.tlbx.setItemText(self.tlbx.indexOf(self.itm_plan), + QApplication.translate( + "dlg_FormBearDist", + "Step 1: Define Survey Plan", + None, + QApplication.UnicodeUTF8)) + self.lbl_ref.setText(QApplication.translate( + "dlg_FormBearDist", + "Reference Beacon", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_ref_back.setText(QApplication.translate( + "dlg_FormBearDist", + "Back", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_ref_next.setText(QApplication.translate( + "dlg_FormBearDist", + "Next", + None, + QApplication.UnicodeUTF8)) + self.tlbx.setItemText(self.tlbx.indexOf(self.itm_ref), + QApplication.translate( + "dlg_FormBearDist", + "Step 2: Define Reference Beacon", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_chain_add.setText(QApplication.translate( + "dlg_FormBearDist", + "Add Link", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_chain_edt.setText(QApplication.translate( + "dlg_FormBearDist", + "Edit Link", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_chain_del.setText(QApplication.translate( + "dlg_FormBearDist", + "Delete Link", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_chain_back.setText(QApplication.translate( + "dlg_FormBearDist", + "Back", + None, + QApplication.UnicodeUTF8)) + self.pshbtn_chain_finish.setText(QApplication.translate( + "dlg_FormBearDist", + "Finish", + None, + QApplication.UnicodeUTF8)) + self.tlbx.setItemText(self.tlbx.indexOf(self.itm_chain), + QApplication.translate( + "dlg_FormBearDist", + "Step 3: Define Bearings and Distances Chain", + None, + QApplication.UnicodeUTF8)) # connect ui widgets self.btnbx_options.accepted.connect(self.accept) self.btnbx_options.rejected.connect(self.reject) @@ -1277,11 +1516,19 @@ def setupUi(self): self.pshbtn_chain_del.clicked.connect(self.deleteLink) QMetaObject.connectSlotsByName(self) + def confirmBack(self): + return QMessageBox.question( + self, + "Going Back", + "Any changes made will be lost. Are your sure that you want to go back?", + QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes + + class dlg_FormBearDistLink(QDialog): """ This dialog enables the user to add a bearing and distance link """ - def __init__(self, fromBeacons, values=[], parent = None): + def __init__(self, db, fromBeacons, query, values=[], parent = None): # initialize QDialog class super(dlg_FormBearDistLink, self).__init__(parent, Qt.WindowStaysOnTopHint) # initialize ui @@ -1289,11 +1536,14 @@ def __init__(self, fromBeacons, values=[], parent = None): # initialize instance variables self.values_old = values self.values_new = [] + self.db = db + self.query = query self.fromBeacons = fromBeacons self.colours = { "EMPTY":"background-color: rgba(255, 107, 107, 150);", "TYPE":"background-color: rgba(107, 107, 255, 150);", - "BEACON":"background-color: rgba(107, 255, 107, 150);" + "BEACON":"background-color: rgba(107, 255, 107, 150);", + "UNIQUE":"background-color: rgba(107, 255, 107, 150);" } # populate form if values are given if bool(values): @@ -1306,9 +1556,7 @@ def populateForm(self, values): if values[index] is not None: lnedt.setText(str(values[index])) - def getReturn(self): - """ Return intended variable(s) after the dialog has been accepted - """ + def getValues(self): return self.values_new def executeOption(self, button): @@ -1319,20 +1567,24 @@ def executeOption(self, button): # check required fields valid = True for index,lnedt in enumerate(self.lnedts): - if not self.fields[index]["NULLABLE"]: + if self.fields[index].required: if str(lnedt.text()).strip() is "": lnedt.setStyleSheet(self.colours["EMPTY"]) valid = False else: lnedt.setStyleSheet("") if not valid: - QMessageBox.information(self, "Empty Fields", "Please ensure that all fields are completed.") + QMessageBox.information( + self, + "Empty Required Fields", + "Please ensure that all required fields are completed." + ) return # check correct field types valid = True for index,lnedt in enumerate(self.lnedts): try: - cast = self.fields[index]["TYPE"] - txt = str(lnedt.text()) + cast = self.fields[index].type + txt = str(lnedt.text()).strip() if txt is "": tmp = None else: tmp = cast(txt) values_new.append(tmp) @@ -1341,21 +1593,51 @@ def executeOption(self, button): lnedt.setStyleSheet(self.colours["TYPE"]) valid = False if not valid: - QMessageBox.information(self, "Invalid Field Types", "Please ensure that fields are completed with valid types.") + QMessageBox.information( + self, + "Invalid Field Types", + "Please ensure that fields are completed with valid types." + ) return # check valid from beacon field valid = True for index,lnedt in enumerate(self.lnedts): - if self.fields[index]["NAME"].lower() == "from": + if self.fields[index].name.lower() == "from": if str(lnedt.text()) not in self.fromBeacons: lnedt.setStyleSheet(self.colours["BEACON"]) valid = False - if not bool(self.values_old) and self.fields[index]["NAME"].lower() == "to": - if str(lnedt.text()) in self.fromBeacons: - lnedt.setStyleSheet(self.colours["BEACON"]) + if not valid: + QMessageBox.information( + self, + "Invalid Reference", + "Please ensure that specified beacons are valid." + ) + return + # check valid to beacon field + valid = True + for index,lnedt in enumerate(self.lnedts): + if self.fields[index].name.lower() == "to": + if bool(self.values_old): + if str(lnedt.text()) not in self.values_old: + if str(lnedt.text()) in self.fromBeacons: + lnedt.setStyleSheet(self.colours["UNIQUE"]) + valid = False + break + elif not bool(self.values_old): + if str(lnedt.text()) in self.fromBeacons: + lnedt.setStyleSheet(self.colours["UNIQUE"]) + valid = False + break + if bool(int(self.db.query(self.query %('beacon', "%s"), (str(lnedt.text()),))[0][0])): + lnedt.setStyleSheet(self.colours["UNIQUE"]) valid = False + break if not valid: - QMessageBox.information(self, "Invalid Reference", "Please ensure that specified beacons is valid.") + QMessageBox.information( + self, + "Invalid Reference", + "Please ensure that the new beacon is unique." + ) return # save values self.values_new = values_new @@ -1376,22 +1658,27 @@ def setupUi(self, fromBeacons): self.lbls = [] self.lnedts = [] self.fields = [ - {"NAME":"Bearing", "TYPE":float, "NULLABLE":False}, - {"NAME":"Distance", "TYPE":float, "NULLABLE":False}, - {"NAME":"From", "TYPE":str, "NULLABLE":False}, - {"NAME":"To", "TYPE":str, "NULLABLE":False}, - {"NAME":"Location", "TYPE":str, "NULLABLE":True}, - {"NAME":"Surveyor", "TYPE":str, "NULLABLE":True} + Field("Bearing", float, True, False), + Field("Distance", float, True, False), + Field("From", str, True, False), + Field("To", str, True, False), + Field("Location", str, False, False), + Field("Surveyor", str, False, False) ] - for index, field in enumerate(self.fields): + for index, f in enumerate(self.fields): lbl = QLabel(self) self.formLayout.setWidget(index, QFormLayout.LabelRole, lbl) - lbl.setText(QApplication.translate("dlg_FormBearDistEntry", field["NAME"].title(), None, QApplication.UnicodeUTF8)) + lbl.setText(QApplication.translate( + "dlg_FormBearDistEntry", + ("*" if f.required else "") + f.name.title(), + None, + QApplication.UnicodeUTF8 + )) self.lbls.append(lbl) lnedt = QLineEdit(self) self.formLayout.setWidget(index, QFormLayout.FieldRole, lnedt) self.lnedts.append(lnedt) - if field["NAME"].lower() == "from": + if f.name.lower() == "from": model = QStringListModel() model.setStringList(fromBeacons) completer = QCompleter() @@ -1404,7 +1691,12 @@ def setupUi(self, fromBeacons): self.btnbx_options.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Save) self.gridLayout.addWidget(self.btnbx_options, 1, 0, 1, 1) # translate ui widgets' text - self.setWindowTitle(QApplication.translate("dlg_FormBearDistEntry", "Link Form", None, QApplication.UnicodeUTF8)) + self.setWindowTitle(QApplication.translate( + "dlg_FormBearDistEntry", + "Link Form", + None, + QApplication.UnicodeUTF8 + )) # connect ui widgets self.btnbx_options.clicked.connect(self.executeOption) QMetaObject.connectSlotsByName(self) diff --git a/README.html b/README.html deleted file mode 100644 index 2d03414..0000000 --- a/README.html +++ /dev/null @@ -1,32 +0,0 @@ - - -

Plugin Builder Results

-
-Your plugin sml_surveyor was created in:
-  /home/robert/Development/sml_surveyor -

-Your QGIS plugin directory is located at:
-  /home/robert/.qgis//python/plugins -

-What's Next -

    -
  1. Copy the entire directory containing your new plugin to the QGIS plugin directory -
  2. Compile the ui file using pyuic4 -
  3. Compile the resources file using pyrcc4 -
  4. Test the plugin by enabling it in the QGIS plugin manager -
  5. Customize it by editing the implementation file sml_surveyor.py -
  6. Create your own custom icon, replacing the default icon.png -
  7. Modify your user interface by opening sml_surveyor.ui in Qt Designer (don't forget to compile it with pyuic4 after changing it) -
  8. You can use the Makefile to compile your Ui and resource files when you make changes. This requires GNU make (gmake) - -
-
-

-For more information, see the PyQGIS Developer Cookbook at: -http://www.qgis.org/pyqgis-cookbook/index.html. -

-
-GeoApt LLC -©2012 GeoApt LLC - geoapt.com - - diff --git a/README.md b/README.md new file mode 100644 index 0000000..a225760 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# CoGo plugin for QGIS + +First developed by Afrispatial cc in 2012 + +Sponsored by: SpatialMatrix, Lagos for Ogun State Government, Nigeria. + +Original authors: Robert Moerman, Admire Nyakudya, Gavin Fleming + +Maintained by Kartoza (Pty) Ltd + +Copyright Kartoza 2017 + +2017 update for Niger State, Nigeria and potentially for general release. + +Licence: + +# Description + +This plugin was developed to enable efficient bulk capture of coordinate geometry off survey diagrams. + +It caters for addition, modification and deletion of cadastral properties. + +## Bearings and distances + +Where bearing and distance data are available, they can and should be used to defined property boundaries. An initial beacon coordinate is captured and then bearings and distances are captured to define all other beacons in a chain. + +Then properties are constructed by grouping beacons in a specific order to define a polygon. + +## Coordinates + +Where bearings and distances are not available, coordinates can be used instead to define beacons. + +## Dependencies + +This plugin depends on a PostGIS database with a predefined schema including tables, materialised views and triggers. + +# User manual + +http://goo.gl/CY9TYn + + +# What's Next +(Robert's old note - needs to be updated) + +- Copy the entire directory containing your new plugin to the QGIS plugin directory +- Compile the ui file using pyuic4 +- Compile the resources file using pyrcc4 +- Test the plugin by enabling it in the QGIS plugin manager +- Customize it by editing the implementation file `sml_surveyor.py` +- Create your own custom icon, replacing the default `icon.png` +- Modify your user interface by opening `sml_surveyor.ui` in Qt Designer (don't forget to compile it with pyuic4 after changing it) +- You can use the `Makefile` to compile your Ui and resource files when you make changes. This requires GNU make (gmake) + + diff --git a/README.txt b/README.txt deleted file mode 100644 index 9a83c38..0000000 --- a/README.txt +++ /dev/null @@ -1,34 +0,0 @@ -Plugin Builder Results - -Your plugin sml_surveyor was created in: - /home/robert/Development/sml_surveyor - -Your QGIS plugin directory is located at: - /home/robert/.qgis//python/plugins - -What's Next: - - * Copy the entire directory containing your new plugin to the QGIS plugin - directory - - * Compile the ui file using pyuic4 - - * Compile the resources file using pyrcc4 - - * Test the plugin by enabling it in the QGIS plugin manager - - * Customize it by editing the implementation file: - sml_surveyor.py - - * Create your own custom icon, replacing the default icon.png - - * Modify your user interface by opening sml_surveyor.ui - in Qt Designer (don't forget to compile it with pyuic4 after changing it) - - * You can use the Makefile to compile your Ui and resource files when - you make changes. This requires GNU make (gmake) - -For more information, see the PyQGIS Developer Cookbook at: -http://www.qgis.org/pyqgis-cookbook/index.html - -(C) 2012 GeoApt LLC - geoapt.com diff --git a/__init__.py b/__init__.py index befd9ea..1e70051 100644 --- a/__init__.py +++ b/__init__.py @@ -25,21 +25,17 @@ def name(): return "SML Surveyor" - def description(): return "SML Surveyor Plugin" - def version(): return "Version 0.1" - def icon(): return "icon.png" - def qgisMinimumVersion(): - return "1.8" + return "2.0" def author(): return "AfriSpatial" @@ -49,5 +45,5 @@ def email(): def classFactory(iface): # load sml_surveyor class from file sml_surveyor - from sml_surveyor import sml_surveyor + from plugin import sml_surveyor return sml_surveyor(iface) diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..366612f --- /dev/null +++ b/constants.py @@ -0,0 +1,54 @@ +SQL_BEACONS = { + "SELECT":"SELECT beacon FROM beacons WHERE gid = %s;", + "UNIQUE":"SELECT COUNT(*) FROM beacons WHERE %s = %s;", + "EDIT":"SELECT {fields} FROM beacons WHERE gid = %s;", + "DELETE":"DELETE FROM beacons WHERE gid = %s;", + "INSERT":"INSERT INTO beacons({fields}) VALUES ({values}) RETURNING gid;", + "UPDATE":"UPDATE beacons SET {set} WHERE {where};", + "BEARDIST":"SELECT CASE WHEN count(*) = 0 THEN FALSE ELSE TRUE END \ + FROM beardist WHERE beacon_to = (SELECT beacon FROM \ + beacons WHERE gid = %s);", +} + +SQL_PARCELS = { + "SELECT":"SELECT parcel_id FROM parcel_lookup WHERE parcel_id = %s;", + "EDIT":"SELECT l.parcel_id, array_agg(s.gid ORDER BY s.sequence) \ + FROM ( SELECT b.gid, d.parcel_id, d.sequence FROM beacons b \ + INNER JOIN parcel_def d ON d.beacon = b.beacon) s JOIN \ + parcel_lookup l ON s.parcel_id = l.parcel_id WHERE \ + l.parcel_id = %s GROUP BY l.parcel_id;", + "AUTOCOMPLETE":"SELECT parcel_id FROM parcel_lookup WHERE available;", + "UNIQUE":"SELECT COUNT(*) FROM parcel_lookup WHERE parcel_id = %s;", + "AVAILABLE":"SELECT available FROM parcel_lookup WHERE parcel_id = %s;", + "INSERT":"INSERT INTO parcel_def(parcel_id, beacon, sequence) \ + VALUES (%s, %s, %s);", + "INSERT_GENERAL": "INSERT INTO parcel_def(parcel_id, beacon, sequence) \ + VALUES %s;", + "DELETE":"DELETE FROM parcel_def WHERE parcel_id = %s;", +} + +SQL_BEARDIST = { + "AUTO_SURVEYPLAN":"SELECT array_agg(plan_no) FROM survey;", + "AUTO_REFERENCEBEACON":"SELECT array_agg(beacon) FROM beacons \ + WHERE beacon NOT IN (SELECT beacon_to FROM beardist WHERE \ + beacon_to NOT IN (SELECT ref_beacon FROM survey));", + "EXIST_REFERENCEBEACON":"SELECT ref_beacon FROM survey where \ + plan_no = %s;", + "EXIST_BEARDISTCHAINS":"SELECT bd.bearing, bd.distance, \ + bd.beacon_from, bd.beacon_to, b.location, b.name FROM beardist \ + bd INNER JOIN beacons b ON bd.beacon_to = b.beacon WHERE \ + bd.plan_no = %s;", + "INDEX_REFERENCEBEACON":"SELECT i.column_index::integer FROM (SELECT \ + row_number() over(ORDER BY c.ordinal_position) -1 as \ + column_index, c.column_name FROM information_schema.columns c \ + WHERE c.table_name = 'beacons' AND c.column_name NOT IN ('geom', \ + 'gid') ORDER BY c.ordinal_position) as i WHERE i.column_name = \ + 'beacon';", + "IS_SURVEYPLAN":"SELECT CASE WHEN COUNT(*) <> 0 THEN TRUE ELSE FALSE \ + END FROM survey WHERE plan_no = %s;", + "INSERT_SURVEYPLAN":"INSERT INTO survey(plan_no, ref_beacon) \ + VALUES(%s, %s);", + "UPDATE_LINK":"SELECT beardistupdate(%s, %s, %s, %s, %s, %s, %s, %s);", + "DELETE_LINK":"DELETE FROM beacons WHERE beacon = %s;", + "INSERT_LINK":"SELECT beardistinsert(%s, %s, %s, %s, %s, %s, %s);" +} diff --git a/database.py b/database.py index 89bf644..d9a0cea 100644 --- a/database.py +++ b/database.py @@ -15,11 +15,20 @@ * * ***************************************************************************/ """ - import psycopg2 -class manager(): - + +class Field: + + def __init__(self, name, type, required, unique): + self.name = name + self.type = type + self.required = required + self.unique = unique + + +class Manager: + def __init__(self, params): # test db settings self.params = params @@ -30,7 +39,7 @@ def connect(self, params): """ Create a backend postgres database connection """ try: - # check if connection object exist + # check if connection object exist if not hasattr(self, 'conn') or self.conn is None: self.conn = psycopg2.connect("host='{HOST}' dbname='{NAME}' user='{USER}' password='{PASSWORD}' port='{PORT}'".format(HOST=params["HOST"], NAME=params["NAME"], USER=params["USER"], PASSWORD= params["PASSWORD"], PORT=params["PORT"])) # check if cursor objet exists @@ -38,17 +47,17 @@ def connect(self, params): self.cursor = self.conn.cursor() except Exception as e: raise Exception('Could not connect to database!\nError raised: {error}.'.format(error = str(e))) - + def disconnect(self): """ Terminate a backend postgres database connection """ try: - # check if a cursor object exists - if hasattr(self, 'cursor') and self.cursor is not None: + # check if a cursor object exists + if hasattr(self, 'cursor') and self.cursor is not None: self.cursor.close() self.cursor = None # check if a connection object exists - if hasattr(self, 'conn') and self.conn is not None: + if hasattr(self, 'conn') and self.conn is not None: self.conn.close() self.conn = None except Exception as e: @@ -60,12 +69,14 @@ def query(self, query, data=None): """ try: self.connect(self.params) - if data is None: self.cursor.execute(query) - else: self.cursor.execute(query, data) + if data is None: + self.cursor.execute(query) + else: + self.cursor.execute(query, data) records = None try: records = self.cursor.fetchall() - except: + except: pass self.conn.commit() self.disconnect() @@ -73,26 +84,37 @@ def query(self, query, data=None): except Exception as e: raise Exception('Backend database query failed!\nError raised: %s.' %(str(e),)) - def queryPreview(self, query, data=None): + def queryPreview(self, query, data=None, multi_data=False): """ Preview query @returns query (str) """ try: self.connect(self.params) sql = "" - if data is None: sql = self.cursor.mogrify(query) - else: sql = self.cursor.mogrify(query, data) + if data is None: + sql = self.cursor.mogrify(query) + else: + if multi_data: + placeholders = ','.join(['%s' for dummy in data]) + query = query % (placeholders) + sql = self.cursor.mogrify(query, data) + else: + sql = self.cursor.mogrify(query, data) self.disconnect() return sql except Exception as e: raise Exception('Backend database mogrification failed!\nError raised: %s.' %(str(e),)) - + def getSchema(self, tbl_name, fld_ignore): - """ Get information abot a specific table + """ Get information abot a specific table @returns [, , ] (list) """ - info = [{"NAME":data[0], "TYPE":self._pythonize_type(data[1]), "REQUIRED":data[2], "UNIQUE":data[3]} for data in reversed(self.query("SELECT c.column_name, c.data_type, CASE WHEN c.is_nullable = 'NO' THEN TRUE ELSE FALSE END AS required, CASE WHEN u.column_name IS NOT NULL THEN TRUE ELSE FALSE END AS unique FROM information_schema.columns c LEFT JOIN (SELECT kcu.column_name, tc.table_name FROM information_schema.table_constraints tc LEFT JOIN information_schema.key_column_usage kcu ON tc.constraint_catalog = kcu.constraint_catalog AND tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name WHERE tc.constraint_type IN ('UNIQUE', 'PRIMARY KEY') AND tc.table_name = '{table}') u ON u.column_name = c.column_name WHERE c.table_name = '{table}' AND c.column_name NOT IN ({ignore});".format(table = tbl_name, ignore = ", ".join("'%s'" %(i,) for i in fld_ignore))))] - return info + return [Field( + data[0], + self._pythonize_type(data[1]), + data[2], + data[3] + ) for data in reversed(self.query("SELECT c.column_name, c.data_type, CASE WHEN c.is_nullable = 'NO' THEN TRUE ELSE FALSE END AS required, CASE WHEN u.column_name IS NOT NULL THEN TRUE ELSE FALSE END AS unique FROM information_schema.columns c LEFT JOIN (SELECT kcu.column_name, tc.table_name FROM information_schema.table_constraints tc LEFT JOIN information_schema.key_column_usage kcu ON tc.constraint_catalog = kcu.constraint_catalog AND tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name WHERE tc.constraint_type IN ('UNIQUE', 'PRIMARY KEY') AND tc.table_name = '{table}') u ON u.column_name = c.column_name WHERE c.table_name = '{table}' AND c.column_name NOT IN ({ignore});".format(table = tbl_name, ignore = ", ".join("'%s'" %(i,) for i in fld_ignore))))] def _pythonize_type(self, db_type): """ Get python type diff --git a/database_changes_v2.sql b/database_changes_v2.sql index 44c443e..47434db 100644 --- a/database_changes_v2.sql +++ b/database_changes_v2.sql @@ -244,4 +244,9 @@ drop view parcels; alter table parcel_lookup alter column plot_sn type character varying; -CREATE VIEW parcels ... \ No newline at end of file +CREATE VIEW parcels ... + +--power cut corrupted parcel view? some parcels defined by < 3 beacons in parcel_def. So to remove them: + +delete from parcel_def where parcel_id in +(select parcel_id from parcel_def group by parcel_id having count(parcel_id) <3) diff --git a/metadata.txt b/metadata.txt index ff58f72..217c8e8 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,38 +1,13 @@ -# This file contains metadata for your plugin. Beginning -# with version 1.8 this is the preferred way to supply information about a -# plugin. The current method of embedding metadata in __init__.py will -# be supported until version 2.0 - -# This file should be included when you package your plugin. - -# Mandatory items: - +; Mandatory items: [general] name=SML Surveyor Plugin -qgisMinimumVersion=1.8 description=SML Surveyor Plugin -version=0.1 +qgisMinimumVersion=2.0 +version=Version 0.2 author=AfriSpatial email=robert@afrispatial.co.za -# end of mandatory metadata - -# Optional items: - -# Uncomment the following line and add your changelog entries: -# changelog= - -# tags are comma separated with spaces allowed -tags=survey - -homepage= -tracker= -repository= -icon=icon.png -# experimental flag +; Optional items: experimental=False - -# deprecated flag (applies to the whole plugin, not just a single version -deprecated=False - +icon=icon.png diff --git a/plugin.py b/plugin.py new file mode 100644 index 0000000..3fbf55d --- /dev/null +++ b/plugin.py @@ -0,0 +1,621 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + sml_surveyor + A QGIS plugin + SML Surveyor Plugin + ------------------- + begin : 2012-12-28 + modified last : 2014-01-07 + copyright : (C) 2012 by AfriSpatial + email : robert@afrispatial.co.za + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +# qgis imports +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from qgis.core import * +# python imports +import os +# user imports +import __init__ as metadata +from PyQt4Dialogs import * +import database +from constants import * +from datetime import datetime + +class RequiredLayer: + + def __init__(self, + name, + name_plural, + table, + primary_key, + geometry_type, + geometry_column='the_geom', + schema='public', + layer=None + ): + self.name = name + self.name_plural = name_plural + self.table = table + self.primary_key = primary_key + self.geometry_type = geometry_type + self.geometry_column = geometry_column + self.schema = schema + self.layer = layer + + +class Mode: + + def __init__(self, actor, action): + self.actor = actor + self.action = action + + +class sml_surveyor: + + def __init__(self, iface): + # save reference to the QGIS interface + self.iface = iface + # get plugin directory + self.plugin_dir = os.path.dirname(os.path.realpath(__file__)) + self.uri = None + self.db = None + self.datetime = datetime.now() + self.requiredLayers = [] + # 1. beacons + # 2. parcels + self.requiredLayers.append(RequiredLayer( + 'Beacon', 'Beacons', 'beacons', 'gid', 'points' + )) + self.requiredLayers.append(RequiredLayer( + 'Parcel', 'Parcels', 'parcels', 'parcel_id', 'polygons' + )) + + + def initGui(self): + """ Initialize gui + """ + # create plugin toolbar + self.createPluginToolBar() + + + def unload(self): + """ Uninitialize gui + """ + # remove plugin toolbar + self.removePluginToolBar() + # remove layers + self.refreshLayers() + for l in self.requiredLayers: + if bool(l.layer): + QgsMapLayerRegistry.instance().removeMapLayers([l.layer.id(),]) + + + def createPluginToolBar(self): + """ Create plugin toolbar to house buttons + """ + # create plugin toolbar + self.pluginToolBar = QToolBar(metadata.name()) + self.pluginToolBar.setObjectName(metadata.name()) + # create Beardist button + self.actionBearDist = QAction( + QIcon(os.path.join(self.plugin_dir, "images", "beardist.png")), + "Manage Bearings and Distances", + self.iface.mainWindow() + ) + self.actionBearDist.setWhatsThis("Manage bearings and distances") + self.actionBearDist.setStatusTip("Manage bearings and distances") + self.actionBearDist.triggered.connect(self.manageBearDist) + # create Beacons button + self.actionBeacons = QAction( + QIcon(os.path.join(self.plugin_dir, "images", "beacon.gif")), + "Manage Beacons", + self.iface.mainWindow() + ) + self.actionBeacons.setWhatsThis("Manage beacons") + self.actionBeacons.setStatusTip("Manage beacons") + self.actionBeacons.triggered.connect(self.manageBeacons) + # create Parcels button + self.actionParcels = QAction( + QIcon(os.path.join(self.plugin_dir, "images", "parcel.png")), + "Manage Parcels", + self.iface.mainWindow() + ) + self.actionParcels.setWhatsThis("Manage parcels") + self.actionParcels.setStatusTip("Manage parcels") + self.actionParcels.triggered.connect(self.manageParcels) + # populate plugin toolbar + self.pluginToolBar.addAction(self.actionBearDist) + self.pluginToolBar.addAction(self.actionBeacons) + self.pluginToolBar.addAction(self.actionParcels) + # add plugin toolbar to gui + self.iface.mainWindow().addToolBar(self.pluginToolBar) + + + def removePluginToolBar(self): + """ Remove plugin toolbar which houses buttons + """ + # remove app toolbar from gui + if hasattr(self,"pluginToolBar"): + self.iface.mainWindow().removeToolBar(self.pluginToolBar) + self.pluginToolBar.hide() + + + def setDatabaseConnection(self): + """ Create a database connection + """ + # fetch settings + settings_plugin = QSettings() + settings_postgis = QSettings() + settings_plugin.beginGroup(metadata.name().replace(" ","_")) + settings_postgis.beginGroup('PostgreSQL/connections') + # fetch pre-chosen database connection + conn = settings_plugin.value("DatabaseConnection", None) + # check if still exists + if bool(conn): + if conn not in settings_postgis.childGroups(): + settings_plugin.setValue("DatabaseConnection", "") + conn = None + # fetch from user if necessary + if not bool(conn): + dlg = dlg_DatabaseConnection() + dlg.show() + if bool(dlg.exec_()): + conn = dlg.getDatabaseConnection() + settings_plugin.setValue("DatabaseConnection", conn) + # validate database connection + if bool(conn): + db_host = settings_postgis.value(conn+'/host') + db_port = settings_postgis.value(conn+'/port') + db_name = settings_postgis.value(conn+'/database') + db_username = '' + db_password = '' + self.uri = QgsDataSourceURI() + self.uri.setConnection( + db_host, + db_port, + db_name, + db_username, + db_password, + ) + max_attempts = 3 + msg = "Please enter the username and password." + for i in range(max_attempts): + ok, db_username, db_password = QgsCredentials.instance().get( + self.uri.connectionInfo(), + db_username, + db_password, + msg + ) + if not ok: break + db_username.replace(" ", "") + db_password.replace(" ", "") + try: + self.db = database.Manager({ + "HOST":db_host, + "NAME":db_name, + "PORT":db_port, + "USER":db_username, + "PASSWORD":db_password + }) + self.uri.setConnection( + db_host, + db_port, + db_name, + db_username, + db_password, + ) + self.datetime = datetime.now() + break + except Exception as e: + msg = "Invalid username and password." + settings_plugin.endGroup() + settings_postgis.endGroup() + + + def refreshLayers(self): + """ Ensure all required layers exist + """ + if bool(self.db): + for l in reversed(self.requiredLayers): + for layer in self.iface.legendInterface().layers(): + if l.name_plural.lower() == layer.name().lower(): + l.layer = layer + break + if not bool(l.layer): + self.uri.setDataSource( + l.schema, + l.table, + l.geometry_column, + '', + l.primary_key + ) + self.iface.addVectorLayer( + self.uri.uri(), + l.name_plural, + "postgres" + ) + for layer in self.iface.legendInterface().layers(): + if l.name_plural == layer.name(): l.layer = layer + + + def manageBeacons(self): + """ Portal which enables the management of beacons + """ + if self.datetime.date() != datetime.now().date(): self.db = None + if self.db is None: + self.setDatabaseConnection() + if self.db is None: return + self.refreshLayers() + BeaconManager(self.iface, self.db, self.requiredLayers) + self.iface.mapCanvas().refresh() + + + def manageParcels(self): + """ Portal which enables the management of parcels + """ + if self.datetime.date() != datetime.now().date(): self.db = None + if self.db is None: + self.setDatabaseConnection() + if self.db is None: return + self.refreshLayers() + ParcelManager(self.iface, self.db, self.requiredLayers) + self.iface.mapCanvas().refresh() + + + def manageBearDist(self): + """ Portal which enables the management of + bearings and distances + """ + if self.datetime.date() != datetime.now().date(): self.db = None + if self.db is None: + self.setDatabaseConnection() + if self.db is None: return + self.refreshLayers() + BearDistManager(self.iface, self.db, self.requiredLayers) + self.iface.mapCanvas().refresh() + + +class BeaconManager(): + + def __init__(self, iface, db, requiredLayers): + self.iface = iface + self.db = db + self.requiredLayers = requiredLayers + self.run() + + + def run(self): + """ Main method + """ + # display manager dialog + mng = dlg_Manager(self.requiredLayers[0]) + mng.show() + mng_ret = mng.exec_() + if bool(mng_ret): + + if mng.getOption() == 0: # create new beacon + while True: + # get fields + fields = self.db.getSchema( + self.requiredLayers[0].table, [ + self.requiredLayers[0].geometry_column, + self.requiredLayers[0].primary_key + ]) + # display form + frm = dlg_FormBeacon( + self.db, + SQL_BEACONS["UNIQUE"], + fields + ) + frm.show() + frm_ret = frm.exec_() + if bool(frm_ret): + # add beacon to database + values_old, values_new = frm.getValues() + self.db.query( + SQL_BEACONS["INSERT"].format(fields = ", ".join(sorted(values_new.keys())), values = ", ".join(["%s" for k in values_new.keys()])), [values_new[k] for k in sorted(values_new.keys())]) + self.iface.mapCanvas().refresh() + else: break + + elif mng.getOption() == 1: # edit existing beacon + # select beacon + mode = Mode("EDITOR","EDIT") + query = SQL_BEACONS["SELECT"] + slc = dlg_Selector( + self.db, + self.iface, + self.requiredLayers[0], + mode, + query, + preserve = True + ) + slc.show() + slc_ret = slc.exec_() + self.iface.mapCanvas().setMapTool(slc.tool) + if bool(slc_ret): + featID = slc.getFeatureId() + # check if defined by a bearing and distance + if self.db.query(SQL_BEACONS["BEARDIST"], (featID,))[0][0]: + QMessageBox.warning( + None, + "Bearing and Distance Definition", + "Cannot edit beacon defined by distance and bearing via this tool" + ) + for l in self.requiredLayers: l.layer.removeSelection() + return + # get fields + fields = self.db.getSchema( + self.requiredLayers[0].table, [ + self.requiredLayers[0].geometry_column, + self.requiredLayers[0].primary_key + ]) + # get values + values = [v for v in self.db.query(SQL_BEACONS["EDIT"].format(fields = ",".join([f.name for f in fields])), (featID,))[0]] + # display form + frm = dlg_FormBeacon( + self.db, + SQL_BEACONS["UNIQUE"], + fields, + values + ) + frm.show() + frm_ret = frm.exec_() + if bool(frm_ret): + # edit beacon in database + fields_old = [] + fields_new = [] + values_old = [] + values_new = [] + for f in fields: + if frm.getValues()[0][f.name] is not None: + fields_old.append(f.name) + values_old.append(frm.getValues()[0][f.name]) + fields_new.append(f.name) + values_new.append(frm.getValues()[1][f.name]) + set = ", ".join(["{field} = %s".format(field = f) for f in fields_new]) + where = " AND ".join(["{field} = %s".format(field = f) for f in fields_old]) + self.db.query( + SQL_BEACONS["UPDATE"].format( + set = set, + where = where + ), values_new + values_old + ) + for l in self.requiredLayers: l.layer.removeSelection() + + elif mng.getOption() == 2: # delete existing beacon + # select beacon + mode = Mode("REMOVER","REMOVE") + query = SQL_BEACONS["SELECT"] + slc = dlg_Selector( + self.db, + self.iface, + self.requiredLayers[0], + mode, + query, + preserve = True + ) + slc.show() + slc_ret = slc.exec_() + self.iface.mapCanvas().setMapTool(slc.tool) + if bool(slc_ret): + featID = slc.getFeatureId() + # check if defined by a bearing and distance + if self.db.query(SQL_BEACONS["BEARDIST"], (featID,))[0][0]: + QMessageBox.warning(None, "Bearing and Distance Definition", "Cannot delete beacon defined by distance and bearing via this tool") + for l in self.requiredLayers: l.layer.removeSelection() + return + # delete beacon from database + self.db.query(SQL_BEACONS["DELETE"], (featID,)) + for l in self.requiredLayers: l.layer.removeSelection() + + +class ParcelManager(): + + def __init__(self, iface, db, requiredLayers): + self.iface = iface + self.db = db + self.requiredLayers = requiredLayers + self.run() + + + def run(self): + """ Main method + """ + # display manager dialog + mng = dlg_Manager(self.requiredLayers[1]) + mng.show() + mng_ret = mng.exec_() + if bool(mng_ret): + + if mng.getOption() == 0: # create new parcel + while True: + # show parcel form + autocomplete = [str(i[0]) for i in self.db.query( + SQL_PARCELS["AUTOCOMPLETE"] + )] + frm = dlg_FormParcel( + self.db, + self.iface, + self.requiredLayers, + SQL_BEACONS, + SQL_PARCELS, + autocomplete + ) + frm.show() + frm_ret = frm.exec_() + self.iface.mapCanvas().setMapTool(frm.tool) + if bool(frm_ret): + # add parcel to database + points = [] + for i, beacon in enumerate(frm.getValues()[1]["sequence"]): + points.append( + (frm.getValues()[1]["parcel_id"], beacon, i)) + sql = self.db.queryPreview( + SQL_PARCELS["INSERT_GENERAL"], + data=points, + multi_data=True + ) + self.db.query(sql) + self.iface.mapCanvas().refresh() + else: + break + for l in self.requiredLayers: l.layer.removeSelection() + + elif mng.getOption() == 1: # edit existing parcel + # select parcel + mode = Mode("EDITOR","EDIT") + query = SQL_PARCELS["SELECT"] + slc = dlg_Selector( + self.db, + self.iface, + self.requiredLayers[1], + mode, + query, + preserve = True + ) + slc.show() + slc_ret = slc.exec_() + self.iface.mapCanvas().setMapTool(slc.tool) + if bool(slc_ret): + # show parcel form + autocomplete = [str(i[0]) for i in self.db.query( + SQL_PARCELS["AUTOCOMPLETE"] + )] + data = (lambda t: {"parcel_id":t[0], "sequence":t[1]})( + self.db.query( + SQL_PARCELS["EDIT"], (slc.getFeatureId(),) + )[0] + ) + frm = dlg_FormParcel( + self.db, + self.iface, + self.requiredLayers, + SQL_BEACONS, + SQL_PARCELS, + autocomplete, + data + ) + frm.show() + frm_ret = frm.exec_() + self.iface.mapCanvas().setMapTool(frm.tool) + if bool(frm_ret): + # edit parcel in database + self.db.query( + SQL_PARCELS["DELETE"], + (frm.getValues()[0]["parcel_id"],) + ) + points = [] + for i, beacon in enumerate(frm.getValues()[1]["sequence"]): + points.append( + (frm.getValues()[1]["parcel_id"], beacon, i)) + sql = self.db.queryPreview( + SQL_PARCELS["INSERT_GENERAL"], + data=points, + multi_data=True + ) + self.db.query(sql) + for l in self.requiredLayers: l.layer.removeSelection() + + elif mng.getOption() == 2: # delete existing parcel + # select parcel + mode = Mode("REMOVER","REMOVE") + query = SQL_PARCELS["SELECT"] + slc = dlg_Selector( + self.db, + self.iface, + self.requiredLayers[1], + mode, + query, + preserve = True + ) + slc.show() + slc_ret = slc.exec_() + self.iface.mapCanvas().setMapTool(slc.tool) + if bool(slc_ret): + # delete parcel from database + featID = slc.getFeatureId() + self.db.query(SQL_PARCELS["DELETE"], (self.db.query( + SQL_PARCELS["SELECT"], (featID,) + )[0][0],)) + for l in self.requiredLayers: l.layer.removeSelection() + + +class BearDistManager(): + + def __init__(self, iface, db, requiredLayers): + self.iface = iface + self.db = db + self.requiredLayers = requiredLayers + self.run() + + def run(self): + """ Main method + """ + dlg = dlg_FormBearDist( + self.db, + SQL_BEARDIST, + SQL_BEACONS, + self.requiredLayers + ) + dlg.show() + dlg_ret = dlg.exec_() + if bool(dlg_ret): + surveyPlan, referenceBeacon, beardistChain = dlg.getReturn() + # check whether survey plan is defined otherwise define it + if not self.db.query( + SQL_BEARDIST["IS_SURVEYPLAN"], + (surveyPlan,) + )[0][0]: + self.db.query( + SQL_BEARDIST["INSERT_SURVEYPLAN"], + (surveyPlan, referenceBeacon) + ) + # get list of existing links + beardistChainExisting = [] + for index, link in enumerate(self.db.query(SQL_BEARDIST["EXIST_BEARDISTCHAINS"],(surveyPlan,))): + beardistChainExisting.append([list(link), "NULL", index]) + # perform appropriate action for each link in the beardist chain + new = [] + old = [] + for link in beardistChain: + if link[2] is None: new.append(link) + else: old.append(link) + # sort out old links + tmp = list(beardistChainExisting) + for elink in beardistChainExisting: + for olink in old: + if elink[2] == olink[2]: + if olink[1] == "NULL": + tmp.remove(elink) + break; + self.db.query( + SQL_BEARDIST["UPDATE_LINK"], + [surveyPlan] + olink[0] + [olink[2]] + ) + tmp.remove(elink) + break; + beardistChainExisting = tmp + for elink in beardistChainExisting: + self.db.query( + SQL_BEARDIST["DELETE_LINK"], + (elink[0][3],) + ) + # sort out new links + for nlink in new: + self.db.query( + SQL_BEARDIST["INSERT_LINK"], + [surveyPlan] + nlink[0] + ) diff --git a/portqgis2_db_changes.sql b/portqgis2_db_changes.sql new file mode 100644 index 0000000..488b5a4 --- /dev/null +++ b/portqgis2_db_changes.sql @@ -0,0 +1,41 @@ +CREATE OR REPLACE FUNCTION beardistupdate(arg_plan_no character varying, arg_bearing double precision, arg_distance double precision, arg_beacon_from character varying, arg_beacon_to character varying, arg_location character varying, arg_name character varying, arg_index integer) + RETURNS void AS +$BODY$ + DECLARE + the_id_beardist integer; + the_id_beacons integer; + the_x double precision; + the_y double precision; + the_geom_ geometry(Point, 26331); + BEGIN + SELECT i.id INTO the_id_beardist FROM ( + SELECT bd.id, row_number() over(ORDER BY bd.id) -1 as index + FROM beardist bd + INNER JOIN beacons b ON bd.beacon_to = b.beacon + WHERE bd.plan_no = arg_plan_no + ) AS i + WHERE i.index = arg_index; + SELECT gid INTO the_id_beacons FROM beacons b INNER JOIN beardist bd ON b.beacon = bd.beacon_to WHERE bd.id = the_id_beardist; + SELECT x INTO the_x FROM beacons WHERE beacon = arg_beacon_from; + SELECT y INTO the_y FROM beacons WHERE beacon = arg_beacon_from; + SELECT pointfrombearinganddistance(the_x, the_y, arg_bearing, arg_distance, 3, 26331) INTO the_geom_; + UPDATE beacons SET + beacon = arg_beacon_to, + y = st_y(the_geom_), + x = st_x(the_geom_), + "location" = arg_location, + "name" = arg_name + WHERE gid = the_id_beacons; + UPDATE beardist SET + plan_no = arg_plan_no, + bearing = arg_bearing, + distance = arg_distance, + beacon_from = arg_beacon_from, + beacon_to = arg_beacon_to + WHERE id = the_id_beardist; + END +$BODY$ + LANGUAGE plpgsql VOLATILE + COST 100; +ALTER FUNCTION beardistupdate(character varying, double precision, double precision, character varying, character varying, character varying, character varying, integer) + OWNER TO robert; \ No newline at end of file diff --git a/qgisToolbox.py b/qgisToolbox.py index bf19f6a..c44437a 100644 --- a/qgisToolbox.py +++ b/qgisToolbox.py @@ -25,7 +25,7 @@ class featureSelector(): """ This tool enables the selection of a single feature or multiple features from a vector layer, returning the feature's id or features' ids after each selection via the captured method in the invoking class """ - def __init__(self, iface, layer, capturing = True, parent = None): + def __init__(self, iface, layer, capturing=True, parent=None): # initialize instance valiables self.parent = parent # assume parent has a captured method self.iface = iface @@ -64,11 +64,9 @@ def appendSelection(self, id): """ Append a feature to the list of currently selected features """ # toggle selection - if id in self.selected: del self.selected[self.selected.index(id)] - else: self.selected.append(id) - self.layer.setSelectedFeatures(self.selected) + self.selected.append(id) # notify parent of changed selection - if self.parent is not None: self.parent.captured(self.selected) + self.parent.captured(self.selected) def capture(self, point, button): """ Capture id of feature selected by the selector tool @@ -76,10 +74,21 @@ def capture(self, point, button): # check that capturing has been enabled if self.capturing: pnt_geom = QgsGeometry.fromPoint(point) - pnt_buffer = pnt_geom.buffer((self.iface.mapCanvas().mapUnitsPerPixel()*5),0) + pnt_buffer = pnt_geom.buffer((self.iface.mapCanvas().mapUnitsPerPixel()*4),0) pnt_rect = pnt_buffer.boundingBox() - self.layer.select([], pnt_rect, True, True) - feat = QgsFeature() - while self.layer.nextFeature(feat): - self.appendSelection(feat.id()) - break + self.layer.invertSelectionInRectangle(pnt_rect) + if bool(self.layer.selectedFeaturesIds()): + for id in self.layer.selectedFeaturesIds(): + if id not in self.selected: + self.selected.append(id) + selected = self.selected + for id in selected: + if id not in self.layer.selectedFeaturesIds(): + self.selected.remove(id) + self.parent.captured(self.selected) + + #self.layer.select([], pnt_rect, True, True) + #feat = QgsFeature() + #while self.layer.nextFeature(feat): + # self.appendSelection(feat.id()) + # break diff --git a/settings.py b/settings.py deleted file mode 100644 index a051a28..0000000 --- a/settings.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -File: settings.py -Description: stores local settings used by the qgis python plugin -Author: Robert Moerman -Contact: robert@afrispatial.co.za -Company: AfriSpatial -""" - -import database_params - -# Database settings -DATABASE_HOST = database_params.DATABASE_PARAMS["HOST"] -DATABASE_PORT = database_params.DATABASE_PARAMS["PORT"] -DATABASE_NAME = database_params.DATABASE_PARAMS["NAME"] -DATABASE_USER = database_params.DATABASE_PARAMS["USER"] -DATABASE_PASSWORD = database_params.DATABASE_PARAMS["PASSWORD"] -DATABASE_PARAMS = database_params.DATABASE_PARAMS -DATABASE_SCHEMA = "public" -DATABASE_LAYERS = {} - -# Define database layers -DATABASE_LAYERS["BEACONS"] = { - "SCHEMA":"public", - "TABLE":"beacons", - "NAME":"Beacon", - "NAME_PLURAL":"Beacons", - "PKEY":"gid", - "GEOM":"the_geom", - "GEOM_TYPE":"points", - "SQL":{ - "SELECT":"SELECT beacon FROM beacons WHERE gid = %s;", - "UNIQUE":"SELECT COUNT(*) FROM beacons WHERE %s = %s;", - "EDIT":"SELECT {fields} FROM beacons WHERE gid = %s;", - "DELETE":"DELETE FROM beacons WHERE gid = %s;", - "INSERT":"INSERT INTO beacons({fields}) VALUES ({values}) RETURNING gid;", - "UPDATE":"UPDATE beacons SET {set} WHERE {where};", - "BEARDIST":"SELECT CASE WHEN count(*) = 0 THEN FALSE ELSE TRUE END FROM beardist WHERE beacon_to = (SELECT beacon FROM beacons WHERE gid = %s);" - } -} -DATABASE_LAYERS["PARCELS"] = { - "SCHEMA":"public", - "TABLE":"parcels", - "NAME":"Parcel", - "NAME_PLURAL":"Parcels", - "PKEY":"parcel_id", - "GEOM":"the_geom", - "GEOM_TYPE":"polygons", - "SQL":{ - "SELECT":"SELECT parcel_id FROM parcel_lookup WHERE parcel_id = %s;", - "EDIT":"SELECT l.parcel_id, array_agg(s.gid ORDER BY s.sequence) FROM ( SELECT b.gid, d.parcel_id, d.sequence FROM beacons b INNER JOIN parcel_def d ON d.beacon = b.beacon) s JOIN parcel_lookup l ON s.parcel_id = l.parcel_id WHERE l.parcel_id = %s GROUP BY l.parcel_id;", - "AUTOCOMPLETE":"SELECT parcel_id FROM parcel_lookup WHERE available;", - "UNIQUE":"SELECT COUNT(*) FROM parcel_lookup WHERE parcel_id = %s;", - "AVAILABLE":"SELECT available FROM parcel_lookup WHERE parcel_id = %s;", - "INSERT":"INSERT INTO parcel_def(parcel_id, beacon, sequence) VALUES (%s, %s, %s);", - "DELETE":"DELETE FROM parcel_def WHERE parcel_id = %s;", - } -} - -# DB Triggers: -# - auto create parcel id in parcel_lookup if it does not exist on -# insert/update on parcel_def -# - auto update available field in parcel_lookup on insert/update/delete on -# parcel_def - -DATABASE_LAYERS_ORDER = ["BEACONS", "PARCELS"] - -# Define other sql commands -DATABASE_OTHER_SQL = { - "AUTO_SURVEYPLAN":"SELECT array_agg(plan_no) FROM survey;", - "AUTO_REFERENCEBEACON":"SELECT array_agg(beacon) FROM beacons WHERE beacon NOT IN (SELECT beacon_to FROM beardist WHERE beacon_to NOT IN (SELECT ref_beacon FROM survey));", - "EXIST_REFERENCEBEACON":"SELECT ref_beacon FROM survey where plan_no = %s;", - "EXIST_BEARDISTCHAINS":"SELECT bd.bearing, bd.distance, bd.beacon_from, bd.beacon_to, b.location, b.name FROM beardist bd INNER JOIN beacons b ON bd.beacon_to = b.beacon WHERE bd.plan_no = %s;", - "INDEX_REFERENCEBEACON":"SELECT i.column_index::integer FROM (SELECT row_number() over(ORDER BY c.ordinal_position) -1 as column_index, c.column_name FROM information_schema.columns c WHERE c.table_name = 'beacons' AND c.column_name NOT IN ('geom', 'gid') ORDER BY c.ordinal_position) as i WHERE i.column_name = 'beacon';", - "IS_SURVEYPLAN":"SELECT CASE WHEN COUNT(*) <> 0 THEN TRUE ELSE FALSE END FROM survey WHERE plan_no = %s;", - "INSERT_SURVEYPLAN":"INSERT INTO survey(plan_no, ref_beacon) VALUES(%s, %s);", - "UPDATE_LINK":"SELECT beardistupdate(%s, %s, %s, %s, %s, %s, %s, %s);", - "DELETE_LINK":"DELETE FROM beacons WHERE beacon = %s;", - "INSERT_LINK":"SELECT beardistinsert(%s, %s, %s, %s, %s, %s, %s);" -} diff --git a/sml_surveyor.py b/sml_surveyor.py deleted file mode 100644 index d9ad15b..0000000 --- a/sml_surveyor.py +++ /dev/null @@ -1,376 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - sml_surveyor - A QGIS plugin - SML Surveyor Plugin - ------------------- - begin : 2012-12-28 - copyright : (C) 2012 by AfriSpatial - email : robert@afrispatial.co.za - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -import __init__ as meta -import database -import settings -from PyQt4Dialogs import * -import json -import os - -class sml_surveyor: - - def __init__(self, iface): - # Save reference to the QGIS interface - self.iface = iface - # initialize plugin directory - self.plugin_dir = os.path.dirname(os.path.realpath(__file__)) - # initialize locale - localePath = "" - locale = QSettings().value("locale/userLocale").toString()[0:2] - if QFileInfo(self.plugin_dir).exists(): - localePath = os.path.join(self.plugin_dir, "i18n", "sml_surveyor_" + str(locale) + ".qm") - if QFileInfo(localePath).exists(): - self.translator = QTranslator() - self.translator.load(localePath) - if qVersion() > '4.3.3': - QCoreApplication.installTranslator(self.translator) - # initialize instance variables - self.layers = {} - self.db = None - - def initGui(self): - """ Initialize gui - """ - # find database - self.db = None - try: - self.db = database.manager(settings.DATABASE_PARAMS) - except: - QMessageBox.information(None, "Configure Database Settings", "Please define database parameters.") - if not(self.manageDatabase()): raise Exception("Unspecied database parameters") - # find layers - self.getLayers() - self.showLayers() - # add app toolbar - self.createAppToolBar() - - def unload(self): - """ Uninitialize gui - """ - # remove layers - #self.dropLayers() - # remove app toolbar - self.removeAppToolBar() - - def createAppToolBar(self): - """ Create plugin toolbar to house buttons - """ - # create app toolbar - self.appToolBar = QToolBar(meta.name()) - self.appToolBar.setObjectName(meta.name()) - # create apps for toolbar - self.actionBearDist = QAction(QIcon(os.path.join(self.plugin_dir, "images", "beardist.png")), "Manage Bearings and Distances", self.iface.mainWindow()) - self.actionBearDist.triggered.connect(self.manageBearDist) - self.actionBeacons = QAction(QIcon(os.path.join(self.plugin_dir, "images", "beacon.gif")), "Manage %s" %(settings.DATABASE_LAYERS["BEACONS"]["NAME_PLURAL"].title(),), self.iface.mainWindow()) - self.actionBeacons.triggered.connect(self.manageBeacons) - self.actionParcels = QAction(QIcon(os.path.join(self.plugin_dir, "images", "parcel.png")), "Manage %s" %(settings.DATABASE_LAYERS["PARCELS"]["NAME_PLURAL"].title(),), self.iface.mainWindow()) - self.actionParcels.triggered.connect(self.manageParcels) - # populate app toolbar - self.appToolBar.addAction(self.actionBearDist) - self.appToolBar.addAction(self.actionBeacons) - self.appToolBar.addAction(self.actionParcels) - # add app toolbar to gui - self.iface.mainWindow().addToolBar(self.appToolBar) - - def removeAppToolBar(self): - """ Remove plugin toolbar which houses buttons - """ - # remove app toolbar from gui - if hasattr(self,"appToolBar"): self.iface.mainWindow().removeToolBar(self.appToolBar) - - def addLayers(self): - """ Add postgres layers - """ - uri = QgsDataSourceURI() - uri.setConnection(settings.DATABASE_HOST, settings.DATABASE_PORT, settings.DATABASE_NAME, settings.DATABASE_USER, settings.DATABASE_PASSWORD) - for name in reversed(settings.DATABASE_LAYERS_ORDER): - lyr = settings.DATABASE_LAYERS[name] - uri.setDataSource(lyr["SCHEMA"], lyr["TABLE"], lyr["GEOM"], "", lyr["PKEY"]) - self.iface.addVectorLayer(uri.uri(), lyr["NAME_PLURAL"], "postgres") - - def getLayers(self): - """ Save reference to added postgis layers - """ - self.layers = {} - names = list(settings.DATABASE_LAYERS_ORDER) - for l in self.iface.legendInterface().layers(): - for n in names: - if settings.DATABASE_LAYERS[n]["NAME_PLURAL"].lower() == str(l.name()).lower(): - self.layers[n] = l - names.remove(n) - - def showLayers(self): - """ Show added postgis layers on map canvas - """ - for n,l in self.layers.items(): - self.iface.legendInterface().setLayerVisible(l, True) - l.loadNamedStyle(os.path.join(self.plugin_dir, "styles", "%s.qml" %(n.lower(),))) - self.iface.legendInterface().refreshLayerSymbology(l) - - def dropLayers(self): - """ Drop added postgis layers - """ - self.getLayers() - QgsMapLayerRegistry.instance().removeMapLayers([l.id() for l in self.layers.values()]) - - def manageBeacons(self): - """ Portal which enables the management of beacons - """ - self.manageLayers() - p = Beacons(self.iface, self.layers, settings.DATABASE_LAYERS, self.db) - p.run() - self.iface.mapCanvas().refresh() - - def manageParcels(self): - """ Portal which enables the management of parcels - """ - self.manageLayers() - p = Parcels(self.iface, self.layers, settings.DATABASE_LAYERS, self.db) - p.run() - self.iface.mapCanvas().refresh() - - def manageDatabase(self): - """ Portal which enables configuration of database settings - """ - dlg = dlg_FormDatabase() - dlg.show() - if bool(dlg.exec_()): - save, db, params = dlg.getReturn() - # save new db reference - self.db = db - # save new params if needed - if save: - import os - p = os.path.join(os.path.dirname(__file__), 'database_params.py') - f = open(p, 'w') - f.write('DATABASE_PARAMS = %s' %(json.dumps(params),)) - f.close() - reload(settings.database_params) - reload(settings) - return True - return False - - def manageBearDist(self): - """ - """ - self.manageLayers() - dlg = dlg_FormBearDist(self.db, settings.DATABASE_OTHER_SQL, settings.DATABASE_LAYERS["BEACONS"]["SQL"], self.db.getSchema(settings.DATABASE_LAYERS["BEACONS"]["TABLE"], [settings.DATABASE_LAYERS["BEACONS"]["GEOM"], settings.DATABASE_LAYERS["BEACONS"]["PKEY"]])) - dlg.show() - if bool(dlg.exec_()): - #tmp = ('YE125', 'PBA6015', [[[29.45, 46.29, 'PBA6015', 'PBA6016lol', None, None], 'UPDATE', 0], [[123.0, 123.0, 'PBA6016lol', 'l', None, None], 'INSERT', None], [[123.0, 234.0, 'l', 'lo', None, None], 'INSERT', None], [[123.0, 345.0, 'lo', 'lol', None, None], 'INSERT', None]]) - surveyPlan, referenceBeacon, beardistChain = dlg.getReturn() - # check whether survey plan is defined otherwise define it - if not self.db.query(settings.DATABASE_OTHER_SQL["IS_SURVEYPLAN"], (surveyPlan,))[0][0]: - QMessageBox.information(None, "", "need to make a plan about this survey") - self.db.query(settings.DATABASE_OTHER_SQL["INSERT_SURVEYPLAN"], (surveyPlan, referenceBeacon)) - # get list of existing links - beardistChainExisting = [] - for index, link in enumerate(self.db.query(settings.DATABASE_OTHER_SQL["EXIST_BEARDISTCHAINS"],(surveyPlan,))): - beardistChainExisting.append([list(link), "NULL", index] ) - # perform appropriate action for each link in the beardist chain - new = [] - old = [] - for link in beardistChain: - if link[2] is None: new.append(link) - else: old.append(link) - #QMessageBox.information(None, "", str(beardistChainExisting)) - #QMessageBox.information(None, "", str(new)) - #QMessageBox.information(None, "", str(old)) - # sort out old links - tmp = list(beardistChainExisting) - for elink in beardistChainExisting: - for olink in old: - if elink[2] == olink[2]: - if olink[1] == "NULL": - tmp.remove(elink) - break; - self.db.query(settings.DATABASE_OTHER_SQL["UPDATE_LINK"], [surveyPlan] + olink[0] + [olink[2]]) - tmp.remove(elink) - break; - beardistChainExisting = tmp - for elink in beardistChainExisting: - self.db.query(settings.DATABASE_OTHER_SQL["DELETE_LINK"], (elink[0][3],)) - # sort out new links - for nlink in new: - self.db.query(settings.DATABASE_OTHER_SQL["INSERT_LINK"], [surveyPlan] + nlink[0]) - self.iface.mapCanvas().refresh() - - def manageLayers(self): - """ Load layers if not yet loaded - """ - self.getLayers() - if len(self.layers.keys()) != len(settings.DATABASE_LAYERS.keys()): - self.dropLayers() - self.addLayers() - self.getLayers() - self.showLayers() - -class Parcels(): - """ Class managing parcels - """ - - def __init__(self, iface, layers, layersDict, db): - self.iface = iface - self.layers = layers - self.layersDict = layersDict - self.db = db - - def run(self): - """ Main method - """ - # display manager dialog - mng = dlg_Manager(obj = {"NAME":self.layersDict["PARCELS"]["NAME"],}) - mng.show() - if bool(mng.exec_()): - if mng.getReturn() == 0: - while True: - # create new parcel - autocomplete = [str(i[0]) for i in self.db.query(self.layersDict["PARCELS"]["SQL"]["AUTOCOMPLETE"])] - #autocomplete = [i[0] for i in self.db.query(self.layersDict["PARCELS"]["SQL"]["AUTOCOMPLETE"])] - frm = dlg_FormParcel(self.db, self.iface, self.layers, self.layersDict, autocomplete) - frm.show() - frm_ret = frm.exec_() - self.iface.mapCanvas().setMapTool(frm.tool) - if bool(frm_ret): - sql = "" - for i, beacon in enumerate(frm.getReturn()[1]["sequence"]): - sql += self.db.queryPreview(self.layersDict["PARCELS"]["SQL"]["INSERT"], (frm.getReturn()[1]["parcel_id"], beacon, i)) - self.db.query(sql) - self.iface.mapCanvas().refresh() - else: - break - for l in self.layers.values(): l.removeSelection() - elif mng.getReturn() == 1: - # edit existing parcel - obj = {"NAME":self.layersDict["PARCELS"]["NAME"],"PURPOSE":"EDITOR","ACTION":"EDIT"} - slc = dlg_Selector(self.db, self.iface, self.layers["PARCELS"], self.layersDict["PARCELS"]["SQL"], obj = obj, preserve = True) - slc.show() - slc_ret = slc.exec_() - self.iface.mapCanvas().setMapTool(slc.tool) - if bool(slc_ret): - autocomplete = [str(i[0]) for i in self.db.query(self.layersDict["PARCELS"]["SQL"]["AUTOCOMPLETE"])] - #autocomplete = [i[0] for i in self.db.query(self.layersDict["PARCELS"]["SQL"]["AUTOCOMPLETE"])] - values = (lambda t: {"parcel_id":t[0], "sequence":t[1]})(self.db.query(self.layersDict["PARCELS"]["SQL"]["EDIT"], (slc.getReturn(),))[0]) - frm = dlg_FormParcel(self.db, self.iface, self.layers, self.layersDict, autocomplete, values) - frm.show() - frm_ret = frm.exec_() - self.iface.mapCanvas().setMapTool(frm.tool) - if bool(frm_ret): - self.db.query(self.layersDict["PARCELS"]["SQL"]["DELETE"], (frm.getReturn()[0]["parcel_id"],)) - sql = "" - for i, beacon in enumerate(frm.getReturn()[1]["sequence"]): - sql += self.db.queryPreview(self.layersDict["PARCELS"]["SQL"]["INSERT"], (frm.getReturn()[1]["parcel_id"], beacon, i)) - self.db.query(sql) - for l in self.layers.values(): l.removeSelection() - elif mng.getReturn() == 2: - # delete existing parcel - obj = {"NAME":self.layersDict["PARCELS"]["NAME"],"PURPOSE":"REMOVER","ACTION":"REMOVE"} - slc = dlg_Selector(self.db, self.iface, self.layers["PARCELS"], self.layersDict["PARCELS"]["SQL"], obj = obj, preserve = True) - slc.show() - slc_ret = slc.exec_() - self.iface.mapCanvas().setMapTool(slc.tool) - if bool(slc_ret): - self.db.query(self.layersDict["PARCELS"]["SQL"]["DELETE"], (self.db.query(self.layersDict["PARCELS"]["SQL"]["SELECT"], (slc.getReturn(),))[0][0],)) - for l in self.layers.values(): l.removeSelection() - -class Beacons(): - """ Class managing beacons - """ - - def __init__(self, iface, layers, layersDict, db): - self.iface = iface - self.layers = layers - self.layersDict = layersDict - self.db = db - - def run(self): - """ Main method - """ - # display manager dialog - mng = dlg_Manager(obj = {"NAME":self.layersDict["BEACONS"]["NAME"],}) - mng.show() - if bool(mng.exec_()): - if mng.getReturn() == 0: - while True: - # create new beacon - data = self.db.getSchema(self.layersDict["BEACONS"]["TABLE"], [self.layersDict["BEACONS"]["GEOM"], self.layersDict["BEACONS"]["PKEY"]]) - frm = dlg_FormBeacon(self.db, data, self.layersDict["BEACONS"]["SQL"]) - frm.show() - frm_ret = frm.exec_() - if bool(frm_ret): - values_old, values_new = frm.getReturn() - self.db.query(self.layersDict["BEACONS"]["SQL"]["INSERT"].format(fields = ", ".join(sorted(values_new.keys())), values = ", ".join(["%s" for k in values_new.keys()])), [values_new[k] for k in sorted(values_new.keys())]) - self.iface.mapCanvas().refresh() - else: - break - elif mng.getReturn() == 1: - # edit existing beacon - obj = {"NAME":self.layersDict["BEACONS"]["NAME"],"PURPOSE":"EDITOR","ACTION":"EDIT"} - slc = dlg_Selector(self.db, self.iface, self.layers["BEACONS"], self.layersDict["BEACONS"]["SQL"], obj = obj, preserve = True) - slc.show() - slc_ret = slc.exec_() - self.iface.mapCanvas().setMapTool(slc.tool) - if bool(slc_ret): - if self.db.query(self.layersDict["BEACONS"]["SQL"]["BEARDIST"], (slc.getReturn(),))[0][0]: - QMessageBox.warning(None, "Bearing and Distance Definition", "Cannot edit beacon defined by distance and bearing via this tool") - for l in self.layers.values(): l.removeSelection() - return - data = self.db.getSchema(self.layersDict["BEACONS"]["TABLE"], [self.layersDict["BEACONS"]["GEOM"], self.layersDict["BEACONS"]["PKEY"]]) - fields = ",".join([f["NAME"] for f in data]) - values = [v for v in self.db.query(self.layersDict["BEACONS"]["SQL"]["EDIT"].format(fields = fields), (slc.getReturn(),))[0]] - frm = dlg_FormBeacon(self.db, data, self.layersDict["BEACONS"]["SQL"], values) - frm.show() - frm_ret = frm.exec_() - if bool(frm_ret): - fields_old = [] - fields_new = [] - values_old = [] - values_new = [] - for f in data: - if frm.getReturn()[0][f["NAME"]] is not None: - fields_old.append(f["NAME"]) - values_old.append(frm.getReturn()[0][f["NAME"]]) - fields_new.append(f["NAME"]) - values_new.append(frm.getReturn()[1][f["NAME"]]) - set = ", ".join(["{field} = %s".format(field = f) for f in fields_new]) - where = " AND ".join(["{field} = %s".format(field = f) for f in fields_old]) - self.db.query(self.layersDict["BEACONS"]["SQL"]["UPDATE"].format(set = set, where = where), values_new + values_old) - for l in self.layers.values(): l.removeSelection() - elif mng.getReturn() == 2: - # delete existing beacon - obj = {"NAME":self.layersDict["BEACONS"]["NAME"],"PURPOSE":"REMOVER","ACTION":"REMOVE"} - slc = dlg_Selector(self.db, self.iface, self.layers["BEACONS"], self.layersDict["BEACONS"]["SQL"], obj = obj, preserve = True) - slc.show() - slc_ret = slc.exec_() - self.iface.mapCanvas().setMapTool(slc.tool) - if bool(slc_ret): - if self.db.query(self.layersDict["BEACONS"]["SQL"]["BEARDIST"], (slc.getReturn(),))[0][0]: - QMessageBox.warning(None, "Bearing and Distance Definition", "Cannot delete beacon defined by distance and bearing via this tool") - for l in self.layers.values(): l.removeSelection() - return - self.db.query(self.layersDict["BEACONS"]["SQL"]["DELETE"], (slc.getReturn(),)) - for l in self.layers.values(): l.removeSelection() - diff --git a/styles/beacons.qml b/styles/beacons.qml index 80b5fd5..a23898b 100644 --- a/styles/beacons.qml +++ b/styles/beacons.qml @@ -1,5 +1,5 @@ - + 255 @@ -11,12 +11,12 @@ - + - + @@ -27,7 +27,7 @@ - + @@ -52,9 +52,24 @@ + + + + + + + + + + + + + + + - + @@ -65,8 +80,8 @@ - - + + @@ -92,14 +107,14 @@ - + - + - + @@ -141,8 +156,16 @@ . . - generatedlayout - - + + + + + + + Pie + 0 + + 0 + diff --git a/styles/parcel_area_errors.qml b/styles/parcel_area_errors.qml new file mode 100644 index 0000000..e9b3d23 --- /dev/null +++ b/styles/parcel_area_errors.qml @@ -0,0 +1,209 @@ + + + 255 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parcel_id + + + + + + + + + + + generatedlayout + + + + diff --git a/styles/parcels.qml b/styles/parcels.qml index b0636c0..0ede778 100644 --- a/styles/parcels.qml +++ b/styles/parcels.qml @@ -1,10 +1,16 @@ 255 - + + + + + + + - - + + @@ -13,9 +19,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - @@ -26,37 +65,37 @@ - - + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + @@ -64,14 +103,14 @@ - + - + @@ -86,15 +125,15 @@ - + - + - + @@ -107,7 +146,7 @@ - id + parcel_id - - - - - - - - + + + + + + + + + + + + . diff --git a/styles/parcels.sld b/styles/parcels.sld new file mode 100644 index 0000000..e635038 --- /dev/null +++ b/styles/parcels.sld @@ -0,0 +1,171 @@ + + + + + + + + parcels + + + group 0 + Feature + + acquisition + + + block + acquisition + + + + + #A020F0 + 1.0 + + + + + block + + + Arial + 12.0 + normal + normal + + + + + 0.0 + 0.0 + + + 0.0 + 0.0 + + + + + 1 + + #FFFF00 + + + + #A020F0 + + + + + perimeter + + + block + perimeter + + + + + #A52A2A + 1.0 + + + + + scheme + + + Arial + 12.0 + normal + normal + + + + + 0.0 + 0.0 + + + 0.0 + 0.0 + + + + + 1 + + #FFFF00 + + + + #A52A2A + + + + + parcels + + + + + + block + perimeter + + + block + acquisition + + + + + block + + + + 32001.0 + + + 0.3 + + + + + parcel_number + + + Arial + 10.0 + normal + normal + + + + + 0.0 + 0.0 + + + 0.0 + 0.0 + + + + + 1 + + #FFFFFF + + + + #000000 + + + + + + + + diff --git a/styles/parcels_18.qml b/styles/parcels_18.qml new file mode 100644 index 0000000..c6ab5e4 --- /dev/null +++ b/styles/parcels_18.qml @@ -0,0 +1,192 @@ + + + 255 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parcel_id + + + + + + + + + + + + + + + + + + . + + . + generatedlayout + + + +