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" )
Example #2
0
    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
Example #3
0
 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
Example #6
0
    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}
Example #7
0
    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)
Example #9
0
    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)])
Example #10
0
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
Example #11
0
    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()"))
Example #14
0
    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)
Example #16
0
    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()
Example #17
0
    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')
Example #18
0
    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)])
Example #19
0
    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()" ) )
Example #20
0
    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)
Example #21
0
    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