【问题标题】:Getting null values on ExifInterface when trying to get Latitude and Longitude data尝试获取纬度和经度数据时在 ExifInterface 上获取空值
【发布时间】:2021-08-03 04:43:43
【问题描述】:

我的目标是让 Camera2 API 拍摄一张新照片,并使用该照片通过代码实现 exif 数据,使用 ExifInterface。不过,我偶然发现了一个小问题。当我运行该应用程序时,当我按下捕获按钮并保存所需的 JPEG 文件时,它会正确运行,尽管当代码尝试添加 exif 数据时,它会显示 null。我添加了我需要的所有内容,即setAttribute() 来设置新数据并覆盖我需要添加的数据,getAttribute()Log.e() 显示它以查看结果。但是 exif 上的结果显示为空。我已经在单独的课程中对 exif 数据进行了一些计算,以确保让我感到舒适,有些人会在同一个课程上做,但分开做。这是 Logcat 中的消息:

E/LATITUDE: null
E/LONGITUDE: null
D/Camera2BasicFragment: /storage/emulated/0/Android/data/com.example.camera2apikotlin4/files/pic.jpg

这是 exif 数据的照片和屏幕截图,实际上是空白且未保存: The picture that took with the app The screenshot that shows with Exif Pilot the exif data

这是file 变量的来源和保存位置:

private lateinit var file: File   

override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        //val PIC_FILE_NAME = SimpleDateFormat("dd.MM.yyyy-HH:mm:ss", Locale.ENGLISH).format(System.currentTimeMillis()) + ".jpg"
        val PIC_FILE_NAME = "pic.jpg"
        file = File(requireActivity().getExternalFilesDir(null), PIC_FILE_NAME)
    }

从哪里开始捕获过程:

@RequiresApi(Build.VERSION_CODES.Q)
        private fun process(result: CaptureResult) {
            when (state) {
                STATE_PREVIEW -> Unit // Do nothing when the camera preview is working normally.
                STATE_WAITING_LOCK -> capturePicture(result)
                STATE_WAITING_PRECAPTURE -> {
                    // CONTROL_AE_STATE can be null on some devices
                    val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
                    if (aeState == null ||
                        aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                        aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                        state = STATE_WAITING_NON_PRECAPTURE
                    }
                }
                STATE_WAITING_NON_PRECAPTURE -> {
                    // CONTROL_AE_STATE can be null on some devices
                    val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
                    if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                        state = STATE_PICTURE_TAKEN
                        captureStillPicture()
                    }
                }
            }
        }

        @RequiresApi(Build.VERSION_CODES.Q)
        private fun capturePicture(result: CaptureResult) {
            val afState = result.get(CaptureResult.CONTROL_AF_STATE)
            if (afState == null) {
                captureStillPicture()
            } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
                || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
                // CONTROL_AE_STATE can be null on some devices
                val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
                if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                    state = STATE_PICTURE_TAKEN
                    captureStillPicture()
                } else {
                    runPrecaptureSequence()
                }
            }
        }

        @RequiresApi(Build.VERSION_CODES.Q)
        override fun onCaptureProgressed(session: CameraCaptureSession,
                                         request: CaptureRequest,
                                         partialResult: CaptureResult) {
            process(partialResult)
        }

        @RequiresApi(Build.VERSION_CODES.Q)
        override fun onCaptureCompleted(session: CameraCaptureSession,
                                        request: CaptureRequest,
                                        result: TotalCaptureResult) {
            process(result)
        }

    }

这是分隔代码geoDegree():

package com.example.camera2apikotlin4

import androidx.exifinterface.media.ExifInterface

class geoDegree {
    private var valid: Boolean = true
    var latitudeFloat: Double = 0.0
    var longitudeFloat: Double = 0.0

    fun geoDegree(exif: ExifInterface) {
        val attrLATITUDE = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
        val attrLATITUDE_REF = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF)
        val attrLONGITUDE = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
        val attrLONGITUDE_REF = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF)

        if (
            (attrLATITUDE != null) &&
            (attrLATITUDE_REF != null) &&
            (attrLONGITUDE != null) &&
            (attrLONGITUDE_REF != null))
            {
                //Telling the code that the validation is true
                valid = true

                //If the reference of the latitude is equals to the letter N, it will convert it to degrees, else it will be none
                if(attrLATITUDE_REF == "N") {
                    latitudeFloat = convertToDegree(attrLATITUDE)
                } else {
                    0 - convertToDegree(attrLATITUDE)
                }

                //If the reference of the longitude is equals to the letter E, it will convert it to degrees, else it will be none
                if(attrLONGITUDE_REF == "E") {
                    longitudeFloat = convertToDegree(attrLONGITUDE)
                } else {
                    0 - convertToDegree(attrLONGITUDE)
                }
        }
    }

    //The method function that converting the degrees, using the list of strings
    private fun convertToDegree(stringDMS: String): Double {
        val result: Double?
        val DMS: List<String> = stringDMS.split(",")

        //Values in degrees
        val stringD: List<String> = DMS[0].split("/")
        val D0 = stringD[0].toDouble()
        val D1 = stringD[1].toDouble()
        val FloatD: Double = D0 / D1

        //Values in minutes
        val stringM = DMS[1].split("/")
        val M0 = stringM[0].toDouble()
        val M1 = stringM[1].toDouble()
        val FloatM: Double = M0 / M1

        //Values in seconds
        val stringS = DMS[2].split("/")
        val S0 = stringS[0].toDouble()
        val S1 = stringS[1].toDouble()
        val FloatS = S0 / S1

        /**Overall results to display on, when combining the Float Degrees
         * and calculating with Minutes about 60, Seconds with 3600 and overall to be as Float type
         **/
        result = (FloatD + (FloatM/60) + (FloatS/3600))

        return result
    }

    fun isValid(): Boolean {
        return valid
    }

    override fun toString(): String {
        return ("$latitudeFloat, $longitudeFloat")
    }

    fun getLatitudeE6(): Int {
        return (latitudeFloat.times(1000000)).toInt()
    }

    fun getLongitudeE6(): Int {
        return (longitudeFloat.times(1000000)).toInt()
    }
}

这是imageCapture中的主要代码:

/**
     * Capture a still picture. This method should be called when we get a response in
     * [.captureCallback] from both [.lockFocus].
     */
    @RequiresApi(Build.VERSION_CODES.Q)
    private fun captureStillPicture() {
        try {
            if (activity == null || cameraDevice == null) return
            val rotation = requireActivity().windowManager.defaultDisplay.rotation

            // This is the CaptureRequest.Builder that we use to take a picture.
            val captureBuilder = cameraDevice?.createCaptureRequest(
                CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply {
                imageReader?.surface?.let { addTarget(it) }

                // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
                // We have to take that into account and rotate JPEG properly.
                // For devices with orientation of 90, we return our mapping from ORIENTATIONS.
                // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
                set(CaptureRequest.JPEG_ORIENTATION,
                    (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360)

                // Use the same AE and AF modes as the preview.
                set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)


            }?.also { setAutoFlash(it) }

            val captureCallback = object : CameraCaptureSession.CaptureCallback() {

                override fun onCaptureCompleted(session: CameraCaptureSession,
                                                request: CaptureRequest,
                                                result: TotalCaptureResult) {
                    Toast.makeText(context, "Saved: $file", Toast.LENGTH_SHORT).show()
                    Log.d(TAG, file.toString())
                    unlockFocus()
                }
            }.apply {
                val exif = ExifInterface(file)
                geoDegree().geoDegree(exif)

                exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, "${geoDegree().getLatitudeE6()}")
                exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "${geoDegree().getLongitudeE6()}")
                exif.saveAttributes()
                geoDegree().isValid()

                val exifLatitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
                val exifLongitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
                Log.e("LATITUDE", "$exifLatitude")
                Log.e("LONGITUDE", "$exifLongitude")
            }

            captureSession?.apply {
                stopRepeating()
                abortCaptures()
                captureBuilder?.build()?.let { capture(it, captureCallback, null) }
            }
        } catch (e: CameraAccessException) {
            Log.e(TAG, e.toString())
        }

    }

任何提示、技巧、建议,以及任何真正有助于解决要解决的小问题的东西。这对你来说可能很容易,但我仍在学习如何去做。提前谢谢你。

【问题讨论】:

  • @blackapps 对此我深表歉意,现在代码在帖子中。
  • 我们看不到您在哪里使用了 file 变量。那么如何记录保存在该文件中的内容呢?
  • @blackapps 好的,所以我再次编辑了代码,所以 file 在外部作为文件类型的私有变量,然后在覆盖方法 onActivityCreated(savedInstanceState: Bundle?) 中使用,使其位于它假设保存文件,因为它在活动片段中使用和转换。
  • 我们仍然没有看到您在某处使用该文件实例来拍照。
  • @blackapps 哦,我真的很抱歉浪费了你的时间,我真的还是这个平台的新手,加上如何解释,但我仍然让你感到困惑,等待至少 10 分钟并刷新获取照片拍摄位置的代码。真的很抱歉。

标签: android kotlin


【解决方案1】:

如果问题发生在 Android API >= 29 中,则考虑从 29 及以上版本开始,地理位置已被编辑,因为这现在被视为敏感信息。这意味着如果你想读回地理位置,那么你需要先请求原始的Uri,并将获得的Uri与ExifInterface或打开一个流一起使用,否则无法取回地理位置:

https://developer.android.com/reference/android/provider/MediaStore#setRequireOriginal(android.net.Uri)

originalUri = MediaStore.setRequireOriginal(photoUri)

此外,您还需要/请求ACCESS_MEDIA_LOCATION 权限。没有它,您的应用将无权读取地理位置。

【讨论】:

    【解决方案2】:

    假设您的代码使用您设置为捕获目标的 ImageReader 并将图像保存到您设置的文件中,我看到的主要缺失是您没有将任何位置信息放入 JPEG 图像.

    相机 API 无法直接访问位置信息;如果您想要在最终 JPEG 中包含位置的 EXIF 元数据,您必须先自己将其提供给相机 API: https://developer.android.com/reference/kotlin/android/hardware/camera2/CaptureRequest#jpeg_gps_location

    为此,您需要获取一个 Location 对象(这需要 LOCATION 权限之一),此时,我不确定您是否真的需要解析 EXIF 以获取某个位置,而不仅仅是使用 Location 对象开始吧。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-15
      • 1970-01-01
      • 2019-03-25
      • 2016-09-29
      • 1970-01-01
      • 2019-10-27
      相关资源
      最近更新 更多