Ejemplo n.º 1
0
    def processAlgorithm(self, parameters, context, feedback):
        input_layers = self.parameterAsLayerList(parameters, self.INPUT_LAYERS,
                                                 context)

        camera_model = self.parameterAsEnum(parameters, self.CAMERA_MODEL,
                                            context)
        camera_model = list(self.CAMERA_DATA)[camera_model]
        camera_data = self.CAMERA_DATA[camera_model]

        sourceCRS = self.parameterAsCrs(parameters, self.SOURCE_CRS, context)
        if sourceCRS is None or not sourceCRS.isValid():
            sourceCRS = uavImagesFolder.crs()

        destinationCRS = self.parameterAsCrs(parameters, self.DESTINATION_CRS,
                                             context)
        if destinationCRS is None or not destinationCRS.isValid():
            feedback.pushInfo(
                self.tr('Getting destination CRS from source image'))
            destinationCRS = sourceCRS

        feedback.pushInfo(
            self.tr('Source CRS is: ') + self.tr(sourceCRS.authid()))
        feedback.pushInfo(
            self.tr('Destination CRS is: ') + self.tr(destinationCRS.authid()))

        # set fields for footprint and nadir vectors
        fields = QgsFields()
        fields.append(QgsField('date_time', QVariant.String))
        fields.append(QgsField('gimball_pitch', QVariant.Double))
        fields.append(QgsField('gimball_roll', QVariant.Double))
        fields.append(QgsField('gimball_jaw', QVariant.Double))
        fields.append(QgsField('relative_altitude', QVariant.Double))
        fields.append(QgsField('layer', QVariant.String))
        fields.append(QgsField('path', QVariant.String))
        fields.append(QgsField('camera_model', QVariant.String))
        fields.append(QgsField('camera_vertical_FOV', QVariant.Double))
        fields.append(QgsField('camera_horizontal_FOV', QVariant.Double))
        fields.append(QgsField('nadir_to_bottom_offset', QVariant.Double))
        fields.append(QgsField('nadir_to_upper_offset', QVariant.Double))

        (footprintSink, footprint_dest_id) = self.parameterAsSink(
            parameters, self.OUTPUT_FOOTPRINTS, context, fields,
            QgsWkbTypes.Polygon, destinationCRS)
        if footprintSink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT_FOOTPRINTS))

        (nadirSink, nadir_dest_id) = self.parameterAsSink(
            parameters, self.OUTPUT_NADIRS, context, fields, QgsWkbTypes.Point,
            destinationCRS)
        if nadirSink is None:
            raise QgsProcessingException(
                self.invalidSinkError(parameters, self.OUTPUT_NADIRS))

        # use tese params only if Camera modes is set to Advanced
        horizontalFOV = self.parameterAsDouble(parameters, self.HORIZONTAL_FOV,
                                               context)
        verticalFOV = self.parameterAsDouble(parameters, self.VERTICAL_FOV,
                                             context)
        nadirToBottomOffset = self.parameterAsDouble(
            parameters, self.NADIR_TO_BOTTOM_OFFSET, context)
        nadirToupperOffset = self.parameterAsDouble(
            parameters, self.NADIR_TO_UPPPER_OFFSET, context)

        self.CAMERA_DATA[camera_model]['horizontal_FOV'] = horizontalFOV
        self.CAMERA_DATA[camera_model]['vertical_FOV'] = verticalFOV
        self.CAMERA_DATA[camera_model][
            'nadir_to_bottom_offset'] = nadirToBottomOffset
        self.CAMERA_DATA[camera_model][
            'nadir_to_upper_offset'] = nadirToupperOffset

        # loop for each file
        progress_step = 100.0 / len(input_layers)

        feedback.pushInfo("Going to process: {} images".format(
            len(input_layers)))
        for index, input_layer in enumerate(input_layers):
            try:
                index += 1

                if feedback.isCanceled():
                    return {}

                if isinstance(input_layer, str):
                    source = input_layer
                else:
                    source = input_layer.source()
                feedback.pushInfo("##### {}:Processing image: {}".format(
                    index, source))

                # extract exif and XMP data
                try:
                    gdal.UseExceptions()
                    dataFrame = gdal.Open(source, gdal.GA_ReadOnly)
                    domains = dataFrame.GetMetadataDomainList()

                    # get exif metadata
                    exifTags = dataFrame.GetMetadata()
                    for key, value in exifTags.items():
                        #print(key, ':', value)
                        pass

                    # select metadata from XMP domain only
                    droneMetadata = {}
                    for domain in domains:
                        metadata = dataFrame.GetMetadata(domain)

                        # skip not relevant tags
                        if isinstance(metadata, dict):
                            for key, value in metadata.items():
                                #print(domain, "--", key, ":", value)
                                pass

                        # probably XMPs
                        if isinstance(metadata, list):
                            if domain == 'xml:XMP':
                                # parse xml
                                root = ElementTree.XML(metadata[0])
                                xmldict = XmlDictConfig(root)

                                # skip first element containing only description and domain info
                                subdict = list(xmldict.values())[0]

                                # get XMP tags
                                subdict = list(subdict.values())[0]
                                # parse XMP stuffs removing head namespace in the key
                                # e.g.
                                #    {http://www.dji.com/drone-dji/1.0/}AbsoluteAltitude
                                # become
                                #    AbsoluteAltitude
                                for key, value in subdict.items():
                                    #print(domain, '--', key, value)
                                    key = key.split('}')[1]
                                    droneMetadata[key] = value

                except Exception as ex:
                    raise QgsProcessingException(str(ex))

                # extract all important tagged information about the image

                # get image lat/lon that will be the coordinates of nadir point
                # converted to destination CRS
                lat = _convert_to_degress(exifTags['EXIF_GPSLatitude'])
                emisphere = exifTags['EXIF_GPSLatitudeRef']
                lon = _convert_to_degress(exifTags['EXIF_GPSLongitude'])
                lonReference = exifTags['EXIF_GPSLongitudeRef']

                if emisphere == 'S':
                    lat = -lat
                if lonReference == 'W':
                    lon = -lon

                exifDateTime = exifTags['EXIF_DateTime']
                feedback.pushInfo("EXIF_DateTime: " + exifDateTime)

                exifImageWidth = exifTags['EXIF_PixelXDimension']
                exifImageLength = exifTags['EXIF_PixelYDimension']
                imageRatio = float(exifImageWidth) / float(exifImageLength)
                feedback.pushInfo("EXIF_PixelXDimension: " + exifImageWidth)
                feedback.pushInfo("EXIF_PixelYDimension: " + exifImageLength)
                feedback.pushInfo("Image ratio: " + str(imageRatio))

                # drone especific metadata
                droneMaker = exifTags['EXIF_Make']
                droneModel = exifTags['EXIF_Model']
                feedback.pushInfo("EXIF_Make: " + droneMaker)
                feedback.pushInfo("EXIF_Model: " + droneModel)

                # drone maker substitute XMP drone dictKey
                dictKey = droneMaker

                relativeAltitude = float(droneMetadata['RelativeAltitude'])
                feedback.pushInfo(
                    self.tr("XMP {}:RelativeAltitude: ".format(dictKey)) +
                    str(relativeAltitude))

                gimballRoll = float(droneMetadata['GimbalRollDegree'])
                gimballPitch = float(droneMetadata['GimbalPitchDegree'])
                gimballYaw = float(droneMetadata['GimbalYawDegree'])
                feedback.pushInfo("XMP {}:GimbalRollDegree: ".format(dictKey) +
                                  str(gimballRoll))
                feedback.pushInfo(
                    "XMP {}:GimbalPitchDegree: ".format(dictKey) +
                    str(gimballPitch))
                feedback.pushInfo("XMP {}:GimbalYawDegree: ".format(dictKey) +
                                  str(gimballYaw))

                flightRoll = float(droneMetadata['FlightRollDegree'])
                flightPitch = float(droneMetadata['FlightPitchDegree'])
                flightYaw = float(droneMetadata['FlightYawDegree'])
                feedback.pushInfo("XMP {}:FlightRollDegree: ".format(dictKey) +
                                  str(flightRoll))
                feedback.pushInfo(
                    "XMP {}:FlightPitchDegree: ".format(dictKey) +
                    str(flightPitch))
                feedback.pushInfo("XMP {}:FlightYawDegree: ".format(dictKey) +
                                  str(flightYaw))

                feedback.pushInfo(
                    self.tr("Horizontal FOV: ") + str(horizontalFOV))
                feedback.pushInfo(self.tr("Vertical FOV: ") + str(verticalFOV))

                # do calculation inspired by:
                # https://photo.stackexchange.com/questions/56596/how-do-i-calculate-the-ground-footprint-of-an-aerial-camera
                # distance of the nearest point to nadir (bottom distance)
                bottomDistance = relativeAltitude * (math.tan(
                    math.radians(90 - gimballPitch - 0.5 * verticalFOV)))
                # distance of the farest point to nadir (upper distance)
                upperDistance = relativeAltitude * (math.tan(
                    math.radians(90 - gimballPitch + 0.5 * verticalFOV)))

                feedback.pushInfo(
                    self.tr("Northing (degree): ") + str(gimballYaw))
                feedback.pushInfo(
                    self.tr("Nadir to bottom distance (metre): ") +
                    str(bottomDistance))
                feedback.pushInfo(
                    self.tr("Nadir to upper distance (metre): ") +
                    str(upperDistance))

                # create base feature to add
                layerName = os.path.basename(source)
                layerName = os.path.splitext(layerName)[0]

                feature = QgsFeature(fields)
                feature.setAttribute('date_time', exifDateTime)
                feature.setAttribute('gimball_pitch', gimballPitch)
                feature.setAttribute('gimball_roll', gimballRoll)
                feature.setAttribute('gimball_jaw', gimballYaw)
                feature.setAttribute('relative_altitude', relativeAltitude)
                feature.setAttribute('layer', layerName)
                feature.setAttribute('path', source)
                feature.setAttribute('camera_model', droneModel)
                feature.setAttribute('camera_vertical_FOV', verticalFOV)
                feature.setAttribute('camera_horizontal_FOV', horizontalFOV)
                feature.setAttribute('nadir_to_bottom_offset',
                                     nadirToBottomOffset)
                feature.setAttribute('nadir_to_upper_offset',
                                     nadirToupperOffset)

                # populate nadir layer
                droneLocation = QgsPoint(lon, lat)
                tr = QgsCoordinateTransform(sourceCRS, destinationCRS,
                                            QgsProject.instance())
                droneLocation.transform(tr)
                feedback.pushInfo(
                    self.tr("Nadir coordinates (lon, lat): ") +
                    '{}, {}'.format(droneLocation.x(), droneLocation.y()))

                nadirGeometry = QgsGeometry.fromPointXY(
                    QgsPointXY(droneLocation.x(), droneLocation.y()))
                feature.setGeometry(nadirGeometry)
                nadirSink.addFeature(feature, QgsFeatureSink.FastInsert)

                # create footprint to add to footprint sink
                feature = QgsFeature(feature)
                footprint = QgsGeometry.createWedgeBuffer(
                    QgsPoint(droneLocation.x(), droneLocation.y()), gimballYaw,
                    horizontalFOV,
                    abs(bottomDistance) + nadirToBottomOffset,
                    abs(upperDistance) + nadirToupperOffset)
                feature.setGeometry(footprint)
                footprintSink.addFeature(feature, QgsFeatureSink.FastInsert)

                feedback.setProgress(int(index * progress_step))
            except Exception as ex:
                exc_type, exc_obj, exc_trace = sys.exc_info()
                trace = traceback.format_exception(exc_type, exc_obj,
                                                   exc_trace)
                raise QgsProcessingException(''.join(trace))

        # Return the results
        results = {
            self.OUTPUT_FOOTPRINTS: footprint_dest_id,
            self.OUTPUT_NADIRS: nadir_dest_id,
        }
        return results