def geomModified(layer, featureId): f = QgsFeature() if layer.getFeatures(QgsFeatureRequest().setFilterFid(featureId)).nextFeature(f) is False: return if f.attribute("update_geometry_alt1") in ('t','f') and f.attribute("update_geometry_alt2") in ('t','f'): print "qwat: already asked if alternative geometries should be updated" return _geometry_alt1_used = f.attribute("_geometry_alt1_used") == 't' _geometry_alt2_used = f.attribute("_geometry_alt2_used") == 't' if _geometry_alt1_used and f.attribute("update_geometry_alt1").isNull() or _geometry_alt2_used and f.attribute("update_geometry_alt2").isNull(): dlg = PipeGeomAltDialog() if f.attribute("update_geometry_alt1").isNull(): dlg.updateAlt1.setChecked( False if _geometry_alt1_used else True ) else: dlg.updateAlt1.setChecked( True if f.attribute("update_geometry_alt1") == 't' else False ) if f.attribute("update_geometry_alt2").isNull(): dlg.updateAlt2.setChecked( False if _geometry_alt2_used else True ) else: dlg.updateAlt2.setChecked( True if f.attribute("update_geometry_alt2") == 't' else False ) while not dlg.exec_(): continue editBuffer = layer.editBuffer() editBuffer.changeAttributeValue( featureId, layer.fieldNameIndex("update_geometry_alt1"), "t" if dlg.updateAlt1.isChecked() else "f" ) editBuffer.changeAttributeValue( featureId, layer.fieldNameIndex("update_geometry_alt2"), "t" if dlg.updateAlt2.isChecked() else "f" )
def load_from_layer(self, layer): # return False on failure pr = layer.dataProvider() fields = pr.fields() if fields.size() < 1: return False field = None for i, f in enumerate(fields): if f.name() == "params": field = i if field is None: return False it = pr.getFeatures() fet = QgsFeature() it.nextFeature(fet) st = fet.attribute(field) self.unserialize(base64.b64decode(st)) if self.geometry is None: self.geometry = QgsGeometry(fet.geometry()) self.orig_geometry = [QgsGeometry(fet.geometry())] return True
def testGdbFilter(self): """ Test opening a GDB database layer with filter""" gdb_path = os.path.join(unitTestDataPath(), 'test_gdb.gdb') l = QgsVectorLayer(gdb_path + '|layerid=1|subset="text" = \'shape 2\'', 'test', 'ogr') self.assertTrue(l.isValid()) it = l.getFeatures() f = QgsFeature() while it.nextFeature(f): self.assertTrue(f.attribute("text") == "shape 2")
def numericFields(self, layer): # get attributes of a sample feature and create numeric field name list numeric_fields = [] f = QgsFeature() layer.getFeatures().nextFeature(f) for field in f.fields(): isNumeric = False try: float(f.attribute(field.name())) isNumeric = True except ValueError: pass if isNumeric: numeric_fields.append(field.name()) return numeric_fields
def getPoiText(self): # Return grid references of POIs poiLayer = None for layer in self.iface.mapCanvas().layers(): if layer.name() == self.ui.poiLayerComboBox.currentText(): poiLayer = layer break if poiLayer == None: return 'Failed to find POI layer %s' % self.ui.poiLayerComboBox.currentText() poiString = 'Grid References\n\n' f = QgsFeature() fit = poiLayer.getFeatures() while fit.nextFeature(f): gridRef = xy_to_osgb(f.geometry().centroid().asPoint()[0], f.geometry().centroid().asPoint()[1], 10) coordText = '%s\t%s\n' % (f.attribute(self.ui.poiFieldComboBox.currentText()), gridRef) poiString += coordText return poiString
def processAlgorithm(self, parameters, context: QgsProcessingContext, feedback: QgsProcessingFeedback): """Here is where the processing itself takes place.""" feedback.setProgress(0) na = qgis_utils.plugins["qgepplugin"].network_analyzer # init params reach_layer = self.parameterAsVectorLayer(parameters, self.REACH_LAYER, context) flow_layer = self.parameterAsVectorLayer(parameters, self.FLOWTIMES_LAYER, context) fk_reach_field = self.parameterAsFields(parameters, self.FK_REACH_FIELD, context)[0] flow_time_field = self.parameterAsFields(parameters, self.FLOWTIMES_FIELD, context)[0] # create feature sink fields = QgsFields() fields.append(QgsField('flow_time', QVariant.Double)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, fields, QgsWkbTypes.LineString, reach_layer.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) # get selected reach iterator = reach_layer.getSelectedFeatures() feature_count = reach_layer.selectedFeatureCount() if feature_count != 1: raise QgsProcessingException( self.invalidSourceError(parameters, self.REACH_LAYER)) reach_feature = QgsFeature() iterator.nextFeature(reach_feature) assert reach_feature.isValid() qgep_reach_obj_id = reach_feature.attribute('obj_id') # get top node reach_features = na.getFeaturesByAttr(na.getEdgeLayer(), 'obj_id', [qgep_reach_obj_id]).asDict() assert len(reach_features) > 0 from_pos = 1 top_node = None for fid, reach_feature in reach_features.items(): if from_pos > reach_feature.attribute('from_pos'): top_node = reach_feature.attribute('from_obj_id_interpolate') from_pos = reach_feature.attribute('from_pos') assert top_node is not None nodes = na.getFeaturesByAttr(na.getNodeLayer(), 'obj_id', [top_node]).asDict() assert len(nodes) == 1 top_node_id = next(iter(nodes.values())).id() # create graph _, edges = na.getTree(top_node_id) feedback.setProgress(50) cache_edge_features = na.getFeaturesById( na.getEdgeLayer(), [edge[2]['feature'] for edge in edges]).asDict() # join and accumulate flow times i = -1 flow_time = 0.0 while True: i += 1 feedback.setProgress(50 + i / len(edges) * 50) if i >= len(edges): break edge = edges[i] edge_feature = cache_edge_features[edge[2]['feature']] # TODO: if top_pos != 1 => merge if edge_feature.attribute('type') != 'reach': continue rate = edge_feature.attribute('to_pos') - edge_feature.attribute( 'from_pos') assert 0 < rate <= 1 expression = QgsExpression("{fk_reach} = '{obj_id}'".format( fk_reach=fk_reach_field, obj_id=edge_feature['obj_id'])) print(expression.expression()) request = QgsFeatureRequest(expression) flow_time_feature = next(flow_layer.getFeatures(request)) if not flow_time_feature.isValid(): break flow_time += rate * flow_time_feature.attribute(flow_time_field) sf = QgsFeature() sf.setFields(fields) sf.setAttribute('flow_time', flow_time) sf.setGeometry(edge_feature.geometry()) sink.addFeature(sf, QgsFeatureSink.FastInsert) # f.setAttributes(attrs) # sink.addFeature(f, QgsFeatureSink.FastInsert) # feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id}
def __adjust(self): """ To look for adjustments and to display them """ self.__layers = self.__lineVertices(True) self.__adjustments = [] self.__altitudes = [] for p in range(len(self.__points)): pt = self.__points[p] x = pt['x'] y = pt['y'] z = pt['z'] num_lines = len(self.__selectedIds) drawdown = False level = None for layer in self.ownSettings.refLayers: laySettings = QgsSnappingUtils.LayerConfig(layer, QgsPointLocator.Vertex, self.SEARCH_TOLERANCE, QgsTolerance.LayerUnits) f_l = Finder.findClosestFeatureAt(self.toMapCoordinates(layer, QgsPoint(x, y)), self.canvas(), [laySettings]) if f_l is not None: feature = f_l[0] point_v2 = GeometryV2.asPointV2(feature.geometry(), self.__iface) if point_v2.z() > 0: if level is not None: if (level - point_v2.z()) > 0.005: self.__iface.messageBar().pushMessage( QCoreApplication.translate( "VDLTools", "More than one reference point, with 2 different elevations !!"), level=QgsMessageBar.CRITICAL, duration=0) self.__cancel() return level = point_v2.z() comp = QCoreApplication.translate("VDLTools", " (at invert)") if str(feature.attribute(self.ownSettings.levelAtt)) in self.ownSettings.levelVals: drawdown = True comp = QCoreApplication.translate("VDLTools", " (on pipe)") if point_v2.z() == 0: comp = QCoreApplication.translate("VDLTools", " (no elevation)") self.__adjustments.append({'point': p, 'previous': point_v2.z(), 'line': False, 'layer': f_l[1], 'comp': comp, 'feature': f_l[0], 'delta': False}) diam = 0 for i in range(num_lines): if z[i] is None: continue id_s = self.__selectedIds[i] feature = QgsFeature() self.ownSettings.drawdownLayer.getFeatures(QgsFeatureRequest().setFilterFid(id_s)).nextFeature(feature) dtemp = feature.attribute(self.ownSettings.pipeDiam)/1000 if dtemp > diam: diam = dtemp selected = None for f in self.ownSettings.drawdownLayer.selectedFeatures(): if f.id() == id_s: selected = f break self.__adjustments.append({'point': p, 'previous': z[i], 'line': True, 'layer': self.ownSettings.drawdownLayer, 'feature': selected, 'delta': True}) for layer in self.__layers: laySettings = QgsSnappingUtils.LayerConfig(layer, QgsPointLocator.Vertex, self.SEARCH_TOLERANCE, QgsTolerance.LayerUnits) f_l = Finder.findClosestFeatureAt(self.toMapCoordinates(layer, QgsPoint(x, y)), self.canvas(), [laySettings]) if f_l is None: z.append(None) else: if layer == self.ownSettings.drawdownLayer: f_ok = None if f_l[0].id() not in self.__selectedIds: f_ok = f_l[0] else: fs = Finder.findFeaturesAt(QgsPoint(x, y), laySettings, self) for f in fs: if f.id() not in self.__selectedIds: vertex = f.geometry().closestVertex(QgsPoint(x, y)) if vertex[4] < self.SEARCH_TOLERANCE: f_ok = f break if f_ok is not None: closest = f_ok.geometry().closestVertex(QgsPoint(x, y)) line, curved = GeometryV2.asLineV2(f_ok.geometry(), self.__iface) zp = line.zAt(closest[1]) dtemp = f_ok.attribute(self.ownSettings.pipeDiam) / 1000 if dtemp > diam: diam = dtemp self.__adjustments.append({'point': p, 'previous': zp, 'line': False, 'comp': QCoreApplication.translate("VDLTools", " connected"), 'feature': f_ok, 'layer': f_l[1], 'delta': True}) if zp is None or zp != zp: z.append(0) else: z.append(zp) else: z.append(None) else: zp = GeometryV2.asPointV2(f_l[0].geometry(), self.__iface).z() if zp is None or zp != zp: zp = 0 z.append(zp) if layer in self.ownSettings.adjLayers: self.__adjustments.append({'point': p, 'previous': zp, 'line': False, 'layer': f_l[1], 'feature': f_l[0], 'delta': True}) if level is not None: if drawdown: alt = level - diam else: alt = level else: alt = None dd = None if drawdown: dd = QCoreApplication.translate("VDLTools", "dradown") self.__altitudes.append({'diam': diam, 'drawdown': dd, 'alt': alt}) last = len(self.__altitudes)-1 self.__extras = [] for i in range(len(self.__altitudes)): if self.__altitudes[i]['alt'] is None: if 0 < i < last: prev_alt = self.__altitudes[i-1]['alt'] next_alt = self.__altitudes[i+1]['alt'] if prev_alt is not None and next_alt is not None: prev_pt = self.__points[i-1] next_pt = self.__points[i+1] pt = self.__points[i] d0 = Finder.sqrDistForCoords(pt['x'], prev_pt['x'], pt['y'], prev_pt['y']) d1 = Finder.sqrDistForCoords(next_pt['x'], pt['x'], next_pt['y'], pt['y']) inter_alt = old_div((d0*next_alt + d1*prev_alt), (d0 + d1)) self.__altitudes[i]['alt'] = round(inter_alt,2) self.__altitudes[i]['drawdown'] = "interpolation" elif i == 0 and len(self.__altitudes) > 2: alt1 = self.__altitudes[1]['alt'] alt2 = self.__altitudes[2]['alt'] if alt1 is not None and alt2 is not None: pt2 = self.__points[2] pt1 = self.__points[1] pt = self.__points[0] big_d = Finder.sqrDistForCoords(pt2['x'], pt1['x'], pt2['y'], pt1['y']) small_d = Finder.sqrDistForCoords(pt1['x'], pt['x'], pt1['y'], pt['y']) extra_alt = alt2 + (1 + old_div(small_d, big_d)) * (alt1 - alt2) alt = round(extra_alt, 2) if small_d < (old_div(big_d, 4)): self.__altitudes[i]['alt'] = alt self.__altitudes[i]['drawdown'] = "extrapolation" else: self.__extras.append([i, alt]) elif i == last and len(self.__altitudes) > 2: alt1 = self.__altitudes[i-1]['alt'] alt2 = self.__altitudes[i-2]['alt'] if alt1 is not None and alt2 is not None: pt2 = self.__points[i-2] pt1 = self.__points[i-1] pt = self.__points[i] big_d = Finder.sqrDistForCoords(pt2['x'], pt1['x'], pt2['y'], pt1['y']) small_d = Finder.sqrDistForCoords(pt1['x'], pt['x'], pt1['y'], pt['y']) extra_alt = alt2 + (1 + old_div(small_d, big_d)) * (alt1 - alt2) alt = round(extra_alt, 2) if small_d < (old_div(big_d, 4)): self.__altitudes[i]['alt'] = alt self.__altitudes[i]['drawdown'] = "extrapolation" else: self.__extras.append([i, alt]) if len(self.__extras) == 0: self.__setAdjustements() else: self.__checkForceExtrapolation()
def testDuplicateFeature(self): """ test duplicating a feature """ project = QgsProject().instance() # LAYERS # - add first layer (parent) layer1 = QgsVectorLayer("Point?field=fldtxt:string&field=pkid:integer", "parentlayer", "memory") # > check first layer (parent) self.assertTrue(layer1.isValid()) # - set the value for the copy layer1.setDefaultValueDefinition(1, QgsDefaultValue("rand(1000,2000)")) # > check first layer (parent) self.assertTrue(layer1.isValid()) # - add second layer (child) layer2 = QgsVectorLayer( "Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer", "childlayer", "memory") # > check second layer (child) self.assertTrue(layer2.isValid()) # - add layers project.addMapLayers([layer1, layer2]) # FEATURES # - add 2 features on layer1 (parent) l1f1orig = QgsFeature() l1f1orig.setFields(layer1.fields()) l1f1orig.setAttributes(["F_l1f1", 100]) l1f2orig = QgsFeature() l1f2orig.setFields(layer1.fields()) l1f2orig.setAttributes(["F_l1f2", 101]) # > check by adding features self.assertTrue(layer1.dataProvider().addFeatures([l1f1orig, l1f2orig])) # add 4 features on layer2 (child) l2f1orig = QgsFeature() l2f1orig.setFields(layer2.fields()) l2f1orig.setAttributes(["F_l2f1", 201, 100]) l2f2orig = QgsFeature() l2f2orig.setFields(layer2.fields()) l2f2orig.setAttributes(["F_l2f2", 202, 100]) l2f3orig = QgsFeature() l2f3orig.setFields(layer2.fields()) l2f3orig.setAttributes(["F_l2f3", 203, 100]) l2f4orig = QgsFeature() l2f4orig.setFields(layer2.fields()) l2f4orig.setAttributes(["F_l2f4", 204, 101]) # > check by adding features self.assertTrue(layer2.dataProvider().addFeatures( [l2f1orig, l2f2orig, l2f3orig, l2f4orig])) # RELATION # - create the relationmanager relMgr = project.relationManager() # - create the relation rel = QgsRelation() rel.setId('rel1') rel.setName('childrel') rel.setReferencingLayer(layer2.id()) rel.setReferencedLayer(layer1.id()) rel.addFieldPair('foreign_key', 'pkid') rel.setStrength(QgsRelation.Composition) # > check relation self.assertTrue(rel.isValid()) # - add relation relMgr.addRelation(rel) # > check if referencedLayer is layer1 self.assertEqual(rel.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(rel.referencingLayer(), layer2) # > check if the layers are correct in relation when loading from relationManager relations = project.relationManager().relations() relation = relations[list(relations.keys())[0]] # > check if referencedLayer is layer1 self.assertEqual(relation.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(relation.referencingLayer(), layer2) # > check the relatedfeatures ''' # testoutput 1 print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) print( "\n--------------------------") print( "\nFeatures on layer1") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2") for f in layer2.getFeatures(): print( f.attributes() ) ''' # DUPLICATION # - duplicate feature l1f1orig with children layer1.startEditing() results = QgsVectorLayerUtils.duplicateFeature(layer1, l1f1orig, project, 0) # > check if name is name of duplicated (pk is different) result_feature = results[0] self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # > check duplicated child layer result_layer = results[1].layers()[0] self.assertEqual(result_layer, layer2) # > check duplicated child features self.assertTrue(results[1].duplicatedFeatures(result_layer)) ''' # testoutput 2 print( "\nFeatures on layer1 (after duplication)") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2 (after duplication)") for f in layer2.getFeatures(): print( f.attributes() ) print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) ''' # > compare text of parent feature self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # - create copyValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(result_feature) copyValueList = [] while relfeatit.nextFeature(childFeature): copyValueList.append(childFeature.attribute('fldtxt')) # - create origValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(l1f1orig) origValueList = [] while relfeatit.nextFeature(childFeature): origValueList.append(childFeature.attribute('fldtxt')) # - check if the ids are still the same self.assertEqual(copyValueList, origValueList)
def testFidSupport(self): # We do not use @unittest.expectedFailure since the test might actually succeed # on Linux 64bit with GDAL 1.11, where "long" is 64 bit... # GDAL 2.0 is guaranteed to properly support it on all platforms version_num = int(gdal.VersionInfo('VERSION_NUM')) if version_num < GDAL_COMPUTE_VERSION(2, 0, 0): return tmpfile = os.path.join(self.basetestpath, 'testFidSupport.sqlite') ds = ogr.GetDriverByName('SQLite').CreateDataSource(tmpfile) lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint, options=['FID=fid']) lyr.CreateField(ogr.FieldDefn('strfield', ogr.OFTString)) lyr.CreateField(ogr.FieldDefn('intfield', ogr.OFTInteger)) f = ogr.Feature(lyr.GetLayerDefn()) f.SetFID(12) f.SetField(0, 'foo') f.SetField(1, 123) lyr.CreateFeature(f) f = None ds = None vl = QgsVectorLayer('{}'.format(tmpfile), 'test', 'ogr') self.assertEqual(len(vl.fields()), 3) got = [(f.attribute('fid'), f.attribute('strfield'), f.attribute('intfield')) for f in vl.getFeatures()] self.assertEqual(got, [(12, 'foo', 123)]) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "strfield = 'foo'"))] self.assertEqual(got, [(12, 'foo')]) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 12"))] self.assertEqual(got, [(12, 'foo')]) result = [ f['strfield'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest( ).setSubsetOfAttributes(['strfield'], vl.dataProvider().fields())) ] self.assertEqual(result, ['foo']) result = [ f['fid'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest( ).setSubsetOfAttributes(['fid'], vl.dataProvider().fields())) ] self.assertEqual(result, [12]) # Test that when the 'fid' field is not set, regular insertion is done f = QgsFeature() f.setFields(vl.fields()) f.setAttributes([None, 'automatic_id']) (res, out_f) = vl.dataProvider().addFeatures([f]) self.assertEqual(out_f[0].id(), 13) self.assertEqual(out_f[0].attribute('fid'), 13) self.assertEqual(out_f[0].attribute('strfield'), 'automatic_id') # Test that when the 'fid' field is set, it is really used to set the id f = QgsFeature() f.setFields(vl.fields()) f.setAttributes([9876543210, 'bar']) (res, out_f) = vl.dataProvider().addFeatures([f]) self.assertEqual(out_f[0].id(), 9876543210) self.assertEqual(out_f[0].attribute('fid'), 9876543210) self.assertEqual(out_f[0].attribute('strfield'), 'bar') got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'bar')]) self.assertTrue(vl.dataProvider().changeAttributeValues( {9876543210: { 1: 'baz' }})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baz')]) self.assertTrue(vl.dataProvider().changeAttributeValues( {9876543210: { 0: 9876543210, 1: 'baw' }})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baw')]) # Not allowed: changing the fid regular field self.assertTrue(vl.dataProvider().changeAttributeValues( {9876543210: { 0: 12, 1: 'baw' }})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baw')]) # Cannot delete fid self.assertFalse(vl.dataProvider().deleteAttributes([0])) # Delete first "genuine" attribute self.assertTrue(vl.dataProvider().deleteAttributes([1])) got = [(f.attribute('fid'), f.attribute('intfield')) for f in vl.dataProvider().getFeatures( QgsFeatureRequest().setFilterExpression("fid = 12"))] self.assertEqual(got, [(12, 123)])
class TOMsProposal(ProposalTypeUtilsMixin, QObject): def __init__(self, proposalsManager, proposalNr=None): QObject.__init__(self) TOMsMessageLog.logMessage("In TOMsProposal:init. ... ", level=Qgis.Info) self.proposalsManager = proposalsManager self.tableNames = self.proposalsManager.tableNames self.setProposalsLayer() TOMsMessageLog.logMessage( "In TOMsProposal:init. ... proposals layer set ", level=Qgis.Info) if proposalNr is not None: self.setProposal(proposalNr) def setProposalsLayer(self): self.proposalsLayer = self.tableNames.setLayer("Proposals") if self.proposalsLayer is None: TOMsMessageLog.logMessage( "In TOMsProposal:setProposalsLayer. Proposals layer NOT set !!!", level=Qgis.Info) return False else: idxProposalID = self.proposalsLayer.fields().indexFromName( "ProposalID") self.idxProposalTitle = self.proposalsLayer.fields().indexFromName( "ProposalTitle") self.idxCreateDate = self.proposalsLayer.fields().indexFromName( "ProposalCreateDate") self.idxOpenDate = self.proposalsLayer.fields().indexFromName( "ProposalOpenDate") self.idxProposalStatusID = self.proposalsLayer.fields( ).indexFromName("ProposalStatusID") TOMsMessageLog.logMessage("In TOMsProposal:setProposalsLayer... ", level=Qgis.Info) def setProposal(self, proposalID): self.thisProposalNr = proposalID self.setProposalsLayer() if (proposalID is not None): query = '\"ProposalID\" = {proposalID}'.format( proposalID=proposalID) request = QgsFeatureRequest().setFilterExpression(query) for proposal in self.proposalsLayer.getFeatures(request): self.thisProposal = proposal # make assumption that only one row return True return False # either not found or 0 def initialiseProposal(self): self.thisProposal = QgsFeature(self.proposalsLayer.fields()) self.thisProposal.setGeometry(QgsGeometry()) #self.proposalsLayer.addFeature(self.thisProposal) # TH (added for v3) """self.setProposalTitle('') #str(uuid.uuid4()) self.setProposalOpenDate = self.proposalsManager.date() self.setProposalCreateDate = self.proposalsManager.date() self.setProposalStatusID = ProposalStatus.IN_PREPARATION""" self.thisProposal[self.idxProposalTitle] = '' #str(uuid.uuid4()) self.thisProposal[self.idxCreateDate] = self.proposalsManager.date() self.thisProposal[self.idxOpenDate] = self.proposalsManager.date() self.thisProposal[ self.idxProposalStatusID] = ProposalStatus.IN_PREPARATION self.proposalsLayer.addFeature(self.thisProposal) # TH (added for v3) TOMsMessageLog.logMessage( "In TOMsProposal:createProposal - attributes: (fid=" + str(self.thisProposal.id()) + ") " + str(self.thisProposal.attributes()), level=Qgis.Info) return self def getProposal(self): return self def getProposalRecord(self): return self.thisProposal def getProposalNr(self): return self.thisProposalNr def getProposalTitle(self): return self.thisProposal.attribute("ProposalTitle") def setProposalTitle(self, value): return self.thisProposal.setAttribute("ProposalTitle", value) def getProposalStatusID(self): return self.thisProposal.attribute("ProposalStatusID") def setProposalStatusID(self, value): return self.thisProposal.setAttribute("ProposalStatusID", value) def getProposalOpenDate(self): return self.thisProposal.attribute("ProposalOpenDate") def setProposalOpenDate(self, value): return self.thisProposal.setAttribute("ProposalOpenDate", value) def getProposalCreateDate(self): return self.thisProposal.attribute("ProposalCreateDate") def setProposalCreateDate(self, value): return self.thisProposal.setAttribute("ProposalCreateDate", value) def createNewProposal(self): # TODO: pass def acceptProposal(self): currProposalID = self.thisProposalNr TOMsMessageLog.logMessage("In TOMsProposal.acceptProposal - " + str(self.thisProposalNr), level=Qgis.Info) """ Steps in acceptance are: 1. Set new open/close dates for restrictions ( remember to clear filter ) 2. update revision numbers for tiles 3. update Proposal details """ if self.thisProposalNr > 0: # need to consider a proposal for (currlayerID, currlayerName) in self.getRestrictionLayersList(): currLayer = self.tableNames.setLayer(currlayerName) restrictionList = [] restrictionList = self.__getRestrictionsInProposalForLayerForAction( currlayerID) # clear filter currFilter = self.tableNames.setLayer( currlayerName).subsetString() self.tableNames.setLayer(currlayerName).setSubsetString('') for currRestrictionID, currRestrictionInProposalDetails in restrictionList: currRestrictionInProposal = TOMsProposalElement( self.proposalsManager, currlayerID, None, currRestrictionID) status = currRestrictionInProposal.acceptActionOnProposalElement( currRestrictionInProposalDetails.attribute( "ActionOnProposalAcceptance") ) # Finding the correct action could go to ProposalElement if status == False: TOMsMessageLog.logMessage( "In TOMsProposal:acceptProposal. " + str(currRestrictionID) + " error on Action", level=Qgis.Info) return status # Now update tile revision nrs TOMsMessageLog.logMessage( "In TOMsProposal:acceptProposal. Updating tile revision nrs", level=Qgis.Info) proposalTileDictionary = self.getProposalTileDictionaryForDate() for tileNr, tile in proposalTileDictionary.items(): TOMsMessageLog.logMessage( "In TOMsProposal.acceptProposal: current tile " + str(tile["id"]) + " current RevisionNr: " + str(tile["RevisionNr"]) + " RevisionDate: " + str(tile["LastRevisionDate"]), level=Qgis.Info) currTile = TOMsTile(self.proposalsManager, tileNr) status = currTile.updateTileRevisionNr(currProposalID) if status == False: TOMsMessageLog.logMessage( "In TOMsProposal:acceptProposal. " + str(tileNr) + " error updating tile revision details", level=Qgis.Info) return status # Now update Proposal status = self.setProposalStatusID(ProposalStatus.ACCEPTED) #self.proposalsManager.newProposalCreated.emit(0) pass def rejectProposal(self): TOMsMessageLog.logMessage("In TOMsProposal.rejectProposal - " + str(self.thisProposalNr), level=Qgis.Info) status = self.setProposalStatusID(ProposalStatus.REJECTED) if status: TOMsMessageLog.logMessage( "In TOMsProposal.rejectProposal. Proposal Rejected ... ", level=Qgis.Info) #self.proposalsManager.newProposalCreated.emit(0) return status def getRestrictionsToOpenForLayer(self, layer): return self.__getRestrictionsListForLayerForAction( layer, RestrictionAction.OPEN) def getRestrictionsToCloseForLayer(self, layer): return self.__getRestrictionsListForLayerForAction( layer, RestrictionAction.CLOSE) def __getRestrictionsListForLayerForAction(self, layer, actionOnAcceptance=None): restrictionList = self.__getRestrictionsInProposalForLayerForAction( layer, actionOnAcceptance) return ",".join("'{restrictionID}'".format(restrictionID=restrictionID) for restrictionID, _ in restrictionList) def __getRestrictionsInProposalForLayerForAction(self, layerID, actionOnAcceptance=None): # Will return a list of restrictions within a Proposal subject to actionOnAcceptance self.RestrictionsInProposalsLayer = self.tableNames.setLayer( "RestrictionsInProposals") query = ( "\"ProposalID\" = {proposalID} AND \"RestrictionTableID\" = {layerID}" ).format(proposalID=str(self.thisProposalNr), layerID=str(layerID)) if actionOnAcceptance is not None: query = ( "{query} AND \"ActionOnProposalAcceptance\" = {actionOnAcceptance}" ).format(query=query, actionOnAcceptance=str(actionOnAcceptance)) TOMsMessageLog.logMessage( "In __getRestrictionsInProposalForLayerForAction. query: " + str(query), level=Qgis.Info) request = QgsFeatureRequest().setFilterExpression(query) restrictionList = [] for restrictionInProposalDetails in self.RestrictionsInProposalsLayer.getFeatures( request): restrictionList.append([ restrictionInProposalDetails["RestrictionID"], restrictionInProposalDetails ]) return restrictionList def getProposalBoundingBox(self): # Need to remember that filters are in operation, so need to ensure that Restriction features are available TOMsMessageLog.logMessage("In getProposalBoundingBox.", level=Qgis.Info) currProposalID = self.thisProposalNr geometryBoundingBox = QgsRectangle() if currProposalID > 0: # need to consider a proposal for (layerID, layerName) in self.getRestrictionLayersList(): currLayer = self.tableNames.setLayer(layerName) restrictionStr = self.__getRestrictionsListForLayerForAction( layerID) #TOMsMessageLog.logMessage("In getProposalBoundingBox. (" + layerName + ") request:" + restrictionStr, level=Qgis.Info) #currLayer.blockSignals(True) # unset filter to get geometries of closed features layerFilterString = currLayer.subsetString() TOMsMessageLog.logMessage("In getProposalBoundingBox. (" + layerName + ") filter 1:" + layerFilterString, level=Qgis.Info) currLayer.setSubsetString(None) query = '"RestrictionID" IN ({restrictions})'.format( restrictions=restrictionStr) request = QgsFeatureRequest().setFilterExpression(query) for currRestriction in currLayer.getFeatures(request): geometryBoundingBox.combineExtentWith( currRestriction.geometry().boundingBox()) currLayer.setSubsetString(layerFilterString) #currLayer.blockSignals(False) TOMsMessageLog.logMessage("In getProposalBoundingBox. (" + currLayer.name() + ") filter 1:" + currLayer.subsetString(), level=Qgis.Info) return geometryBoundingBox def getProposalTileDictionaryForDate(self, revisionDate=None): if not revisionDate: revisionDate = self.proposalsManager.date() # returns list of tiles in the proposal and their current revision numbers TOMsMessageLog.logMessage( "In TOMsProposal.getProposalTileDictionaryForDate. considering Proposal: " + str(self.getProposalNr()) + " for " + str(revisionDate), level=Qgis.Info) dictTilesInProposal = dict() # Logic is: #Loop through each map tile # Check whether or not there are any currently open restrictions within it if self.getProposalNr() > 0: # need to consider a proposal # loop through all the layers that might have restrictions for (layerID, layerName) in self.getRestrictionLayersList(): # clear filter currFilter = self.tableNames.setLayer(layerName).subsetString() self.tableNames.setLayer(layerName).setSubsetString('') for (currRestrictionID, restrictionInProposalObject ) in self.__getRestrictionsInProposalForLayerForAction( layerID): currRestriction = ProposalElementFactory.getProposalElement( self.proposalsManager, layerID, None, currRestrictionID) dictTilesInProposal.update( currRestriction.getTilesForRestriction(revisionDate)) # reset filter self.tableNames.setLayer(layerName).setSubsetString(currFilter) else: """ # *** Unfortunately, this takes too long ... best to just look at Tiles and check current versions *** # loop through all the layers that might have restrictions for (layerID, layerName) in self.getRestrictionLayersList(): # clear filter currFilter = self.tableNames.setLayer(layerName).subsetString() self.tableNames.setLayer(layerName).setSubsetString('') for (currRestrictionID, currRestriction) in self.proposalsManager.getCurrentRestrictionsForLayerAtDate(layerID, revisionDate): currRestriction = ProposalElementFactory.getProposalElement(self.proposalsManager, layerID, None, currRestrictionID) dictTilesInProposal.update(currRestriction.getTilesForRestriction(revisionDate)) # reset filter self.tableNames.setLayer(layerName).setSubsetString(currFilter)""" currTileObject = TOMsTile(self.proposalsManager) for currTileRecord in self.tableNames.setLayer( "MapGrid").getFeatures(): TOMsMessageLog.logMessage( "In TOMsProposal.getProposalTileDictionaryForDate. Current. Tile: " + str(currTileRecord.attribute("id")), level=Qgis.Info) status = currTileObject.setTile(currTileRecord.attribute("id")) lastRevisionNr, lastProposalOpendate = currTileObject.getTileRevisionNrAtDate( revisionDate) if lastRevisionNr is not None: # the assumption is that tiles without restrictions have a NULL revision number dictTilesInProposal[ currTileObject.thisTileNr] = currTileRecord for tileNr, tile in dictTilesInProposal.items(): TOMsMessageLog.logMessage( "In TOMsProposal.getProposalTileDictionaryForDate: " + str(tile["id"]) + " RevisionNr: " + str(tile["RevisionNr"]) + " RevisionDate: " + str(tile["LastRevisionDate"]), level=Qgis.Info) return dictTilesInProposal def updateTileRevisionNrsInProposal(self, dictTilesInProposal): TOMsMessageLog.logMessage( "In TOMsProposal:updateTileRevisionNrsInProposal.", level=Qgis.Info) # Increment the relevant tile numbers currRevisionDate = self.getProposalOpenDate() MapGridLayer = self.tableNames.setLayer("MapGrid") TilesInAcceptedProposalsLayer = self.tableNames.setLayer( "TilesInAcceptedProposals") # self.getProposalTileList(currProposalID, currRevisionDate) currTile = TOMsTile(self.proposalsManager) for tileNr, tile in dictTilesInProposal.items(): status = currTile.setTile(tileNr) # TODO: Create a tile object and have increment method ... lastRevisionNr, lastProposalOpendate = currTile.getTileRevisionNrAtDate( currRevisionDate) currRevisionNr = currTile["RevisionNr"] TOMsMessageLog.logMessage("In updateTileRevisionNrs. tile" + str(tileNr) + " currRevNr: " + str(currRevisionNr), level=Qgis.Info) if currRevisionNr is None: MapGridLayer.changeAttributeValue( currTile.id(), MapGridLayer.fields().indexFromName("RevisionNr"), 1) else: MapGridLayer.changeAttributeValue( currTile.id(), MapGridLayer.fields().indexFromName("RevisionNr"), currRevisionNr + 1) MapGridLayer.changeAttributeValue( currTile.id(), MapGridLayer.fields().indexFromName("LastRevisionDate"), self.getProposalOpenDate()) # Now need to add the details of this tile to "TilesWithinAcceptedProposals" (including revision numbers at time of acceptance) newRecord = QgsFeature(TilesInAcceptedProposalsLayer.fields()) idxProposalID = TilesInAcceptedProposalsLayer.fields( ).indexFromName("ProposalID") idxTileNr = TilesInAcceptedProposalsLayer.fields().indexFromName( "TileNr") idxRevisionNr = TilesInAcceptedProposalsLayer.fields( ).indexFromName("RevisionNr") newRecord[idxProposalID] = self.thisProposalNr() newRecord[idxTileNr] = tileNr newRecord[idxRevisionNr] = currRevisionNr + 1 newRecord.setGeometry(QgsGeometry()) status = TilesInAcceptedProposalsLayer.addFeature(newRecord) if not status: return status # TODO: Check return status from add return status
def testFidSupport(self): tmpfile = os.path.join(self.basetestpath, 'testFidSupport.sqlite') ds = ogr.GetDriverByName('SQLite').CreateDataSource(tmpfile) lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint, options=['FID=fid']) lyr.CreateField(ogr.FieldDefn('strfield', ogr.OFTString)) lyr.CreateField(ogr.FieldDefn('intfield', ogr.OFTInteger)) f = ogr.Feature(lyr.GetLayerDefn()) f.SetFID(12) f.SetField(0, 'foo') f.SetField(1, 123) lyr.CreateFeature(f) f = None ds = None vl = QgsVectorLayer('{}'.format(tmpfile), 'test', 'ogr') self.assertEqual(len(vl.fields()), 3) got = [(f.attribute('fid'), f.attribute('strfield'), f.attribute('intfield')) for f in vl.getFeatures()] self.assertEqual(got, [(12, 'foo', 123)]) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "strfield = 'foo'"))] self.assertEqual(got, [(12, 'foo')]) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 12"))] self.assertEqual(got, [(12, 'foo')]) result = [ f['strfield'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest( ).setSubsetOfAttributes(['strfield'], vl.dataProvider().fields())) ] self.assertEqual(result, ['foo']) result = [ f['fid'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest( ).setSubsetOfAttributes(['fid'], vl.dataProvider().fields())) ] self.assertEqual(result, [12]) # Test that when the 'fid' field is not set, regular insertion is done f = QgsFeature() f.setFields(vl.fields()) f.setAttributes([None, 'automatic_id']) (res, out_f) = vl.dataProvider().addFeatures([f]) self.assertEqual(out_f[0].id(), 13) self.assertEqual(out_f[0].attribute('fid'), 13) self.assertEqual(out_f[0].attribute('strfield'), 'automatic_id') # Test that when the 'fid' field is set, it is really used to set the id f = QgsFeature() f.setFields(vl.fields()) f.setAttributes([9876543210, 'bar']) (res, out_f) = vl.dataProvider().addFeatures([f]) self.assertEqual(out_f[0].id(), 9876543210) self.assertEqual(out_f[0].attribute('fid'), 9876543210) self.assertEqual(out_f[0].attribute('strfield'), 'bar') got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'bar')]) self.assertTrue(vl.dataProvider().changeAttributeValues( {9876543210: { 1: 'baz' }})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baz')]) self.assertTrue(vl.dataProvider().changeAttributeValues( {9876543210: { 0: 9876543210, 1: 'baw' }})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baw')]) # Not allowed: changing the fid regular field self.assertFalse(vl.dataProvider().changeAttributeValues( {9876543210: { 0: 12, 1: 'baw' }})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression( "fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baw')]) # Cannot delete fid self.assertFalse(vl.dataProvider().deleteAttributes([0])) # Delete first "genuine" attribute self.assertTrue(vl.dataProvider().deleteAttributes([1])) got = [(f.attribute('fid'), f.attribute('intfield')) for f in vl.dataProvider().getFeatures( QgsFeatureRequest().setFilterExpression("fid = 12"))] self.assertEqual(got, [(12, 123)])
def _test(autoTransaction): """Test buffer methods within and without transactions - create a feature - save - retrieve the feature - change geom and attrs - test changes are seen in the buffer """ def _check_feature(wkt): f = next(layer_a.getFeatures()) self.assertEqual(f.geometry().asWkt().upper(), wkt) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.geometry().asWkt().upper(), wkt) ml = QgsVectorLayer('Point?crs=epsg:4326&field=int:integer', 'test', 'memory') self.assertTrue(ml.isValid()) d = QTemporaryDir() options = QgsVectorFileWriter.SaveVectorOptions() options.driverName = 'GPKG' options.layerName = 'layer_a' err, _ = QgsVectorFileWriter.writeAsVectorFormatV2(ml, os.path.join(d.path(), 'transaction_test.gpkg'), QgsCoordinateTransformContext(), options) self.assertEqual(err, QgsVectorFileWriter.NoError) self.assertTrue(os.path.isfile(os.path.join(d.path(), 'transaction_test.gpkg'))) options.layerName = 'layer_b' options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer err, _ = QgsVectorFileWriter.writeAsVectorFormatV2(ml, os.path.join(d.path(), 'transaction_test.gpkg'), QgsCoordinateTransformContext(), options) layer_a = QgsVectorLayer(os.path.join(d.path(), 'transaction_test.gpkg|layername=layer_a')) self.assertTrue(layer_a.isValid()) project = QgsProject() project.setAutoTransaction(autoTransaction) project.addMapLayers([layer_a]) ########################################### # Tests with a new feature self.assertTrue(layer_a.startEditing()) buffer = layer_a.editBuffer() f = QgsFeature(layer_a.fields()) f.setAttribute('int', 123) f.setGeometry(QgsGeometry.fromWkt('point(7 45)')) self.assertTrue(layer_a.addFeatures([f])) _check_feature('POINT (7 45)') # Need to fetch the feature because its ID is NULL (-9223372036854775808) f = next(layer_a.getFeatures()) self.assertEqual(len(buffer.addedFeatures()), 1) layer_a.undoStack().undo() self.assertEqual(len(buffer.addedFeatures()), 0) layer_a.undoStack().redo() self.assertEqual(len(buffer.addedFeatures()), 1) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.attribute('int'), 123) # Now change attribute self.assertEqual(buffer.changedAttributeValues(), {}) layer_a.changeAttributeValue(f.id(), 1, 321) self.assertEqual(len(buffer.addedFeatures()), 1) # This is surprising: because it was a new feature it has been changed directly self.assertEqual(buffer.changedAttributeValues(), {}) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.attribute('int'), 321) layer_a.undoStack().undo() self.assertEqual(buffer.changedAttributeValues(), {}) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.attribute('int'), 123) f = next(layer_a.getFeatures()) self.assertEqual(f.attribute('int'), 123) # Change geometry f = next(layer_a.getFeatures()) self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)'))) _check_feature('POINT (9 43)') self.assertEqual(buffer.changedGeometries(), {}) layer_a.undoStack().undo() _check_feature('POINT (7 45)') self.assertEqual(buffer.changedGeometries(), {}) self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)'))) _check_feature('POINT (9 43)') self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(10 44)'))) _check_feature('POINT (10 44)') # This is anothr surprise: geometry edits get collapsed into a single # one because they have the same hardcoded id layer_a.undoStack().undo() _check_feature('POINT (7 45)') self.assertTrue(layer_a.commitChanges()) ########################################### # Tests with the existing feature # Get the feature f = next(layer_a.getFeatures()) self.assertTrue(f.isValid()) self.assertEqual(f.attribute('int'), 123) self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)') self.assertTrue(layer_a.startEditing()) layer_a.changeAttributeValue(f.id(), 1, 321) buffer = layer_a.editBuffer() self.assertEqual(buffer.changedAttributeValues(), {1: {1: 321}}) layer_a.undoStack().undo() self.assertEqual(buffer.changedAttributeValues(), {}) # Change geometry self.assertTrue(layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)'))) f = next(layer_a.getFeatures()) self.assertEqual(f.geometry().asWkt().upper(), 'POINT (9 43)') self.assertEqual(buffer.changedGeometries()[1].asWkt().upper(), 'POINT (9 43)') layer_a.undoStack().undo() self.assertEqual(buffer.changedGeometries(), {}) f = next(layer_a.getFeatures()) self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)') self.assertEqual(buffer.changedGeometries(), {}) # Delete an existing feature self.assertTrue(layer_a.deleteFeature(f.id())) with self.assertRaises(StopIteration): next(layer_a.getFeatures()) self.assertEqual(buffer.deletedFeatureIds(), [f.id()]) layer_a.undoStack().undo() self.assertTrue(layer_a.getFeature(f.id()).isValid()) self.assertEqual(buffer.deletedFeatureIds(), []) ########################################### # Test delete # Delete a new feature f = QgsFeature(layer_a.fields()) f.setAttribute('int', 555) f.setGeometry(QgsGeometry.fromWkt('point(8 46)')) self.assertTrue(layer_a.addFeatures([f])) f = [f for f in layer_a.getFeatures() if f.attribute('int') == 555][0] self.assertTrue(f.id() in buffer.addedFeatures()) self.assertTrue(layer_a.deleteFeature(f.id())) self.assertFalse(f.id() in buffer.addedFeatures()) self.assertFalse(f.id() in buffer.deletedFeatureIds()) layer_a.undoStack().undo() self.assertTrue(f.id() in buffer.addedFeatures()) ########################################### # Add attribute field = QgsField('attr1', QVariant.String) self.assertTrue(layer_a.addAttribute(field)) self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1) self.assertEqual(buffer.addedAttributes(), [field]) layer_a.undoStack().undo() self.assertEqual(layer_a.fields().lookupField(field.name()), -1) self.assertEqual(buffer.addedAttributes(), []) layer_a.undoStack().redo() self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1) self.assertEqual(buffer.addedAttributes(), [field]) self.assertTrue(layer_a.commitChanges()) ########################################### # Remove attribute self.assertTrue(layer_a.startEditing()) buffer = layer_a.editBuffer() attr_idx = layer_a.fields().lookupField(field.name()) self.assertNotEqual(attr_idx, -1) self.assertTrue(layer_a.deleteAttribute(attr_idx)) self.assertEqual(buffer.deletedAttributeIds(), [2]) self.assertEqual(layer_a.fields().lookupField(field.name()), -1) layer_a.undoStack().undo() self.assertEqual(buffer.deletedAttributeIds(), []) self.assertEqual(layer_a.fields().lookupField(field.name()), attr_idx) layer_a.undoStack().redo() self.assertEqual(buffer.deletedAttributeIds(), [2]) self.assertEqual(layer_a.fields().lookupField(field.name()), -1) self.assertTrue(layer_a.rollBack()) ########################################### # Rename attribute self.assertTrue(layer_a.startEditing()) attr_idx = layer_a.fields().lookupField(field.name()) self.assertNotEqual(attr_idx, -1) self.assertEqual(layer_a.fields().lookupField('new_name'), -1) self.assertTrue(layer_a.renameAttribute(attr_idx, 'new_name')) self.assertEqual(layer_a.fields().lookupField('new_name'), attr_idx) layer_a.undoStack().undo() self.assertEqual(layer_a.fields().lookupField(field.name()), attr_idx) self.assertEqual(layer_a.fields().lookupField('new_name'), -1) layer_a.undoStack().redo() self.assertEqual(layer_a.fields().lookupField('new_name'), attr_idx) self.assertEqual(layer_a.fields().lookupField(field.name()), -1)
def run(self): self.mutex.lock() self.stopMe = 0 self.mutex.unlock() interrupted = False polyProvider = self.layerPoly.dataProvider() pointProvider = self.layerPoints.dataProvider() fieldList = ftools_utils.getFieldList(self.layerPoly) index = polyProvider.fieldNameIndex(unicode(self.fieldName)) if index == -1: index = polyProvider.fields().count() fieldList.append(QgsField(unicode(self.fieldName), QVariant.Int, "int", 10, 0, self.tr("point count field"))) # Add the selected vector fields to the output polygon vector layer selectedItems = self.attributeList.selectedItems() for item in selectedItems: global typeDouble columnName = unicode(item.text() + "_" + self.statistics) index = polyProvider.fieldNameIndex(unicode(columnName)) if index == -1: if item.type() == typeDouble or self.statistics == "mean" or self.statistics == "stddev": fieldList.append(QgsField(columnName, QVariant.Double, "double", 24, 15, "Value")) else: fieldList.append(QgsField(columnName, QVariant.Int, "int", 10, 0, "Value")) sRs = polyProvider.crs() if QFile(self.outPath).exists(): if not QgsVectorFileWriter.deleteShapeFile(self.outPath): return writer = QgsVectorFileWriter(self.outPath, self.encoding, fieldList, polyProvider.geometryType(), sRs) spatialIndex = ftools_utils.createIndex(pointProvider) self.emit(SIGNAL("rangeChanged(int)"), polyProvider.featureCount()) polyFeat = QgsFeature() pntFeat = QgsFeature() outFeat = QgsFeature() inGeom = QgsGeometry() polyFit = polyProvider.getFeatures() while polyFit.nextFeature(polyFeat): inGeom = polyFeat.geometry() atMap = polyFeat.attributes() outFeat.setAttributes(atMap) outFeat.setGeometry(inGeom) count = 0 pointList = [] hasIntersection = True pointList = spatialIndex.intersects(inGeom.boundingBox()) if len(pointList) > 0: hasIntersection = True else: hasIntersection = False if hasIntersection: valueList = {} for item in selectedItems: valueList[item.text()] = [] for p in pointList: pointProvider.getFeatures(QgsFeatureRequest().setFilterFid(p)).nextFeature(pntFeat) tmpGeom = QgsGeometry(pntFeat.geometry()) if inGeom.intersects(tmpGeom): count += 1 for item in selectedItems: valueList[item.text()].append(pntFeat.attribute(item.text())) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break atMap.append(count) # Compute the statistical values for selected vector attributes for item in selectedItems: values = valueList[item.text()] # Check if the input contains non-numeric values non_numeric_values = False for value in values: if not isinstance(value, type(float())) and not isinstance(value, type(int())): non_numeric_values = True break # Jump over invalid values if non_numeric_values is True: continue if values and len(values) > 0: if self.statistics == "sum": value = reduce(myAdder, values) elif self.statistics == "mean": value = reduce(myAdder, values) / float(len(values)) elif self.statistics == "min": values.sort() value = values[0] elif self.statistics == "max": values.sort() value = values[-1] elif self.statistics == "stddev": value = two_pass_variance(values) value = math.sqrt(value) atMap.append(value) else: # no intersection - store at least the zero count atMap.append(0) outFeat.setAttributes(atMap) writer.addFeature(outFeat) self.emit(SIGNAL("updateProgress()")) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break del writer if not interrupted: self.emit(SIGNAL("processingFinished()")) else: self.emit(SIGNAL("processingInterrupted()"))
def processAlgorithm(self, parameters, context, feedback): # Constants (probably parameters in future) JUNCTION_WIDTH = 4 ROAD_WIDTH = 1 CLUSTER_THRESHOLD = 1 LOOP_LINK_THRESHOLD = 2.5 # should be min. 2 as loops can cross junction and back CIRCLE_APPROX_SEGMENTS = 16 OUTPUT_DEBUG_NODE_BUFFERS = False POINT_ERROR_BUFFER_RADIUS = 10 # Retrieve the feature source and sink. The 'dest_id' variable is used # to uniquely identify the feature sink, and must be included in the # dictionary returned by the processAlgorithm function. source = self.parameterAsSource(parameters, self.INPUT, context) if source is None: raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) crossing_source = self.parameterAsSource(parameters, self.INPUT_POINTS, context) crossing_fields = [] crossing_points_to_features = {} if crossing_source is not None: crossing_fields = [QgsField(f) for f in crossing_source.fields()] for cf in crossing_fields: cf.setName(self.CROSSING_INPUT_FIELD_PREFIX + cf.name()) feedback.pushInfo("Reading crossings") total = 100.0 / crossing_source.featureCount( ) if crossing_source.featureCount() else 0 features = crossing_source.getFeatures() for current, in_feature in enumerate(features): if feedback.isCanceled(): break geom = in_feature.geometry() if not geom.convertToSingleType(): feedback.pushInfo( "Error: Multipoint found in crossings layer") raise Exception("Multipoint found in crossings layer") crossing_points_to_features[geom.asPoint()] = in_feature feedback.setProgress(int(current * total)) outfields = QgsFields() for f in source.fields(): outfields.append(f) for f in crossing_fields: outfields.append(f) outfields.append( QgsField(self.SIDEWALK_FIELD_NAME, QVariant.String, len=1)) outfields.append( QgsField(self.SIDEWALK_METRIC_FIELD_NAME, QVariant.Double, "double", 10, 3)) outfields.append( QgsField(self.SIDEWALK_WEIGHT_FIELD_NAME, QVariant.Double, "double", 10, 3)) outfields.append(QgsField(self.ID_FIELD_NAME, QVariant.Int)) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, outfields, source.wkbType(), source.sourceCrs()) errfields = QgsFields() errfields.append(QgsField("Error", QVariant.String)) errfields.append(QgsField("Details", QVariant.String)) (err_sink, err_dest_id) = self.parameterAsSink(parameters, self.ERROR_OUTPUT, context, errfields, source.wkbType(), source.sourceCrs()) if sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.OUTPUT)) if err_sink is None: raise QgsProcessingException( self.invalidSinkError(parameters, self.ERROR_OUTPUT)) def add_error(feature, message, details=""): if OUTPUT_DEBUG_NODE_BUFFERS: feedback.pushInfo(f"{message}{details}: {feature}") new_feature = QgsFeature() new_feature.setGeometry(feature.geometry()) new_feature.setAttributes([message, details]) err_sink.addFeature(new_feature, QgsFeatureSink.FastInsert) def add_point_error(point, message, details=""): buffer = QgsGeometry.fromPointXY(point).buffer( POINT_ERROR_BUFFER_RADIUS, CIRCLE_APPROX_SEGMENTS) feature = QgsFeature() feature.setGeometry(buffer) add_error(feature, message, details) START, END = False, True LinkEnd = namedtuple("LinkEnd", ["id", "end"]) def linkend_to_point(linkend): line = link_id_to_feature[linkend.id].geometry().asPolyline() return line[0] if linkend.end == START else line[-1] node_coords_to_link_ends = defaultdict( list) #(x,y ) as index, list of original link ends as contents link_id_to_feature = { } # link id (tuple of (number,type)) for index, feature as contents loop_link_ids = set() LINK_TYPE_ORIGINAL, LINK_TYPE_LEFT, LINK_TYPE_RIGHT, LINK_TYPE_OTHER, LINK_TYPE_BUFFER = list( range(5)) feedback.pushInfo("Reading links and building node graph") total = 100.0 / source.featureCount() if source.featureCount() else 0 features = source.getFeatures() for current, in_feature in enumerate(features): if feedback.isCanceled(): break geom = in_feature.geometry() if not geom.convertToSingleType(): add_error(feature, "Multipart line") continue feature = QgsFeature() feature.setFields(outfields) for name in in_feature.fields().names(): feature.setAttribute(name, in_feature.attribute(name)) feature.setGeometry(geom) points = geom.asPolyline() linkid = (current, LINK_TYPE_ORIGINAL) link_id_to_feature[linkid] = feature endpoints = [(START, points[0]), (END, points[-1])] if points[0] == points[-1]: loop_link_ids.add(current) for end, point in endpoints: le = LinkEnd(linkid, end) node_coords_to_link_ends[point] += [le] feedback.setProgress(int(current * total)) next_index_for_split_link_id = current + 1 def break_link_on_first_crossing_found(link_id): nonlocal next_index_for_split_link_id, node_coords_to_link_ends, link_id_to_feature, outfields, loop_link_ids feature = link_id_to_feature[link_id] geom = feature.geometry() points = geom.asPolyline() for point in points[1:-1]: if point in crossing_points_to_features: # break line, splitting extra part to new feature index = points.index(point) points1 = points[0:index + 1] points2 = points[index:] feature.setGeometry(QgsGeometry.fromPolylineXY(points1)) newfeature = QgsFeature() newfeature.setFields(outfields) newfeature.setAttributes(feature.attributes()) newfeature.setGeometry(QgsGeometry.fromPolylineXY(points2)) new_feature_id = (next_index_for_split_link_id, LINK_TYPE_ORIGINAL) next_index_for_split_link_id += 1 link_id_to_feature[new_feature_id] = newfeature # update node_coords_link_ends for the split point and end point (start point remains the same) if point in node_coords_to_link_ends: add_point_error( point, "Crossing on both link and node is ambiguous") node_coords_to_link_ends[point] += [ LinkEnd(link_id, END), LinkEnd(new_feature_id, START) ] end_link_end_list = node_coords_to_link_ends[points[-1]] end_link_end_list.remove(LinkEnd(link_id, END)) end_link_end_list += [LinkEnd(new_feature_id, END)] if link_id[0] in loop_link_ids: loop_link_ids.remove(link_id[0]) if points1[0] == points1[-1]: loop_link_ids.add(new_feature_id[0]) for f in [feature, newfeature]: if point in feature.geometry().asPolyline()[1:-1]: add_error( feature, "Crossing intersects at multiple points") return new_feature_id return False feedback.pushInfo("Splitting links with crossings inside") total = 100.0 / len(link_id_to_feature) if len( link_id_to_feature) else 0 for current, link_id in enumerate(list( link_id_to_feature.keys())): # copy the keys if feedback.isCanceled(): break link_id_to_try_splitting = link_id while link_id_to_try_splitting: link_id_to_try_splitting = break_link_on_first_crossing_found( link_id_to_try_splitting) feedback.setProgress(int(current * total)) feedback.pushInfo("Matching crossings to link ends") linkend_to_crossing_feature = {} crossing_points_matched = set() total = 100.0 / len(link_id_to_feature) if len( link_id_to_feature) else 0 for current, (link_id, feature) in enumerate(link_id_to_feature.items()): if feedback.isCanceled(): break points = feature.geometry().asPolyline() endpoints = [(START, points[0]), (END, points[-1])] for end, point in endpoints: if point in crossing_points_to_features: crossing_points_matched.add(point) linkend_to_crossing_feature[LinkEnd( link_id, end)] = crossing_points_to_features[point] feedback.setProgress(int(current * total)) for point in crossing_points_to_features: if point not in crossing_points_matched: add_point_error(point, "Unmatched crossing") feedback.pushInfo("Testing node distances") total = 100.0 / len(node_coords_to_link_ends) if len( node_coords_to_link_ends) else 0 node_neighbours = defaultdict(list) for current, n1 in enumerate(node_coords_to_link_ends.keys()): if feedback.isCanceled(): break for n2 in node_coords_to_link_ends.keys(): if n1.distance(n2) <= JUNCTION_WIDTH * CLUSTER_THRESHOLD: node_neighbours[n1] += [n2] node_neighbours[n2] += [n1] feedback.setProgress(int(current * total)) feedback.pushInfo("Computing node clusters") node_clusters = [] while node_neighbours: node, searchlist = node_neighbours.popitem() cluster = set() cluster.add(node) while searchlist: node = searchlist.pop() if node in node_neighbours: neighbours = node_neighbours.pop(node) for n in neighbours: if n not in cluster: cluster.add(n) searchlist += [n] node_clusters += [cluster] feedback.pushInfo("Merging node clusters") def get_node_buffer(point): return QgsGeometry.fromPointXY(point).buffer( JUNCTION_WIDTH / 2, CIRCLE_APPROX_SEGMENTS) old_to_new_node_points = {} linkends_that_got_nodes_moved = set() total = 100.0 / len(node_clusters) if len(node_clusters) else 0 for current, cluster in enumerate(node_clusters): if feedback.isCanceled(): break cluster_arr = np.array([[p.x(), p.y()] for p in cluster]) if (cluster_arr[0] == cluster_arr[1:]).all(): continue # these links already share the same endpoints newx, newy = np.mean(cluster_arr, axis=0) new_node_point = QgsPointXY(newx, newy) cluster_linkends = [] for node in cluster: assert node in node_coords_to_link_ends cluster_linkends += node_coords_to_link_ends[node] del node_coords_to_link_ends[node] old_to_new_node_points[node] = new_node_point # stretch the links to meet the new cluster for linkend in cluster_linkends: feature = link_id_to_feature[linkend.id] points = feature.geometry().asPolyline() if linkend.end == START: points.insert(0, new_node_point) else: assert linkend.end == END points.append(new_node_point) feature.setGeometry(QgsGeometry.fromPolylineXY(points)) for le in cluster_linkends: linkends_that_got_nodes_moved.add(le) # delete any links # (a) wholly inside the cluster # (b) with both ends in the cluster unless they're (1) originally loop links or (2) long loop links not present originally, in which case warn links_to_delete = [] for le1, le2 in combinations(cluster_linkends, 2): if le1.id == le2.id: geom = link_id_to_feature[le1.id].geometry() assert le1.end != le2.end node_buffer = get_node_buffer(new_node_point) trimmed_linkgeom = geom.difference(node_buffer) if trimmed_linkgeom.isEmpty(): links_to_delete += [le1.id] elif le1.id[0] not in loop_link_ids: if geom.length( ) < LOOP_LINK_THRESHOLD * CLUSTER_THRESHOLD * JUNCTION_WIDTH: links_to_delete += [le1.id] else: add_error(link_id_to_feature[le1.id], "Loop link formed by node merge", f"{le1}") loop_link_ids.add(le1.id[0]) for link in links_to_delete: for end in [START, END]: cluster_linkends.remove(LinkEnd(link, end)) assert all([le.id != link for le in cluster_linkends]) del link_id_to_feature[link] node_coords_to_link_ends[new_node_point] = cluster_linkends feedback.setProgress(int(current * total)) feedback.pushInfo("Computing radial orderings") node_link_ordering = {} total = 100.0 / len(node_coords_to_link_ends) if len( node_coords_to_link_ends) else 0 for current, node_point in enumerate(node_coords_to_link_ends): if feedback.isCanceled(): break link_end_list = node_coords_to_link_ends[node_point] node_buffer = get_node_buffer(node_point) bearing_and_link_end_pairs_list = [] for le in link_end_list: link = link_id_to_feature[le.id] linkgeom = link.geometry() trimmed_linkgeom = linkgeom.difference(node_buffer) if trimmed_linkgeom.isMultipart(): trimmed_linkgeom = QgsGeometry.fromPolyline( list(trimmed_linkgeom.constParts())[0] ) # this may be wrong but the error is flagged up later pl = trimmed_linkgeom.asPolyline() endpoint_near_node = pl[0] if le.end == START else pl[-1] bearing_and_link_end_pairs_list += [ (node_point.azimuth(endpoint_near_node), le) ] bearing_and_link_end_pairs_list.sort() node_link_ordering[node_point] = bearing_and_link_end_pairs_list feedback.pushInfo("Dividing links") nodes_of_divided_links = set() divided_linkends = {} total = 100.0 / source.featureCount() if source.featureCount() else 0 link_ids_to_process = list(link_id_to_feature.keys()) for current, orig_id in enumerate(link_ids_to_process): if feedback.isCanceled(): break feature = link_id_to_feature[orig_id] if feature.attribute(self.DIVIDE_FIELDNAME): left, right = self.divide(feature, ROAD_WIDTH / 2, outfields) # these tuple ids are used later to identify that left and right originated from the same feature orig_index, _ = orig_id left_id = (orig_index, LINK_TYPE_LEFT) right_id = (orig_index, LINK_TYPE_RIGHT) del link_id_to_feature[orig_id] link_id_to_feature[left_id] = left link_id_to_feature[right_id] = right points = feature.geometry().asPolyline() endpoints = [(START, points[0]), (END, points[-1])] for end, point in endpoints: if not point in node_coords_to_link_ends: point = old_to_new_node_points[point] assert point in node_coords_to_link_ends node_link_end_list = node_coords_to_link_ends[point] node_link_end_list.remove(LinkEnd(orig_id, end)) node_link_end_list += [ LinkEnd(left_id, end), LinkEnd(right_id, end) ] nodes_of_divided_links.add(point) divided_linkends[LinkEnd(orig_id, end)] = [ LinkEnd(left_id, end), LinkEnd(right_id, end) ] else: feature.setAttribute(self.SIDEWALK_FIELD_NAME, self.SIDEWALK_FIELD_NONE) feedback.setProgress(int(current * total)) def left_right_pair(type1, type2): return (type1 == LINK_TYPE_LEFT and type2 == LINK_TYPE_RIGHT) or ( type1 == LINK_TYPE_RIGHT and type2 == LINK_TYPE_LEFT) def over_180_degrees(b1, b2): return (b2 - b1) % 360 > 180 def count_backtracks(bleplist): bleplist = bleplist.copy() backtracks = 0 bleplist += [bleplist[0]] for ((b1, _), (b2, _)) in zip(bleplist[0:-1], bleplist[1:]): if over_180_degrees(b1, b2): backtracks += 1 return backtracks feedback.pushInfo("Generating crossings and junction links") total = 100.0 / len(nodes_of_divided_links) if len( nodes_of_divided_links) else 0 next_new_link_index = 0 links_we_deleted_in_intersection = set() for current, node_point in enumerate(nodes_of_divided_links): if feedback.isCanceled(): break link_end_list = node_coords_to_link_ends[node_point] if len(link_end_list) == 1: continue # don't trim ends off path cul-de-sacs if len(link_end_list) == 2: ((id1, type1), end1), ((id2, type2), end2) = link_end_list if id1 == id2: assert left_right_pair(type1, type2) continue # don't trim ends off divided cul-de-sacs # from here on we no longer need to update node_coords_to_link_ends except to check for intersections outside nodes # - it becomes invalid but link_id_to_feature contains the result # - and for the intersection outside node check we remove deleted links during that check node_buffer = get_node_buffer(node_point) # report intersections with buffer that aren't part of node for id, link in link_id_to_feature.items(): if link.geometry().intersects(node_buffer) and all( [le.id != id for le in link_end_list]): add_error(link, "Link intersects unrelated junction") # trim joining links links_already_trimmed = set() for le in link_end_list: if not le.id in link_id_to_feature: assert le.id in links_we_deleted_in_intersection continue link = link_id_to_feature[le.id] linkgeom = link.geometry() inter = node_buffer.intersection(linkgeom) if inter.constGet().partCount() > 0: trimmed_linkgeom = linkgeom.difference(node_buffer) if trimmed_linkgeom.isMultipart(): add_error(link, "Link intersects junction more than once") feedback.pushInfo( f"Link {le} intersects junction {node_point} more than once" ) trimmed_linkgeom = QgsGeometry.fromPolyline( list(trimmed_linkgeom.constParts())[0]) if trimmed_linkgeom.isEmpty(): add_error( link, "Divided link half fully within junction buffer", f"{le.id}") #above** del link_id_to_feature[le.id] links_we_deleted_in_intersection.add(le.id) else: link.setGeometry(trimmed_linkgeom) links_already_trimmed.add(le.id) else: if not (le.id in links_already_trimmed and le.id[0] in loop_link_ids): if LinkEnd((le.id[0], LINK_TYPE_ORIGINAL), le.end) in linkends_that_got_nodes_moved: add_error( link, "Link does not intersect its own junction buffer" ) else: add_error( link, "Link does not intersect its own UNMERGED junction buffer" ) old_bearing_and_link_end_pairs_list = node_link_ordering[ node_point] bearing_and_link_end_pairs_list = [] # update bearing_and_link_end_pairs_list by inserting divided halves where needed for ob, le in old_bearing_and_link_end_pairs_list: if le in divided_linkends: divided_linkend_pair = divided_linkends[le] assert len(divided_linkend_pair) == 2 linkends_from_this_division = [ x for x in divided_linkend_pair if x.id in link_id_to_feature ] # filter out halves we deleted already bearing_link_end_pairs_from_this_division = [ (node_point.azimuth(linkend_to_point(le)), le) for le in linkends_from_this_division ] if len(bearing_link_end_pairs_from_this_division) == 2: # if both halves still present, order correctly (b1, _), (b2, _) = bearing_link_end_pairs_from_this_division if over_180_degrees(b1, b2): bearing_link_end_pairs_from_this_division.reverse() # if one or both halves are missing, this is probably an error state, but we report it above** bearing_and_link_end_pairs_list += bearing_link_end_pairs_from_this_division else: bearing_and_link_end_pairs_list += [(ob, le)] # test for backtracks - if more backtracks in bearing_and_link_end_pairs_list than sorted bearing_and_link_end_pairs_list if count_backtracks( bearing_and_link_end_pairs_list) > count_backtracks( sorted(bearing_and_link_end_pairs_list)): f = QgsFeature() f.setGeometry(node_buffer) add_error(f, "Junction/crossing link backtrack") # iterate pairwise around circle, repeating first element, to add crossings SIDEWALK_FIELD_CROSSING SIDEWALK_FIELD_JUNCTION bearing_and_link_end_pairs_list += [ bearing_and_link_end_pairs_list[0] ] for ((b1, le1), (b2, le2)) in zip(bearing_and_link_end_pairs_list[0:-1], bearing_and_link_end_pairs_list[1:]): startpoint, endpoint = [ linkend_to_point(le) for le in [le1, le2] ] if startpoint == endpoint: error = "Endpoints already match when adding crossings/junction links" add_error(link_id_to_feature[le1.id], error) add_error(link_id_to_feature[le2.id], error) geom = QgsGeometry.fromPolylineXY([startpoint, endpoint]) feature = QgsFeature() feature.setGeometry(geom) feature.setFields(outfields) id1, type1 = le1.id id2, type2 = le2.id if id1 == id2 and left_right_pair(type1, type2): crossed_road = link_id_to_feature[le1.id] for name in crossed_road.fields().names(): feature.setAttribute(name, crossed_road.attribute( name)) # some to be overwritten below assert le1.end == le2.end crossed_road_orig_id = (le1.id[0], LINK_TYPE_ORIGINAL) crossed_road_orig_linkend = LinkEnd( crossed_road_orig_id, le1.end) if crossed_road_orig_linkend in linkend_to_crossing_feature: feature.setAttribute( self.SIDEWALK_FIELD_NAME, self.SIDEWALK_FIELD_FORMAL_CROSSING) formal_crossing_feature = linkend_to_crossing_feature[ crossed_road_orig_linkend] for name in formal_crossing_feature.fields().names(): feature.setAttribute( self.CROSSING_INPUT_FIELD_PREFIX + name, formal_crossing_feature.attribute(name)) feature.setAttribute( "pednet", 1 ) # formal crossings of non pedesetrian roads are allowed else: feature.setAttribute(self.SIDEWALK_FIELD_NAME, self.SIDEWALK_FIELD_CROSSING) else: feature.setAttribute(self.SIDEWALK_FIELD_NAME, self.SIDEWALK_FIELD_JUNCTION) feature.setAttribute("pednet", 1) # junction links always allowed if id1 == id2 and id1 not in loop_link_ids: error = "Matching ids are not left/right pair or loop link" add_error(link_id_to_feature[le1.id], error) add_error(link_id_to_feature[le2.id], error) new_link_id = (next_new_link_index, LINK_TYPE_OTHER) next_new_link_index += 1 link_id_to_feature[new_link_id] = feature if OUTPUT_DEBUG_NODE_BUFFERS: feature = QgsFeature() feature.setGeometry(node_buffer) feature.setFields(outfields) feature.setAttribute(self.SIDEWALK_FIELD_NAME, self.SIDEWALK_FIELD_BUFFER) new_link_id = (next_new_link_index, LINK_TYPE_BUFFER) next_new_link_index += 1 link_id_to_feature[new_link_id] = feature feedback.setProgress(int(current * total)) feedback.pushInfo("Checking for link intersections outside nodes") total = 100.0 / len(nodes_of_divided_links) if len( nodes_of_divided_links) else 0 next_new_link_index = 0 for current, node_point in enumerate(nodes_of_divided_links): if feedback.isCanceled(): break link_end_list = [ le for le in node_coords_to_link_ends[node_point] if le.id in link_id_to_feature ] # remove what we deleted before check for le1, le2 in combinations(link_end_list, 2): if le1.id != le2.id: feature1, feature2 = [ link_id_to_feature[le.id] for le in [le1, le2] ] if feature1.geometry().intersects(feature2.geometry()): message = "Intersection of links outside junction" details = "{!r}{!r}".format(le1, le2) add_error(feature1, message, details) add_error(feature2, message, details) feedback.pushInfo("Computing sidewalker metric and writing output") total = 100.0 / len(link_id_to_feature) if len( link_id_to_feature) else 0 for current, link in enumerate(link_id_to_feature.values()): if feedback.isCanceled(): break length = link.geometry().length() extra_length = 0 crossed_road_deterrence = link.attribute("cyclist_de") if link.attribute( self.SIDEWALK_FIELD_NAME) == self.SIDEWALK_FIELD_CROSSING: if crossed_road_deterrence >= 260: # picks up tertiary, secondary extra_length = 191 if crossed_road_deterrence >= 1500: #picks up trunk, primary extra_length = 340 if link.attribute( self.SIDEWALK_FIELD_NAME ) == self.SIDEWALK_FIELD_FORMAL_CROSSING and crossed_road_deterrence >= 260: extra_length = 60 link.setAttribute(self.SIDEWALK_METRIC_FIELD_NAME, length + extra_length) link.setAttribute(self.SIDEWALK_WEIGHT_FIELD_NAME, 0) if link.attribute(self.SIDEWALK_FIELD_NAME) in [None, "NULL", ""]: link.setAttribute(self.SIDEWALK_WEIGHT_FIELD_NAME, length) if link.attribute(self.SIDEWALK_FIELD_NAME) in [ self.SIDEWALK_FIELD_LEFT, self.SIDEWALK_FIELD_RIGHT ]: link.setAttribute(self.SIDEWALK_WEIGHT_FIELD_NAME, length / 2) link.setAttribute(self.ID_FIELD_NAME, current) sink.addFeature(link, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) return {self.OUTPUT: dest_id, self.ERROR_OUTPUT: err_dest_id}
def _test(autoTransaction): """Test buffer methods within and without transactions - create a feature - save - retrieve the feature - change geom and attrs - test changes are seen in the buffer """ def _check_feature(wkt): f = next(layer_a.getFeatures()) self.assertEqual(f.geometry().asWkt().upper(), wkt) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.geometry().asWkt().upper(), wkt) ml = QgsVectorLayer( 'Point?crs=epsg:4326&field=int:integer&field=int2:integer', 'test', 'memory') self.assertTrue(ml.isValid()) d = QTemporaryDir() options = QgsVectorFileWriter.SaveVectorOptions() options.driverName = 'GPKG' options.layerName = 'layer_a' err, msg, newFileName, newLayer = QgsVectorFileWriter.writeAsVectorFormatV3( ml, os.path.join(d.path(), 'transaction_test.gpkg'), QgsCoordinateTransformContext(), options) self.assertEqual(err, QgsVectorFileWriter.NoError) self.assertTrue(os.path.isfile(newFileName)) layer_a = QgsVectorLayer(newFileName + '|layername=layer_a') self.assertTrue(layer_a.isValid()) project = QgsProject() project.setAutoTransaction(autoTransaction) project.addMapLayers([layer_a]) ########################################### # Tests with a new feature self.assertTrue(layer_a.startEditing()) buffer = layer_a.editBuffer() f = QgsFeature(layer_a.fields()) f.setAttribute('int', 123) f.setGeometry(QgsGeometry.fromWkt('point(7 45)')) self.assertTrue(layer_a.addFeatures([f])) _check_feature('POINT (7 45)') # Need to fetch the feature because its ID is NULL (-9223372036854775808) f = next(layer_a.getFeatures()) self.assertEqual(len(buffer.addedFeatures()), 1) layer_a.undoStack().undo() self.assertEqual(len(buffer.addedFeatures()), 0) layer_a.undoStack().redo() self.assertEqual(len(buffer.addedFeatures()), 1) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.attribute('int'), 123) # Now change attribute self.assertEqual(buffer.changedAttributeValues(), {}) spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.changeAttributeValue(f.id(), 1, 321) self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 321]) self.assertEqual(len(buffer.addedFeatures()), 1) # This is surprising: because it was a new feature it has been changed directly self.assertEqual(buffer.changedAttributeValues(), {}) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.attribute('int'), 321) spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.undoStack().undo() self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 123]) self.assertEqual(buffer.changedAttributeValues(), {}) f = list(buffer.addedFeatures().values())[0] self.assertEqual(f.attribute('int'), 123) f = next(layer_a.getFeatures()) self.assertEqual(f.attribute('int'), 123) # Change multiple attributes spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.changeAttributeValues(f.id(), {1: 321, 2: 456}) self.assertEqual(len(spy_attribute_changed), 2) self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 321]) self.assertEqual(spy_attribute_changed[1], [f.id(), 2, 456]) buffer = layer_a.editBuffer() # This is surprising: because it was a new feature it has been changed directly self.assertEqual(buffer.changedAttributeValues(), {}) spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.undoStack().undo() # This is because QgsVectorLayerUndoCommandChangeAttribute plural if not autoTransaction: layer_a.undoStack().undo() f = next(layer_a.getFeatures()) self.assertEqual(f.attribute('int'), 123) self.assertEqual(f.attribute('int2'), None) self.assertEqual(len(spy_attribute_changed), 2) self.assertEqual( spy_attribute_changed[1 if autoTransaction else 0], [f.id(), 2, None]) self.assertEqual( spy_attribute_changed[0 if autoTransaction else 1], [f.id(), 1, 123]) # Change geometry f = next(layer_a.getFeatures()) spy_geometry_changed = QSignalSpy(layer_a.geometryChanged) self.assertTrue( layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)'))) self.assertTrue(len(spy_geometry_changed), 1) self.assertEqual(spy_geometry_changed[0][0], f.id()) self.assertEqual(spy_geometry_changed[0][1].asWkt(), QgsGeometry.fromWkt('point(9 43)').asWkt()) _check_feature('POINT (9 43)') self.assertEqual(buffer.changedGeometries(), {}) layer_a.undoStack().undo() _check_feature('POINT (7 45)') self.assertEqual(buffer.changedGeometries(), {}) self.assertTrue( layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)'))) _check_feature('POINT (9 43)') self.assertTrue( layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(10 44)'))) _check_feature('POINT (10 44)') # This is another surprise: geometry edits get collapsed into a single # one because they have the same hardcoded id layer_a.undoStack().undo() _check_feature('POINT (7 45)') self.assertTrue(layer_a.commitChanges()) ########################################### # Tests with the existing feature # Get the feature f = next(layer_a.getFeatures()) self.assertTrue(f.isValid()) self.assertEqual(f.attribute('int'), 123) self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)') # Change single attribute self.assertTrue(layer_a.startEditing()) spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.changeAttributeValue(f.id(), 1, 321) self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 321]) buffer = layer_a.editBuffer() self.assertEqual(buffer.changedAttributeValues(), {1: {1: 321}}) f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(1), 321) spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.undoStack().undo() f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(1), 123) self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 1, 123]) self.assertEqual(buffer.changedAttributeValues(), {}) # Change attributes spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.changeAttributeValues(f.id(), {1: 111, 2: 654}) self.assertEqual(len(spy_attribute_changed), 2) self.assertEqual(spy_attribute_changed[0], [1, 1, 111]) self.assertEqual(spy_attribute_changed[1], [1, 2, 654]) f = next(layer_a.getFeatures()) self.assertEqual(f.attributes(), [1, 111, 654]) self.assertEqual(buffer.changedAttributeValues(), {1: { 1: 111, 2: 654 }}) spy_attribute_changed = QSignalSpy(layer_a.attributeValueChanged) layer_a.undoStack().undo() # This is because QgsVectorLayerUndoCommandChangeAttribute plural if not autoTransaction: layer_a.undoStack().undo() self.assertEqual(len(spy_attribute_changed), 2) self.assertEqual( spy_attribute_changed[0 if autoTransaction else 1], [1, 1, 123]) self.assertEqual( spy_attribute_changed[1 if autoTransaction else 0], [1, 2, None]) f = next(layer_a.getFeatures()) self.assertEqual(f.attributes(), [1, 123, None]) self.assertEqual(buffer.changedAttributeValues(), {}) # Change geometry spy_geometry_changed = QSignalSpy(layer_a.geometryChanged) self.assertTrue( layer_a.changeGeometry(f.id(), QgsGeometry.fromWkt('point(9 43)'))) self.assertEqual(spy_geometry_changed[0][0], 1) self.assertEqual(spy_geometry_changed[0][1].asWkt(), QgsGeometry.fromWkt('point(9 43)').asWkt()) f = next(layer_a.getFeatures()) self.assertEqual(f.geometry().asWkt().upper(), 'POINT (9 43)') self.assertEqual(buffer.changedGeometries()[1].asWkt().upper(), 'POINT (9 43)') spy_geometry_changed = QSignalSpy(layer_a.geometryChanged) layer_a.undoStack().undo() self.assertEqual(spy_geometry_changed[0][0], 1) self.assertEqual(spy_geometry_changed[0][1].asWkt(), QgsGeometry.fromWkt('point(7 45)').asWkt()) self.assertEqual(buffer.changedGeometries(), {}) f = next(layer_a.getFeatures()) self.assertEqual(f.geometry().asWkt().upper(), 'POINT (7 45)') self.assertEqual(buffer.changedGeometries(), {}) # Delete an existing feature self.assertTrue(layer_a.deleteFeature(f.id())) with self.assertRaises(StopIteration): next(layer_a.getFeatures()) self.assertEqual(buffer.deletedFeatureIds(), [f.id()]) layer_a.undoStack().undo() self.assertTrue(layer_a.getFeature(f.id()).isValid()) self.assertEqual(buffer.deletedFeatureIds(), []) ########################################### # Test delete # Delete a new feature f = QgsFeature(layer_a.fields()) f.setAttribute('int', 555) f.setGeometry(QgsGeometry.fromWkt('point(8 46)')) self.assertTrue(layer_a.addFeatures([f])) f = [ f for f in layer_a.getFeatures() if f.attribute('int') == 555 ][0] self.assertTrue(f.id() in buffer.addedFeatures()) self.assertTrue(layer_a.deleteFeature(f.id())) self.assertFalse(f.id() in buffer.addedFeatures()) self.assertFalse(f.id() in buffer.deletedFeatureIds()) layer_a.undoStack().undo() self.assertTrue(f.id() in buffer.addedFeatures()) ########################################### # Add attribute field = QgsField('attr1', QVariant.String) self.assertTrue(layer_a.addAttribute(field)) self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1) self.assertEqual(buffer.addedAttributes(), [field]) layer_a.undoStack().undo() self.assertEqual(layer_a.fields().lookupField(field.name()), -1) self.assertEqual(buffer.addedAttributes(), []) layer_a.undoStack().redo() self.assertNotEqual(layer_a.fields().lookupField(field.name()), -1) self.assertEqual(buffer.addedAttributes(), [field]) self.assertTrue(layer_a.commitChanges()) ########################################### # Remove attribute self.assertTrue(layer_a.startEditing()) buffer = layer_a.editBuffer() attr_idx = layer_a.fields().lookupField(field.name()) self.assertNotEqual(attr_idx, -1) self.assertTrue(layer_a.deleteAttribute(attr_idx)) self.assertEqual(buffer.deletedAttributeIds(), [attr_idx]) self.assertEqual(layer_a.fields().lookupField(field.name()), -1) layer_a.undoStack().undo() self.assertEqual(buffer.deletedAttributeIds(), []) self.assertEqual(layer_a.fields().lookupField(field.name()), attr_idx) # This is totally broken at least on OGR/GPKG: the rollback # does not restore the original fields if False: layer_a.undoStack().redo() self.assertEqual(buffer.deletedAttributeIds(), [attr_idx]) self.assertEqual(layer_a.fields().lookupField(field.name()), -1) # Rollback! self.assertTrue(layer_a.rollBack()) self.assertIn('attr1', layer_a.dataProvider().fields().names()) self.assertIn('attr1', layer_a.fields().names()) self.assertEqual(layer_a.fields().names(), layer_a.dataProvider().fields().names()) attr_idx = layer_a.fields().lookupField(field.name()) self.assertNotEqual(attr_idx, -1) self.assertTrue(layer_a.startEditing()) attr_idx = layer_a.fields().lookupField(field.name()) self.assertNotEqual(attr_idx, -1) ########################################### # Rename attribute attr_idx = layer_a.fields().lookupField(field.name()) self.assertEqual(layer_a.fields().lookupField('new_name'), -1) self.assertTrue(layer_a.renameAttribute(attr_idx, 'new_name')) self.assertEqual(layer_a.fields().lookupField('new_name'), attr_idx) layer_a.undoStack().undo() self.assertEqual(layer_a.fields().lookupField(field.name()), attr_idx) self.assertEqual(layer_a.fields().lookupField('new_name'), -1) layer_a.undoStack().redo() self.assertEqual(layer_a.fields().lookupField('new_name'), attr_idx) self.assertEqual(layer_a.fields().lookupField(field.name()), -1) ############################################# # Try hard to make this fail for transactions if autoTransaction: self.assertTrue(layer_a.commitChanges()) self.assertTrue(layer_a.startEditing()) f = next(layer_a.getFeatures()) # Do for i in range(10): spy_attribute_changed = QSignalSpy( layer_a.attributeValueChanged) layer_a.changeAttributeValue(f.id(), 2, i) self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 2, i]) buffer = layer_a.editBuffer() self.assertEqual(buffer.changedAttributeValues(), {f.id(): { 2: i }}) f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(2), i) # Undo/redo for i in range(9): # Undo spy_attribute_changed = QSignalSpy( layer_a.attributeValueChanged) layer_a.undoStack().undo() f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(2), 8 - i) self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 2, 8 - i]) buffer = layer_a.editBuffer() self.assertEqual(buffer.changedAttributeValues(), {f.id(): { 2: 8 - i }}) # Redo spy_attribute_changed = QSignalSpy( layer_a.attributeValueChanged) layer_a.undoStack().redo() f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(2), 9 - i) self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 2, 9 - i]) # Undo again spy_attribute_changed = QSignalSpy( layer_a.attributeValueChanged) layer_a.undoStack().undo() f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(2), 8 - i) self.assertEqual(len(spy_attribute_changed), 1) self.assertEqual(spy_attribute_changed[0], [f.id(), 2, 8 - i]) buffer = layer_a.editBuffer() self.assertEqual(buffer.changedAttributeValues(), {f.id(): { 2: 8 - i }}) # Last check f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(2), 8 - i) self.assertEqual(buffer.changedAttributeValues(), {f.id(): { 2: 0 }}) layer_a.undoStack().undo() buffer = layer_a.editBuffer() self.assertEqual(buffer.changedAttributeValues(), {}) f = next(layer_a.getFeatures()) self.assertEqual(f.attribute(2), None)
def __adjust(self): """ To look for adjustments and to display them """ self.__layers = self.__lineVertices(True) self.__adjustments = [] self.__altitudes = [] for p in range(len(self.__points)): pt = self.__points[p] x = pt['x'] y = pt['y'] z = pt['z'] num_lines = len(self.__selectedIds) drawdown = False level = None for layer in self.ownSettings.refLayers: laySettings = QgsSnappingUtils.LayerConfig( layer, QgsPointLocator.All, self.SEARCH_TOLERANCE, QgsTolerance.LayerUnits) feature = Finder.findClosestFeatureAt( self.toMapCoordinates(layer, QgsPoint(x, y)), laySettings, self) if feature is not None: point_v2 = GeometryV2.asPointV2(feature.geometry(), self.__iface) if point_v2.z() > 0: if level is not None: if (level - point_v2.z()) > 0.005: self.__iface.messageBar( ).pushMessage(QCoreApplication.translate( "VDLTools", "More than one reference point, with 2 different elevations !!" ), level=QgsMessageBar.CRITICAL, duration=0) self.__cancel() return level = point_v2.z() comp = QCoreApplication.translate("VDLTools", " (at invert)") if str(feature.attribute(self.ownSettings.levelAtt) ) in self.ownSettings.levelVals: drawdown = True comp = QCoreApplication.translate( "VDLTools", " (on pipe)") if point_v2.z() == 0: comp = QCoreApplication.translate( "VDLTools", " (no elevation)") self.__adjustments.append({ 'point': p, 'previous': point_v2.z(), 'line': False, 'layer': layer, 'comp': comp, 'feature': feature, 'delta': False }) diam = 0 for i in range(num_lines): if z[i] is None: continue id_s = self.__selectedIds[i] feature = QgsFeature() self.ownSettings.drawdownLayer.getFeatures( QgsFeatureRequest().setFilterFid(id_s)).nextFeature( feature) dtemp = feature.attribute(self.ownSettings.pipeDiam) / 1000 if dtemp > diam: diam = dtemp selected = None for f in self.ownSettings.drawdownLayer.selectedFeatures(): if f.id() == id_s: selected = f break self.__adjustments.append({ 'point': p, 'previous': z[i], 'line': True, 'diam': dtemp, 'layer': self.ownSettings.drawdownLayer, 'feature': selected, 'delta': True }) for layer in self.__layers: laySettings = QgsSnappingUtils.LayerConfig( layer, QgsPointLocator.All, self.SEARCH_TOLERANCE, QgsTolerance.LayerUnits) fs = Finder.findFeaturesAt(QgsPoint(x, y), laySettings, self) if len(fs) == 0: z.append(None) else: zz = [] for f in fs: if layer == self.ownSettings.drawdownLayer: if f.id() not in self.__selectedIds: closest = f.geometry().closestVertex( QgsPoint(x, y)) if closest[4] < self.SEARCH_TOLERANCE: line, curved = GeometryV2.asLineV2( f.geometry(), self.__iface) zp = line.zAt(closest[1]) dtemp = f.attribute( self.ownSettings.pipeDiam) / 1000 if dtemp > diam: diam = dtemp self.__adjustments.append({ 'point': p, 'previous': zp, 'line': False, 'diam': dtemp, 'comp': QCoreApplication.translate( "VDLTools", " connected"), 'feature': f, 'layer': layer, 'delta': True }) if zp is None or zp != zp: zz.append(0) else: zz.append(zp) else: zp = GeometryV2.asPointV2(f.geometry(), self.__iface).z() if zp is None or zp != zp: zp = 0 zz.append(zp) if layer in self.ownSettings.adjLayers: self.__adjustments.append({ 'point': p, 'previous': zp, 'line': False, 'layer': layer, 'feature': f, 'delta': True }) if len(zz) == 0: z.append(None) elif len(zz) == 1: z.append(zz[0]) else: z.append(zz) if level is not None: if drawdown: alt = level - diam else: alt = level else: alt = 0 dd = None if drawdown: dd = QCoreApplication.translate("VDLTools", "drawdown") self.__altitudes.append({'diam': diam, 'drawdown': dd, 'alt': alt}) last = len(self.__altitudes) - 1 self.__extras = [] for i in range(len(self.__altitudes)): if self.__altitudes[i]['alt'] is 0: if 0 < i < last: av = None j = 1 while True: if i - j < 0: break if self.__altitudes[i - j]['alt'] != 0: av = j break j += 1 ap = None j = 1 while True: if i + j > len(self.__points) - 1: break if self.__altitudes[i + j]['alt'] != 0: ap = j break j += 1 if av is not None and ap is not None: prev_alt = self.__altitudes[i - av]['alt'] next_alt = self.__altitudes[i + ap]['alt'] prev_pt = self.__points[i - av] next_pt = self.__points[i + ap] pt = self.__points[i] d0 = Finder.sqrDistForCoords(pt['x'], prev_pt['x'], pt['y'], prev_pt['y']) d1 = Finder.sqrDistForCoords(next_pt['x'], pt['x'], next_pt['y'], pt['y']) inter_alt = round( old_div((d0 * next_alt + d1 * prev_alt), (d0 + d1)), 3) self.__altitudes[i]['alt'] = inter_alt self.__altitudes[i]['drawdown'] = "interpolation" elif i == 0 and len(self.__altitudes) > 2: alt1 = self.__altitudes[1]['alt'] alt2 = self.__altitudes[2]['alt'] if alt1 != 0 and alt2 != 0: pt2 = self.__points[2] pt1 = self.__points[1] pt = self.__points[0] big_d = Finder.sqrDistForCoords( pt2['x'], pt1['x'], pt2['y'], pt1['y']) small_d = Finder.sqrDistForCoords( pt1['x'], pt['x'], pt1['y'], pt['y']) extra_alt = round( alt2 + (1 + old_div(small_d, big_d)) * (alt1 - alt2), 3) if small_d < (old_div(big_d, 4)): self.__altitudes[i]['alt'] = extra_alt self.__altitudes[i]['drawdown'] = "extrapolation" else: self.__extras.append([i, extra_alt]) elif i == last and len(self.__altitudes) > 2: alt1 = self.__altitudes[i - 1]['alt'] alt2 = self.__altitudes[i - 2]['alt'] if alt1 != 0 and alt2 != 0: pt2 = self.__points[i - 2] pt1 = self.__points[i - 1] pt = self.__points[i] big_d = Finder.sqrDistForCoords( pt2['x'], pt1['x'], pt2['y'], pt1['y']) small_d = Finder.sqrDistForCoords( pt1['x'], pt['x'], pt1['y'], pt['y']) extra_alt = round( alt2 + (1 + old_div(small_d, big_d)) * (alt1 - alt2), 3) if small_d < (old_div(big_d, 4)): self.__altitudes[i]['alt'] = extra_alt self.__altitudes[i]['drawdown'] = "extrapolation" else: self.__extras.append([i, extra_alt]) if len(self.__extras) == 0: self.__setAdjustements() else: self.__checkForceExtrapolation()
def testSpatialiteDefaultValues(self): """Test whether in spatialite table with default values like CURRENT_TIMESTAMP or (datetime('now','localtime')) they are respected. See GH #33383""" # Create the test table dbname = os.path.join(tempfile.gettempdir(), "test.sqlite") if os.path.exists(dbname): os.remove(dbname) con = spatialite_connect(dbname, isolation_level=None) cur = con.cursor() cur.execute("BEGIN") sql = "SELECT InitSpatialMetadata()" cur.execute(sql) # simple table with primary key sql = """ CREATE TABLE test_table_default_values ( id integer primary key autoincrement, comment TEXT, created_at_01 text DEFAULT (datetime('now','localtime')), created_at_02 text DEFAULT CURRENT_TIMESTAMP, anumber INTEGER DEFAULT 123, atext TEXT default 'My default' ) """ cur.execute(sql) cur.execute("COMMIT") con.close() vl = QgsVectorLayer(dbname + '|layername=test_table_default_values', 'test_table_default_values', 'ogr') self.assertTrue(vl.isValid()) # Save it for the test now = datetime.now() # Test default values dp = vl.dataProvider() # FIXME: should it be None? self.assertTrue(dp.defaultValue(0).isNull()) self.assertIsNone(dp.defaultValue(1)) # FIXME: This fails because there is no backend-side evaluation in this provider # self.assertTrue(dp.defaultValue(2).startswith(now.strftime('%Y-%m-%d'))) self.assertTrue(dp.defaultValue(3).startswith(now.strftime('%Y-%m-%d'))) self.assertEqual(dp.defaultValue(4), 123) self.assertEqual(dp.defaultValue(5), 'My default') self.assertEqual(dp.defaultValueClause(0), 'Autogenerate') self.assertEqual(dp.defaultValueClause(1), '') self.assertEqual(dp.defaultValueClause(2), "datetime('now','localtime')") self.assertEqual(dp.defaultValueClause(3), "CURRENT_TIMESTAMP") # FIXME: ogr provider simply returns values when asked for clauses # self.assertEqual(dp.defaultValueClause(4), '') # self.assertEqual(dp.defaultValueClause(5), '') feature = QgsFeature(vl.fields()) for idx in range(vl.fields().count()): default = vl.dataProvider().defaultValue(idx) if not default: feature.setAttribute(idx, 'A comment') else: feature.setAttribute(idx, default) self.assertTrue(vl.dataProvider().addFeature(feature)) del (vl) # Verify vl2 = QgsVectorLayer(dbname + '|layername=test_table_default_values', 'test_table_default_values', 'ogr') self.assertTrue(vl2.isValid()) feature = next(vl2.getFeatures()) self.assertEqual(feature.attribute(1), 'A comment') self.assertTrue(feature.attribute(2).startswith(now.strftime('%Y-%m-%d'))) self.assertTrue(feature.attribute(3).startswith(now.strftime('%Y-%m-%d'))) self.assertEqual(feature.attribute(4), 123) self.assertEqual(feature.attribute(5), 'My default')
def testFidSupport(self): # We do not use @unittest.expectedFailure since the test might actually succeed # on Linux 64bit with GDAL 1.11, where "long" is 64 bit... # GDAL 2.0 is guaranteed to properly support it on all platforms version_num = int(gdal.VersionInfo('VERSION_NUM')) if version_num < GDAL_COMPUTE_VERSION(2, 0, 0): return tmpfile = os.path.join(self.basetestpath, 'testFidSupport.sqlite') ds = ogr.GetDriverByName('SQLite').CreateDataSource(tmpfile) lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint, options=['FID=fid']) lyr.CreateField(ogr.FieldDefn('strfield', ogr.OFTString)) lyr.CreateField(ogr.FieldDefn('intfield', ogr.OFTInteger)) f = ogr.Feature(lyr.GetLayerDefn()) f.SetFID(12) f.SetField(0, 'foo') f.SetField(1, 123) lyr.CreateFeature(f) f = None ds = None vl = QgsVectorLayer('{}'.format(tmpfile), 'test', 'ogr') self.assertEqual(len(vl.fields()), 3) got = [(f.attribute('fid'), f.attribute('strfield'), f.attribute('intfield')) for f in vl.getFeatures()] self.assertEqual(got, [(12, 'foo', 123)]) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("strfield = 'foo'"))] self.assertEqual(got, [(12, 'foo')]) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 12"))] self.assertEqual(got, [(12, 'foo')]) result = [f['strfield'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest().setSubsetOfAttributes(['strfield'], vl.dataProvider().fields()))] self.assertEqual(result, ['foo']) result = [f['fid'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest().setSubsetOfAttributes(['fid'], vl.dataProvider().fields()))] self.assertEqual(result, [12]) # Test that when the 'fid' field is not set, regular insertion is done f = QgsFeature() f.setFields(vl.fields()) f.setAttributes([None, 'automatic_id']) (res, out_f) = vl.dataProvider().addFeatures([f]) self.assertEqual(out_f[0].id(), 13) self.assertEqual(out_f[0].attribute('fid'), 13) self.assertEqual(out_f[0].attribute('strfield'), 'automatic_id') # Test that when the 'fid' field is set, it is really used to set the id f = QgsFeature() f.setFields(vl.fields()) f.setAttributes([9876543210, 'bar']) (res, out_f) = vl.dataProvider().addFeatures([f]) self.assertEqual(out_f[0].id(), 9876543210) self.assertEqual(out_f[0].attribute('fid'), 9876543210) self.assertEqual(out_f[0].attribute('strfield'), 'bar') got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'bar')]) self.assertTrue(vl.dataProvider().changeAttributeValues({9876543210: {1: 'baz'}})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baz')]) self.assertTrue(vl.dataProvider().changeAttributeValues({9876543210: {0: 9876543210, 1: 'baw'}})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baw')]) # Not allowed: changing the fid regular field self.assertTrue(vl.dataProvider().changeAttributeValues({9876543210: {0: 12, 1: 'baw'}})) got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))] self.assertEqual(got, [(9876543210, 'baw')]) # Cannot delete fid self.assertFalse(vl.dataProvider().deleteAttributes([0])) # Delete first "genuine" attribute self.assertTrue(vl.dataProvider().deleteAttributes([1])) got = [(f.attribute('fid'), f.attribute('intfield')) for f in vl.dataProvider().getFeatures(QgsFeatureRequest().setFilterExpression("fid = 12"))] self.assertEqual(got, [(12, 123)])
def run(self): self.mutex.lock() self.stopMe = 0 self.mutex.unlock() interrupted = False polyProvider = self.layerPoly.dataProvider() pointProvider = self.layerPoints.dataProvider() fieldList = ftools_utils.getFieldList(self.layerPoly) index = polyProvider.fieldNameIndex(unicode(self.fieldName)) if index == -1: index = polyProvider.fields().count() fieldList.append( QgsField(unicode(self.fieldName), QVariant.Int, "int", 10, 0, self.tr("point count field")) ) # Add the selected vector fields to the output polygon vector layer selectedItems = self.attributeList.selectedItems() for item in selectedItems: global typeDouble columnName = unicode(item.text() + "_" + self.statistics) index = polyProvider.fieldNameIndex(unicode(columnName)) if index == -1: if item.type() == typeDouble or self.statistics == "mean" or self.statistics == "stddev": fieldList.append( QgsField(columnName, QVariant.Double, "double", 24, 15, "Value") ) else: fieldList.append( QgsField(columnName, QVariant.Int, "int", 10, 0, "Value") ) sRs = polyProvider.crs() if QFile(self.outPath).exists(): if not QgsVectorFileWriter.deleteShapeFile(self.outPath): return writer = QgsVectorFileWriter(self.outPath, self.encoding, fieldList, polyProvider.geometryType(), sRs) spatialIndex = ftools_utils.createIndex( pointProvider ) self.emit(SIGNAL("rangeChanged(int)"), polyProvider.featureCount() ) polyFeat = QgsFeature() pntFeat = QgsFeature() outFeat = QgsFeature() inGeom = QgsGeometry() polyFit = polyProvider.getFeatures() while polyFit.nextFeature(polyFeat): inGeom = polyFeat.geometry() atMap = polyFeat.attributes() outFeat.setAttributes(atMap) outFeat.setGeometry(inGeom) count = 0 pointList = [] hasIntersection = True pointList = spatialIndex.intersects(inGeom.boundingBox()) if len(pointList) > 0: hasIntersection = True else: hasIntersection = False if hasIntersection: valueList = {} for item in selectedItems: valueList[item.text()] = [] for p in pointList: pointProvider.getFeatures( QgsFeatureRequest().setFilterFid( p ) ).nextFeature( pntFeat ) tmpGeom = QgsGeometry(pntFeat.geometry()) if inGeom.intersects(tmpGeom): count += 1 for item in selectedItems: valueList[item.text()].append(pntFeat.attribute(item.text())) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break atMap.append(count) # Compute the statistical values for selected vector attributes for item in selectedItems: values = valueList[item.text()] # Check if the input contains non-numeric values non_numeric_values = False for value in values: if not isinstance(value, type(float())) and not isinstance(value, type(int())): non_numeric_values = True break # Jump over invalid values if non_numeric_values is True: continue if values and len(values) > 0: if self.statistics == "sum": value = reduce(myAdder, values) elif self.statistics == "mean": value = reduce(myAdder, values) / float(len(values)) elif self.statistics == "min": values.sort() value = values[0] elif self.statistics == "max": values.sort() value = values[-1] elif self.statistics == "stddev": value = two_pass_variance(values) value = math.sqrt(value) atMap.append(value) outFeat.setAttributes(atMap) writer.addFeature(outFeat) self.emit( SIGNAL( "updateProgress()" ) ) self.mutex.lock() s = self.stopMe self.mutex.unlock() if s == 1: interrupted = True break del writer if not interrupted: self.emit( SIGNAL( "processingFinished()" ) ) else: self.emit( SIGNAL( "processingInterrupted()" ) )
def testDuplicateFeature(self): """ test duplicating a feature """ project = QgsProject().instance() # LAYERS # - add first layer (parent) layer1 = QgsVectorLayer("Point?field=fldtxt:string&field=pkid:integer", "parentlayer", "memory") # > check first layer (parent) self.assertTrue(layer1.isValid()) # - set the value for the copy layer1.setDefaultValueDefinition(1, QgsDefaultValue("rand(1000,2000)")) # > check first layer (parent) self.assertTrue(layer1.isValid()) # - add second layer (child) layer2 = QgsVectorLayer("Point?field=fldtxt:string&field=id:integer&field=foreign_key:integer", "childlayer", "memory") # > check second layer (child) self.assertTrue(layer2.isValid()) # - add layers project.addMapLayers([layer1, layer2]) # FEATURES # - add 2 features on layer1 (parent) l1f1orig = QgsFeature() l1f1orig.setFields(layer1.fields()) l1f1orig.setAttributes(["F_l1f1", 100]) l1f2orig = QgsFeature() l1f2orig.setFields(layer1.fields()) l1f2orig.setAttributes(["F_l1f2", 101]) # > check by adding features self.assertTrue(layer1.dataProvider().addFeatures([l1f1orig, l1f2orig])) # add 4 features on layer2 (child) l2f1orig = QgsFeature() l2f1orig.setFields(layer2.fields()) l2f1orig.setAttributes(["F_l2f1", 201, 100]) l2f2orig = QgsFeature() l2f2orig.setFields(layer2.fields()) l2f2orig.setAttributes(["F_l2f2", 202, 100]) l2f3orig = QgsFeature() l2f3orig.setFields(layer2.fields()) l2f3orig.setAttributes(["F_l2f3", 203, 100]) l2f4orig = QgsFeature() l2f4orig.setFields(layer2.fields()) l2f4orig.setAttributes(["F_l2f4", 204, 101]) # > check by adding features self.assertTrue(layer2.dataProvider().addFeatures([l2f1orig, l2f2orig, l2f3orig, l2f4orig])) # RELATION # - create the relationmanager relMgr = project.relationManager() # - create the relation rel = QgsRelation() rel.setId('rel1') rel.setName('childrel') rel.setReferencingLayer(layer2.id()) rel.setReferencedLayer(layer1.id()) rel.addFieldPair('foreign_key', 'pkid') rel.setStrength(QgsRelation.Composition) # > check relation self.assertTrue(rel.isValid()) # - add relation relMgr.addRelation(rel) # > check if referencedLayer is layer1 self.assertEqual(rel.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(rel.referencingLayer(), layer2) # > check if the layers are correct in relation when loading from relationManager relations = project.relationManager().relations() relation = relations[list(relations.keys())[0]] # > check if referencedLayer is layer1 self.assertEqual(relation.referencedLayer(), layer1) # > check if referencingLayer is layer2 self.assertEqual(relation.referencingLayer(), layer2) # > check the relatedfeatures ''' # testoutput 1 print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) print( "\n--------------------------") print( "\nFeatures on layer1") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2") for f in layer2.getFeatures(): print( f.attributes() ) ''' # DUPLICATION # - duplicate feature l1f1orig with children layer1.startEditing() results = QgsVectorLayerUtils.duplicateFeature(layer1, l1f1orig, project, 0) # > check if name is name of duplicated (pk is different) result_feature = results[0] self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # > check duplicated child layer result_layer = results[1].layers()[0] self.assertEqual(result_layer, layer2) # > check duplicated child features self.assertTrue(results[1].duplicatedFeatures(result_layer)) ''' # testoutput 2 print( "\nFeatures on layer1 (after duplication)") for f in layer1.getFeatures(): print( f.attributes() ) print( "\nFeatures on layer2 (after duplication)") for f in layer2.getFeatures(): print( f.attributes() ) print( "\nAll Features and relations") featit=layer1.getFeatures() f=QgsFeature() while featit.nextFeature(f): print( f.attributes()) childFeature = QgsFeature() relfeatit=rel.getRelatedFeatures(f) while relfeatit.nextFeature(childFeature): print( childFeature.attributes() ) ''' # > compare text of parent feature self.assertEqual(result_feature.attribute('fldtxt'), l1f1orig.attribute('fldtxt')) # - create copyValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(result_feature) copyValueList = [] while relfeatit.nextFeature(childFeature): copyValueList.append(childFeature.attribute('fldtxt')) # - create origValueList childFeature = QgsFeature() relfeatit = rel.getRelatedFeatures(l1f1orig) origValueList = [] while relfeatit.nextFeature(childFeature): origValueList.append(childFeature.attribute('fldtxt')) # - check if the ids are still the same self.assertEqual(copyValueList, origValueList)
def evaluate(self): #Write parameters to GIS bfeat = QgsFeature() featreq = QgsFeatureRequest() i = 0 attr = {} for featid in self.bfeatids: i += 1 attr.clear() bfeat = next( self.sim.blayer.getFeatures(featreq.setFilterFid(featid))) for key in sorted(self.ofile.OptAttributes.keys()): bfindx = self.sim.bprovider.fieldNameIndex( self.ofile.OptAttributes[key][0]) ftype = str(self.sim.bprovider.fields()[bfindx].typeName()) if ftype in ['Integer', 'Integer64']: value = int(self.current.x[key]) elif ftype in ['Real', 'Double']: value = float(self.current.x[key]) if int(self.ofile.OptAttributes[key][4]) in [0, i]: attr.update({bfindx: value}) result = self.sim.bprovider.changeAttributeValues( {bfeat.id(): attr}) if not result: self.setCursor(Qt.ArrowCursor) QMessageBox.critical(self, 'Simulation Optimizer', 'Could not change attribute value1.') return #Can't get attribute table repainting to work for all conditions. self.sim.blayer.selectByIds(self.bfeatids) QApplication.processEvents() #Run simulations for featid in self.bfeatids: self.sim.ui.cbxOnlySelected.setChecked(1) self.sim.blayer.selectByIds([featid]) self.sim.on_btnRun_clicked() self.ui.textSimulation.append(self.sim.p.stdout) self.ui.textSimulation.append(self.sim.p.stderr) #Calculate error sqrerr = [] for featid in self.bfeatids: bfeat = next( self.sim.blayer.getFeatures(featreq.setFilterFid(featid))) for key in sorted(self.ofile.ObjAttributes.keys()): measured = float( bfeat.attribute(self.ofile.ObjAttributes[key][0])) simulated = float( bfeat.attribute(self.ofile.ObjAttributes[key][1])) factor = float(self.ofile.ObjAttributes[key][2]) error = (simulated - measured) * factor #error = (simulated - measured) / measured #error = (simulated - measured) sqrerr.append(error * error) sumsq = sum(sqrerr) / len(sqrerr) rmse = math.sqrt(sumsq) #Output string = '%4d ' % self.iterat string += '%11.5f ' % self.T string += '%5d ' % self.feval string += '%5.3f ' % self.p string += '%4d ' % self.accepted string += '%4d ' % self.declined if self.best.cost: string += ('%10.4f ' % self.best.cost) else: string += ('%10.4f ' % rmse) string += ('%10.4f ' % rmse) for i in self.best.x: string += '%10.4f ' % i for i in self.current.x: string += '%10.4f ' % i self.ui.textOptimization.append(string) f = open(self.logfile, 'a') f.write(string + '\n') f.close() return rmse