Example #1
0
    def __init__(self, name='', description='', options=[], default=None, isSource=False,
                 optional=False):
        Parameter.__init__(self, name, description, default, optional)
        isSource = parseBool(isSource)
        self.options = options
        if isSource:
            self.options = []
            layer = QgsVectorLayer(options[0], "layer", "ogr")
            if layer.isValid():
                try:
                    index = resolveFieldIndex(layer, options[1])
                    feats = features(layer)
                    for feature in feats:
                        self.options.append(unicode(feature.attributes()[index]))
                except ValueError:
                    pass
        elif isinstance(self.options, basestring):
            self.options = self.options.split(";")

        if default is not None:
            try:
                self.default = int(default)
            except:
                self.default = 0
            self.value = self.default
Example #2
0
    def __init__(self, name='', description='', options=[], default=None, isSource=False,
                 multiple=False, optional=False):
        Parameter.__init__(self, name, description, default, optional)
        self.multiple = multiple
        isSource = parseBool(isSource)
        self.options = options
        if isSource:
            self.options = []
            layer = QgsVectorLayer(options[0], "layer", "ogr")
            if layer.isValid():
                try:
                    index = resolveFieldIndex(layer, options[1])
                    feats = QgsProcessingUtils.getFeatures(layer, dataobjects.createContext())
                    for feature in feats:
                        self.options.append(str(feature.attributes()[index]))
                except ValueError:
                    pass
        elif isinstance(self.options, str):
            self.options = self.options.split(";")

        # compute options as (value, text)
        options = []
        for i, option in enumerate(self.options):
            if option is None or isinstance(option, basestring):
                options.append((i, option))
            else:
                options.append((option[0], option[1]))
        self.options = options
        self.values = [option[0] for option in options]

        self.value = None
Example #3
0
    def __init__(self, name='', description='', options=[], default=None, isSource=False,
                 multiple=False, optional=False):
        Parameter.__init__(self, name, description, default, optional)
        self.multiple = multiple
        isSource = parseBool(isSource)
        self.options = options
        if isSource:
            self.options = []
            layer = QgsVectorLayer(options[0], "layer", "ogr")
            if layer.isValid():
                try:
                    index = resolveFieldIndex(layer, options[1])
                    feats = QgsProcessingUtils.getFeatures(layer, dataobjects.createContext())
                    for feature in feats:
                        self.options.append(str(feature.attributes()[index]))
                except ValueError:
                    pass
        elif isinstance(self.options, str):
            self.options = self.options.split(";")

        # compute options as (value, text)
        options = []
        for i, option in enumerate(self.options):
            if option is None or isinstance(option, basestring):
                options.append((i, option))
            else:
                options.append((option[0], option[1]))
        self.options = options
        self.values = [option[0] for option in options]

        self.value = None
Example #4
0
    def __init__(self, name='', description='', options=[], default=None, isSource=False,
                 optional=False):
        Parameter.__init__(self, name, description, default, optional)
        isSource = parseBool(isSource)
        self.options = options
        if isSource:
            self.options = []
            layer = QgsVectorLayer(options[0], "layer", "ogr")
            if layer.isValid():
                try:
                    index = resolveFieldIndex(layer, options[1])
                    feats = features(layer)
                    for feature in feats:
                        self.options.append(unicode(feature.attributes()[index]))
                except ValueError:
                    pass
        elif isinstance(self.options, basestring):
            self.options = self.options.split(";")

        if default is not None:
            try:
                self.default = int(default)
            except:
                self.default = 0
            self.value = self.default
Example #5
0
    def processAlgorithm(self, feedback):
        inLayer = dataobjects.getObjectFromUri(
            self.getParameterValue(self.INPUT))
        boundary = self.getParameterValue(self.MODE) == self.MODE_BOUNDARY
        smallestArea = self.getParameterValue(
            self.MODE) == self.MODE_SMALLEST_AREA
        keepSelection = self.getParameterValue(self.KEEPSELECTION)
        processLayer = vector.duplicateInMemory(inLayer)

        if not keepSelection:
            # Make a selection with the values provided
            attribute = self.getParameterValue(self.ATTRIBUTE)
            comparison = self.comparisons[self.getParameterValue(
                self.COMPARISON)]
            comparisonvalue = self.getParameterValue(self.COMPARISONVALUE)

            selectindex = vector.resolveFieldIndex(processLayer, attribute)
            selectType = processLayer.fields()[selectindex].type()
            selectionError = False

            if selectType in [
                    QVariant.Int, QVariant.LongLong, QVariant.UInt,
                    QVariant.ULongLong
            ]:
                try:
                    y = int(comparisonvalue)
                except ValueError:
                    selectionError = True
                    msg = self.tr('Cannot convert "%s" to integer' %
                                  str(comparisonvalue))
            elif selectType == QVariant.Double:
                try:
                    y = float(comparisonvalue)
                except ValueError:
                    selectionError = True
                    msg = self.tr('Cannot convert "%s" to float' %
                                  str(comparisonvalue))
            elif selectType == QVariant.String:
                # 10: string, boolean
                try:
                    y = str(comparisonvalue)
                except ValueError:
                    selectionError = True
                    msg = self.tr('Cannot convert "%s" to Unicode' %
                                  str(comparisonvalue))
            elif selectType == QVariant.Date:
                # date
                dateAndFormat = comparisonvalue.split(' ')

                if len(dateAndFormat) == 1:
                    # QDate object
                    y = QLocale.system().toDate(dateAndFormat[0])

                    if y.isNull():
                        msg = self.tr(
                            'Cannot convert "%s" to date with system date format %s'
                            % (str(dateAndFormat),
                               QLocale.system().dateFormat()))
                elif len(dateAndFormat) == 2:
                    y = QDate.fromString(dateAndFormat[0], dateAndFormat[1])

                    if y.isNull():
                        msg = self.tr(
                            'Cannot convert "%s" to date with format string "%s"'
                            % (str(dateAndFormat[0]), dateAndFormat[1]))
                else:
                    y = QDate()
                    msg = ''

                if y.isNull():
                    # Conversion was unsuccessful
                    selectionError = True
                    msg += self.tr(
                        'Enter the date and the date format, e.g. "07.26.2011" "MM.dd.yyyy".'
                    )

            if (comparison == 'begins with' or comparison == 'contains') \
               and selectType != QVariant.String:
                selectionError = True
                msg = self.tr('"%s" can only be used with string fields' %
                              comparison)

            selected = []

            if selectionError:
                raise GeoAlgorithmExecutionException(
                    self.tr('Error in selection input: %s' % msg))
            else:
                for feature in processLayer.getFeatures():
                    aValue = feature.attributes()[selectindex]

                    if aValue is None:
                        continue

                    if selectType in [
                            QVariant.Int, QVariant.LongLong, QVariant.UInt,
                            QVariant.ULongLong
                    ]:
                        x = int(aValue)
                    elif selectType == QVariant.Double:
                        x = float(aValue)
                    elif selectType == QVariant.String:
                        # 10: string, boolean
                        x = str(aValue)
                    elif selectType == QVariant.Date:
                        # date
                        x = aValue  # should be date

                    match = False

                    if comparison == '==':
                        match = x == y
                    elif comparison == '!=':
                        match = x != y
                    elif comparison == '>':
                        match = x > y
                    elif comparison == '>=':
                        match = x >= y
                    elif comparison == '<':
                        match = x < y
                    elif comparison == '<=':
                        match = x <= y
                    elif comparison == 'begins with':
                        match = x.startswith(y)
                    elif comparison == 'contains':
                        match = x.find(y) >= 0

                    if match:
                        selected.append(feature.id())

            processLayer.selectByIds(selected)

        if processLayer.selectedFeatureCount() == 0:
            ProcessingLog.addToLog(
                ProcessingLog.LOG_WARNING,
                self.tr('%s: (No selection in input layer "%s")' %
                        (self.commandLineName(),
                         self.getParameterValue(self.INPUT))))

        # Keep references to the features to eliminate
        featToEliminate = []
        for aFeat in processLayer.selectedFeatures():
            featToEliminate.append(aFeat)

        # Delete all features to eliminate in processLayer (we won't save this)
        processLayer.startEditing()
        processLayer.deleteSelectedFeatures()

        # ANALYZE
        if len(featToEliminate) > 0:  # Prevent zero division
            start = 20.00
            add = 80.00 / len(featToEliminate)
        else:
            start = 100

        feedback.setProgress(start)
        madeProgress = True

        # We go through the list and see if we find any polygons we can
        # merge the selected with. If we have no success with some we
        # merge and then restart the whole story.
        while madeProgress:  # Check if we made any progress
            madeProgress = False
            featNotEliminated = []

            # Iterate over the polygons to eliminate
            for i in range(len(featToEliminate)):
                feat = featToEliminate.pop()
                geom2Eliminate = feat.geometry()
                bbox = geom2Eliminate.boundingBox()
                fit = processLayer.getFeatures(
                    QgsFeatureRequest().setFilterRect(
                        bbox).setSubsetOfAttributes([]))
                mergeWithFid = None
                mergeWithGeom = None
                max = 0
                min = -1
                selFeat = QgsFeature()

                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(
                    geom2Eliminate.geometry())
                engine.prepareGeometry()

                while fit.nextFeature(selFeat):
                    selGeom = selFeat.geometry()

                    if engine.intersects(selGeom.geometry()):
                        # We have a candidate
                        iGeom = geom2Eliminate.intersection(selGeom)

                        if not iGeom:
                            continue

                        if boundary:
                            selValue = iGeom.length()
                        else:
                            # area. We need a common boundary in
                            # order to merge
                            if 0 < iGeom.length():
                                selValue = selGeom.area()
                            else:
                                selValue = -1

                        if -1 != selValue:
                            useThis = True

                            if smallestArea:
                                if -1 == min:
                                    min = selValue
                                else:
                                    if selValue < min:
                                        min = selValue
                                    else:
                                        useThis = False
                            else:
                                if selValue > max:
                                    max = selValue
                                else:
                                    useThis = False

                            if useThis:
                                mergeWithFid = selFeat.id()
                                mergeWithGeom = QgsGeometry(selGeom)
                # End while fit

                if mergeWithFid is not None:
                    # A successful candidate
                    newGeom = mergeWithGeom.combine(geom2Eliminate)

                    if processLayer.changeGeometry(mergeWithFid, newGeom):
                        madeProgress = True
                    else:
                        raise GeoAlgorithmExecutionException(
                            self.
                            tr('Could not replace geometry of feature with id %s'
                               % mergeWithFid))

                    start = start + add
                    feedback.setProgress(start)
                else:
                    featNotEliminated.append(feat)

            # End for featToEliminate

            featToEliminate = featNotEliminated

        # End while

        # Create output
        output = self.getOutputFromName(self.OUTPUT)
        writer = output.getVectorWriter(processLayer.fields(),
                                        processLayer.wkbType(),
                                        processLayer.crs())

        # Write all features that are left over to output layer
        iterator = processLayer.getFeatures()
        for feature in iterator:
            writer.addFeature(feature)

        # Leave processLayer untouched
        processLayer.rollBack()

        for feature in featNotEliminated:
            writer.addFeature(feature)
Example #6
0
    def processAlgorithm(self, feedback):
        inLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.INPUT))
        boundary = self.getParameterValue(self.MODE) == self.MODE_BOUNDARY
        smallestArea = self.getParameterValue(self.MODE) == self.MODE_SMALLEST_AREA
        keepSelection = self.getParameterValue(self.KEEPSELECTION)
        processLayer = vector.duplicateInMemory(inLayer)

        if not keepSelection:
            # Make a selection with the values provided
            attribute = self.getParameterValue(self.ATTRIBUTE)
            comparison = self.comparisons[self.getParameterValue(self.COMPARISON)]
            comparisonvalue = self.getParameterValue(self.COMPARISONVALUE)

            selectindex = vector.resolveFieldIndex(processLayer, attribute)
            selectType = processLayer.fields()[selectindex].type()
            selectionError = False

            if selectType in [QVariant.Int, QVariant.LongLong, QVariant.UInt, QVariant.ULongLong]:
                try:
                    y = int(comparisonvalue)
                except ValueError:
                    selectionError = True
                    msg = self.tr('Cannot convert "%s" to integer' % str(comparisonvalue))
            elif selectType == QVariant.Double:
                try:
                    y = float(comparisonvalue)
                except ValueError:
                    selectionError = True
                    msg = self.tr('Cannot convert "%s" to float' % str(comparisonvalue))
            elif selectType == QVariant.String:
                # 10: string, boolean
                try:
                    y = str(comparisonvalue)
                except ValueError:
                    selectionError = True
                    msg = self.tr('Cannot convert "%s" to Unicode' % str(comparisonvalue))
            elif selectType == QVariant.Date:
                # date
                dateAndFormat = comparisonvalue.split(' ')

                if len(dateAndFormat) == 1:
                    # QDate object
                    y = QLocale.system().toDate(dateAndFormat[0])

                    if y.isNull():
                        msg = self.tr('Cannot convert "%s" to date with system date format %s' % (str(dateAndFormat), QLocale.system().dateFormat()))
                elif len(dateAndFormat) == 2:
                    y = QDate.fromString(dateAndFormat[0], dateAndFormat[1])

                    if y.isNull():
                        msg = self.tr('Cannot convert "%s" to date with format string "%s"' % (str(dateAndFormat[0]), dateAndFormat[1]))
                else:
                    y = QDate()
                    msg = ''

                if y.isNull():
                    # Conversion was unsuccessful
                    selectionError = True
                    msg += self.tr('Enter the date and the date format, e.g. "07.26.2011" "MM.dd.yyyy".')

            if (comparison == 'begins with' or comparison == 'contains') \
               and selectType != QVariant.String:
                selectionError = True
                msg = self.tr('"%s" can only be used with string fields' % comparison)

            selected = []

            if selectionError:
                raise GeoAlgorithmExecutionException(
                    self.tr('Error in selection input: %s' % msg))
            else:
                for feature in processLayer.getFeatures():
                    aValue = feature.attributes()[selectindex]

                    if aValue is None:
                        continue

                    if selectType in [QVariant.Int, QVariant.LongLong, QVariant.UInt, QVariant.ULongLong]:
                        x = int(aValue)
                    elif selectType == QVariant.Double:
                        x = float(aValue)
                    elif selectType == QVariant.String:
                        # 10: string, boolean
                        x = str(aValue)
                    elif selectType == QVariant.Date:
                        # date
                        x = aValue  # should be date

                    match = False

                    if comparison == '==':
                        match = x == y
                    elif comparison == '!=':
                        match = x != y
                    elif comparison == '>':
                        match = x > y
                    elif comparison == '>=':
                        match = x >= y
                    elif comparison == '<':
                        match = x < y
                    elif comparison == '<=':
                        match = x <= y
                    elif comparison == 'begins with':
                        match = x.startswith(y)
                    elif comparison == 'contains':
                        match = x.find(y) >= 0

                    if match:
                        selected.append(feature.id())

            processLayer.selectByIds(selected)

        if processLayer.selectedFeatureCount() == 0:
            ProcessingLog.addToLog(ProcessingLog.LOG_WARNING,
                                   self.tr('%s: (No selection in input layer "%s")' % (self.commandLineName(), self.getParameterValue(self.INPUT))))

        # Keep references to the features to eliminate
        featToEliminate = []
        for aFeat in processLayer.selectedFeatures():
            featToEliminate.append(aFeat)

        # Delete all features to eliminate in processLayer (we won't save this)
        processLayer.startEditing()
        processLayer.deleteSelectedFeatures()

        # ANALYZE
        if len(featToEliminate) > 0:  # Prevent zero division
            start = 20.00
            add = 80.00 / len(featToEliminate)
        else:
            start = 100

        feedback.setProgress(start)
        madeProgress = True

        # We go through the list and see if we find any polygons we can
        # merge the selected with. If we have no success with some we
        # merge and then restart the whole story.
        while madeProgress:  # Check if we made any progress
            madeProgress = False
            featNotEliminated = []

            # Iterate over the polygons to eliminate
            for i in range(len(featToEliminate)):
                feat = featToEliminate.pop()
                geom2Eliminate = feat.geometry()
                bbox = geom2Eliminate.boundingBox()
                fit = processLayer.getFeatures(
                    QgsFeatureRequest().setFilterRect(bbox).setSubsetOfAttributes([]))
                mergeWithFid = None
                mergeWithGeom = None
                max = 0
                min = -1
                selFeat = QgsFeature()

                # use prepared geometries for faster intersection tests
                engine = QgsGeometry.createGeometryEngine(geom2Eliminate.geometry())
                engine.prepareGeometry()

                while fit.nextFeature(selFeat):
                    selGeom = selFeat.geometry()

                    if engine.intersects(selGeom.geometry()):
                        # We have a candidate
                        iGeom = geom2Eliminate.intersection(selGeom)

                        if not iGeom:
                            continue

                        if boundary:
                            selValue = iGeom.length()
                        else:
                            # area. We need a common boundary in
                            # order to merge
                            if 0 < iGeom.length():
                                selValue = selGeom.area()
                            else:
                                selValue = -1

                        if -1 != selValue:
                            useThis = True

                            if smallestArea:
                                if -1 == min:
                                    min = selValue
                                else:
                                    if selValue < min:
                                        min = selValue
                                    else:
                                        useThis = False
                            else:
                                if selValue > max:
                                    max = selValue
                                else:
                                    useThis = False

                            if useThis:
                                mergeWithFid = selFeat.id()
                                mergeWithGeom = QgsGeometry(selGeom)
                # End while fit

                if mergeWithFid is not None:
                    # A successful candidate
                    newGeom = mergeWithGeom.combine(geom2Eliminate)

                    if processLayer.changeGeometry(mergeWithFid, newGeom):
                        madeProgress = True
                    else:
                        raise GeoAlgorithmExecutionException(
                            self.tr('Could not replace geometry of feature with id %s' % mergeWithFid))

                    start = start + add
                    feedback.setProgress(start)
                else:
                    featNotEliminated.append(feat)

            # End for featToEliminate

            featToEliminate = featNotEliminated

        # End while

        # Create output
        output = self.getOutputFromName(self.OUTPUT)
        writer = output.getVectorWriter(processLayer.fields(),
                                        processLayer.wkbType(), processLayer.crs())

        # Write all features that are left over to output layer
        iterator = processLayer.getFeatures()
        for feature in iterator:
            writer.addFeature(feature)

        # Leave processLayer untouched
        processLayer.rollBack()

        for feature in featNotEliminated:
            writer.addFeature(feature)
Example #7
0
    def processAlgorithm(self, progress):
        # Retrieve the values of the parameters entered by the user
        sourceLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.SOURCE_LAYER))
        sourceField = self.getParameterValue(self.SOURCE_FIELD)
        targetLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.TARGET_LAYER))
        targetField = self.getParameterValue(self.TARGET_FIELD)

        # check for tdg_id fields in source and target
        progress.setPercentage(0)
        progress.setInfo('Checking for tdg_id fields')
        exists = False
        try:
            vector.resolveFieldIndex(sourceLayer,'tdg_id')
            exists = True
        except:
            pass

        if not exists:
            raise GeoAlgorithmExecutionException('No tdg_id field was found in \
                layer %s' % sourceLayer.name())

        exists = False
        try:
            vector.resolveFieldIndex(targetLayer,'tdg_id')
            exists = True
        except:
            pass

        if not exists:
            raise GeoAlgorithmExecutionException('No tdg_id field was found in \
                layer %s' % targetLayer.name())
        progress.setPercentage(2)

        # build dict of source data
        progress.setInfo('Reading source values')
        count = 0
        totalCount = len(vector.features(sourceLayer))
        sourceValues = {}
        for feat in vector.features(sourceLayer):
            count += 1
            progress.setPercentage(2 + 28*count/totalCount)
            tdgId = feat['tdg_id']
            copyAttr = feat[sourceField]
            if tdgId is None:
                continue
            sourceValues[tdgId] = copyAttr

        # start editing
        targetLayer.startEditing()

        # iterate target features and assign matching source value
        progress.setInfo('Copying to target features')
        count = 0
        totalCount = len(vector.features(targetLayer))
        for targetFeat in vector.features(targetLayer):
            count += 1
            progress.setPercentage(30 + 68*count/totalCount)
            tdgId = targetFeat['tdg_id']
            if tdgId is None:
                continue
            if tdgId in sourceValues:
                fieldIndex = vector.resolveFieldIndex(targetLayer,targetField)
                sourceVal = sourceValues.get(tdgId)
                targetFeat.setAttribute(fieldIndex,sourceVal)
                targetLayer.updateFeature(targetFeat)

        progress.setInfo('Editing has been left active for layer %s.' % targetLayer.name())
        progress.setInfo('Please finalize this operation by checking values, saving your edits, and closing the edit session.')
Example #8
0
    def __init__(self,
                 input_network,
                 input_points, # must be qgsvectorlayer (either form disc or built from coord)
                 input_directionFieldName=None,
                 input_directDirectionValue=None,
                 input_reverseDirectionValue=None,
                 input_bothDirectionValue=None, 
                 input_defaultDirection=None):

        logPanel("__init__[QneatBaseCalculator]: setting up parameters")


        logPanel("__init__[QneatBaseCalculator]: setting up datasets")
        
        #init datasets and check for right geometry
        if isGeometryType(input_network, QGis.Line):
            self.input_network = input_network
        else:
            logPanel("ERROR: Network layer should be dataset layer")
            raise QneatGeometryException(input_network.geometryType(), QGis.Line)
        
        if isGeometryType(input_points, QGis.Point):
            self.input_points = input_points
        else:
            logPanel("ERROR: Point layer should be point dataset")
            raise QneatGeometryException(input_points.geometryType(), QGis.Point)
        
        #init computabiliyt and crs
        logPanel("__init__[QneatBaseCalculator]: checking computability")
        self.ComputabilityStatus = self.checkComputabilityStatus()
        
        if self.ComputabilityStatus == True:
            logPanel("__init__[QneatBaseCalculator]: computability OK")
            logPanel("__init__[QneatBaseCalculator]: setting up network analysis parameters")
            self.AnalysisCrs = self.setAnalysisCrs()
            
            #init direction fields
            self.directedAnalysis = self.checkIfDirected((input_directDirectionValue, input_reverseDirectionValue, input_bothDirectionValue, input_defaultDirection))
            if self.directedAnalysis == True:
                logPanel("...Analysis is directed")
                logPanel("...setting up Director")
                self.director = QgsLineVectorLayerDirector(self.input_network,
                                            resolveFieldIndex(self.input_network, input_directionFieldName),
                                            input_directDirectionValue,
                                            input_reverseDirectionValue,
                                            input_bothDirectionValue,
                                            input_defaultDirection)
            else:
                logPanel("...Analysis is undirected")
                logPanel("...defaulting to normal director")
                self.director = QgsLineVectorLayerDirector(self.input_network,
                                                         -1,
                                                         '',
                                                         '',
                                                         '',
                                                         3)
            
            #init graph analysis
            logPanel("__init__[QneatBaseCalculator]: setting up network analysis")
            logPanel("...getting all analysis points")
            self.list_input_points = self.input_points.getFeatures(QgsFeatureRequest().setFilterFids(self.input_points.allFeatureIds()))
        
            #Use distance as cost-strategy pattern.
            logPanel("...Setting distance as cost property")
            self.properter = QgsDistanceArcProperter()
            #add the properter to the QgsGraphDirector
            self.director.addProperter(self.properter)
            logPanel("...Setting the graph builders spatial reference")
            self.builder = QgsGraphBuilder(self.AnalysisCrs)
            #tell the graph-director to make the graph using the builder object and tie the start point geometry to the graph
            logPanel("...Tying input_points to the graph")
            self.list_tiedPoints = self.director.makeGraph(self.builder, getListOfPoints(self.input_points))
            #get the graph
            logPanel("...Build the graph")
            self.network = self.builder.graph()
            logPanel("__init__[QneatBaseCalculator]: init complete")
Example #9
0
    def processAlgorithm(self, progress):
        # Retrieve the values of the parameters entered by the user
        targetLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.TARGET_LAYER))
        targetFieldName = self.getParameterValue(self.TARGET_IDS)
        sourceLayer = dataobjects.getObjectFromUri(self.getParameterValue(self.SOURCE_LAYER))
        sourceFieldName = self.getParameterValue(self.SOURCE_IDS)
        tolerance = self.getParameterValue(self.TOLERANCE)
        method = self.METHODS[self.getParameterValue(self.METHOD)]
        keepNulls = self.getParameterValue(self.KEEP_NULLS)

        # get input field types and set output fields
        targetField = targetLayer.dataProvider().fields().at(vector.resolveFieldIndex(targetLayer, targetFieldName))
        sourceField = sourceLayer.dataProvider().fields().at(vector.resolveFieldIndex(sourceLayer, sourceFieldName))
        fields = QgsFields()
        fields.append(QgsField("target_id", targetField.type()))
        fields.append(QgsField("source_id", sourceField.type()))

        # set up output writer
        writer = self.getOutputFromName(self.OUT_LAYER).getVectorWriter(fields, QGis.WKBLineString, targetLayer.crs())

        # create spatial index for source features
        progress.setInfo("Indexing source features")
        index = vector.spatialindex(sourceLayer)

        # build dictionary of source features
        sourceFeatures = {}
        for sourceFeat in vector.features(sourceLayer):
            sourceFeatures[sourceFeat.id()] = sourceFeat

        # loop through target features
        progress.setInfo("Checking target features")
        targetFeats = vector.features(targetLayer)
        count = 0
        totalCount = len(targetFeats)
        progress.setInfo("%i target features identified" % totalCount)
        for targetFeat in targetFeats:
            count += 1
            progress.setPercentage(int(100 * count / totalCount))
            outFeat = QgsFeature(fields)
            targetId = targetFeat[targetFieldName]
            if targetId is None:
                raise GeoAlgorithmExecutionException("Target ID value cannot be empty")
            matchId = None
            outFeat.setAttribute(0, targetId)

            targetGeom = QgsGeometry(targetFeat.geometry())
            if targetGeom is None:
                continue

            # get first, last, and mid points of target feature
            targetLength = targetGeom.length()
            firstPoint = targetGeom.interpolate(0)
            midPoint = targetGeom.interpolate(targetLength * 0.5)
            lastPoint = targetGeom.interpolate(targetLength)

            # get source features within the tolerance
            targetBox = targetGeom.buffer(tolerance, 5).boundingBox()
            sourceIds = index.intersects(targetBox)

            # test for nearness
            if method == "Midpoint":
                # get the 5 nearest neighbors
                midSourceIds = index.nearestNeighbor(midPoint.asPoint(), 5)

                # loop through the nearest features and identify the closest match
                minDist = -1
                for sourceId in midSourceIds:
                    if not sourceId in sourceIds:
                        continue
                    sourceGeom = QgsGeometry(sourceFeatures.get(sourceId).geometry())

                    if midPoint.distance(sourceGeom) > tolerance:
                        continue

                    # get distances
                    firstDist = firstPoint.distance(sourceGeom)
                    lastDist = lastPoint.distance(sourceGeom)

                    if minDist < 0:
                        minDist = firstDist + lastDist
                        matchId = sourceFeatures[sourceId][sourceFieldName]
                    elif minDist > firstDist + lastDist:
                        minDist = firstDist + lastDist
                        matchId = sourceFeatures[sourceId][sourceFieldName]
            elif method == "Thirds":
                thirdPoint = targetGeom.interpolate(targetLength * 0.33)
                twoThirdsPoint = targetGeom.interpolate(targetLength * 0.67)

                # get the 5 nearest neighbors
                thirdSourceIds = index.nearestNeighbor(thirdPoint.asPoint(), 5)
                twoThirdsSourceIds = index.nearestNeighbor(twoThirdsPoint.asPoint(), 5)

                # find common ids

                # loop through the nearest features and identify the closest match
                minDist = -1
                for sourceId in midSourceIds:
                    if not sourceId in sourceIds:
                        continue
                    sourceGeom = QgsGeometry(sourceFeatures.get(sourceId).geometry())

                    if midPoint.distance(sourceGeom) > tolerance:
                        continue

                    # get distances
                    firstDist = firstPoint.distance(sourceGeom)
                    lastDist = lastPoint.distance(sourceGeom)

                    if minDist < 0:
                        minDist = firstDist + lastDist
                        matchId = sourceFeatures[sourceId][sourceFieldName]
                    elif minDist > firstDist + lastDist:
                        minDist = firstDist + lastDist
                        matchId = sourceFeatures[sourceId][sourceFieldName]
            elif method == "Endpoints":
                avgDist = None

                for sourceId in sourceIds:
                    sourceGeom = QgsGeometry(sourceFeatures.get(sourceId).geometry())

                    # skip a feature if it's not at least 1/2 as long as target
                    if sourceGeom.length() < (targetGeom.length() * 0.5):
                        continue

                    # get distances
                    firstDist = firstPoint.distance(sourceGeom)
                    midDist = midPoint.distance(sourceGeom)
                    lastDist = lastPoint.distance(sourceGeom)

                    # skip a feature if it's beyond the tolerance at any point on the target
                    if firstDist > tolerance or midDist > tolerance or lastDist > tolerance:
                        continue

                    # check deviation
                    dev = sqrt((midDist - firstDist) ** 2 + (midDist - lastDist) ** 2)
                    if dev > (targetLength / 2):
                        continue

                    # get the closest match
                    checkDist = sum([firstDist, midDist, lastDist]) / 3

                    if avgDist is None:
                        avgDist = checkDist
                        matchId = sourceFeatures[sourceId][sourceFieldName]
                    elif checkDist < avgDist:
                        avgDist = checkDist
                        matchId = sourceFeatures[sourceId][sourceFieldName]

            outFeat.setAttribute(1, matchId)
            outFeat.setGeometry(targetGeom)
            if keepNulls:
                writer.addFeature(outFeat)
            elif not matchId is None:
                writer.addFeature(outFeat)

        del writer