You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

安卓开发:实现拍照/图库图片添加GeoTag功能求助

嘿,我之前做过类似的功能,给你整理了一套完整的解决方案,一步步来应该能解决你的问题:

安卓拍照/选图添加并显示GeoTag 实现方案

一、先搞定基础权限配置

GeoTag依赖GPS位置信息,同时拍照和访问图库也需要对应权限,先在AndroidManifest.xml里声明:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Android 13+ 访问图库 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

Android 6.0以上需要动态申请权限,在Activity里加这段逻辑:

private val requiredPermissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION)
private const val PERMISSION_REQUEST_CODE = 1001

private fun checkAndRequestPermissions() {
    val isPermissionsGranted = requiredPermissions.all {
        ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
    }
    if (!isPermissionsGranted) {
        ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE)
    }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == PERMISSION_REQUEST_CODE) {
        if (grantResults.any { it != PackageManager.PERMISSION_GRANTED }) {
            Toast.makeText(this, "相机和位置权限是功能必需的哦", Toast.LENGTH_SHORT).show()
        }
    }
}

二、拍照时嵌入GeoTag(用谷歌推荐的CameraX)

CameraX比旧版Camera API更稳定适配性更好,先在build.gradle添加依赖:

dependencies {
    def camerax_version = "1.3.0"
    implementation "androidx.camera:camera-core:$camerax_version"
    implementation "androidx.camera:camera-camera2:$camerax_version"
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    implementation "androidx.camera:camera-view:$camerax_version"
}

然后实现拍照+获取位置+写入EXIF的完整流程:

  1. 先获取当前位置(用FusedLocationProviderClient):
private lateinit var fusedLocationClient: FusedLocationProviderClient

private fun getCurrentLocation(): Task<Location> {
    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
    return fusedLocationClient.lastLocation
}
  1. 拍照并写入GeoTag到图片EXIF:
private val imageCapture = ImageCapture.Builder().build()

// 拍照按钮点击事件
fun takePhoto() {
    getCurrentLocation().addOnSuccessListener { location ->
        val photoFile = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

        imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
            override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                location?.let {
                    writeGeoTagToExif(photoFile.absolutePath, it.latitude, it.longitude)
                }
                // 拍完直接显示图片和GeoTag
                displayImageWithGeoTag(photoFile.absolutePath)
            }

            override fun onError(exception: ImageCaptureException) {
                Toast.makeText(this@MainActivity, "拍照失败:${exception.message}", Toast.LENGTH_SHORT).show()
            }
        })
    }.addOnFailureListener {
        Toast.makeText(this, "获取位置失败:${it.message}", Toast.LENGTH_SHORT).show()
    }
}

// 将经纬度写入图片EXIF的工具方法
private fun writeGeoTagToExif(imagePath: String, latitude: Double, longitude: Double) {
    try {
        val exifInterface = ExifInterface(imagePath)
        // 转换经纬度为EXIF要求的度分秒格式
        val latDeg = latitude.toInt()
        val latMin = ((latitude - latDeg) * 60).toInt()
        val latSec = ((latitude - latDeg - latMin/60.0) * 3600 * 1000).toInt()
        val lonDeg = longitude.toInt()
        val lonMin = ((longitude - lonDeg) * 60).toInt()
        val lonSec = ((longitude - lonDeg - lonMin/60.0) * 3600 * 1000).toInt()

        exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE, "$latDeg/1,$latMin/1,$latSec/1000")
        exifInterface.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, if (latitude >= 0) "N" else "S")
        exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "$lonDeg/1,$lonMin/1,$lonSec/1000")
        exifInterface.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, if (longitude >= 0) "E" else "W")
        exifInterface.saveAttributes()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

三、从图库选图读取GeoTag

实现图库选择逻辑,并读取图片的EXIF地理信息:

private const val GALLERY_REQUEST_CODE = 1002

// 打开图库按钮点击事件
fun openGallery() {
    val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
    startActivityForResult(intent, GALLERY_REQUEST_CODE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == GALLERY_REQUEST_CODE && resultCode == RESULT_OK && data != null) {
        val imageUri = data.data
        imageUri?.let { uri ->
            val imagePath = getRealPathFromUri(uri)
            imagePath?.let {
                displayImageWithGeoTag(it)
            }
        }
    }
}

// 适配不同系统版本,获取图片真实路径
private fun getRealPathFromUri(uri: Uri): String? {
    var path: String? = null
    val projection = arrayOf(MediaStore.Images.Media.DATA)
    contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
        if (cursor.moveToFirst()) {
            val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
            path = cursor.getString(columnIndex)
        }
    }
    return path
}

// 读取图片EXIF中的GeoTag
private fun readGeoTagFromExif(imagePath: String): Pair<Double, Double>? {
    try {
        val exifInterface = ExifInterface(imagePath)
        val latStr = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
        val latRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF)
        val lonStr = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
        val lonRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF)

        if (!latStr.isNullOrEmpty() && !latRef.isNullOrEmpty() && !lonStr.isNullOrEmpty() && !lonRef.isNullOrEmpty()) {
            // 把度分秒格式转成十进制经纬度
            val latParts = latStr.split(",").map { part ->
                val split = part.split("/")
                split[0].toDouble() / split[1].toDouble()
            }
            val latitude = latParts[0] + latParts[1]/60 + latParts[2]/3600
            val correctedLat = if (latRef == "S") -latitude else latitude

            val lonParts = lonStr.split(",").map { part ->
                val split = part.split("/")
                split[0].toDouble() / split[1].toDouble()
            }
            val longitude = lonParts[0] + lonParts[1]/60 + lonParts[2]/3600
            val correctedLon = if (lonRef == "W") -longitude else longitude

            return Pair(correctedLat, correctedLon)
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
    return null
}

四、显示图片和GeoTag

有两种显示方式,你可以选一种:

方式1:用TextView叠加在ImageView上

private fun displayImageWithGeoTag(imagePath: String) {
    val imageView = findViewById<ImageView>(R.id.imageView)
    imageView.setImageBitmap(BitmapFactory.decodeFile(imagePath))

    val geoTagTextView = findViewById<TextView>(R.id.geoTagTextView)
    val geoTag = readGeoTagFromExif(imagePath)
    geoTagTextView.text = if (geoTag != null) {
        "📍 经纬度:${String.format("%.6f", geoTag.first)}, ${String.format("%.6f", geoTag.second)}"
    } else {
        "⚠️ 无GeoTag信息"
    }
}

方式2:直接把GeoTag绘制在图片上

private fun drawGeoTagOnImage(imagePath: String): Bitmap {
    val originalBitmap = BitmapFactory.decodeFile(imagePath)
    val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
    val canvas = Canvas(mutableBitmap)
    val paint = Paint().apply {
        color = Color.WHITE
        textSize = 40f
        strokeWidth = 2f
        style = Paint.Style.FILL_AND_STROKE
        shadowLayer = 5f, 2f, 2f, Color.BLACK // 加阴影让文字更清晰
    }

    val geoTag = readGeoTagFromExif(imagePath)
    val text = geoTag?.let {
        "📍 ${String.format("%.6f", it.first)}, ${String.format("%.6f", it.second)}"
    } ?: "⚠️ 无GeoTag信息"

    // 在图片左下角绘制文字
    canvas.drawText(text, 20f, mutableBitmap.height - 20f, paint)
    return mutableBitmap
}

// 调用时:
imageView.setImageBitmap(drawGeoTagOnImage(imagePath))

内容的提问来源于stack exchange,提问作者Krish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:34:55