info: parse geotiff keys

This commit is contained in:
Thibault Deckers 2022-03-30 13:05:03 +09:00
parent 036fd40d52
commit 85957348b2
5 changed files with 254 additions and 94 deletions

View file

@ -40,12 +40,16 @@ import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeRational
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDateMillis
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDescription
import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeInt
import deckers.thibault.aves.metadata.Metadata.DIR_DNG
import deckers.thibault.aves.metadata.Metadata.DIR_EXIF_GEOTIFF
import deckers.thibault.aves.metadata.Metadata.DIR_PNG_TEXTUAL_DATA
import deckers.thibault.aves.metadata.Metadata.getRotationDegreesForExifCode
import deckers.thibault.aves.metadata.Metadata.isFlippedForExifCode
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_ITXT_DIR_NAME
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_LAST_MODIFICATION_TIME_FORMAT
import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_TIME_DIR_NAME
import deckers.thibault.aves.metadata.MetadataExtractorHelper.containsGeoTiffTags
import deckers.thibault.aves.metadata.MetadataExtractorHelper.extractGeoKeys
import deckers.thibault.aves.metadata.MetadataExtractorHelper.extractPngProfile
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getDateDigitizedMillis
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getDateModifiedMillis
@ -55,7 +59,6 @@ import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeDateMillis
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeInt
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeRational
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeString
import deckers.thibault.aves.metadata.MetadataExtractorHelper.isGeoTiff
import deckers.thibault.aves.metadata.MetadataExtractorHelper.isPngTextDir
import deckers.thibault.aves.metadata.XMP.getSafeDateMillis
import deckers.thibault.aves.metadata.XMP.getSafeInt
@ -83,6 +86,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.text.DecimalFormat
import java.text.ParseException
import kotlin.math.roundToInt
import kotlin.math.roundToLong
@ -95,6 +99,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
"getAllMetadata" -> ioScope.launch { safe(call, result, ::getAllMetadata) }
"getCatalogMetadata" -> ioScope.launch { safe(call, result, ::getCatalogMetadata) }
"getOverlayMetadata" -> ioScope.launch { safe(call, result, ::getOverlayMetadata) }
"getGeoTiffInfo" -> ioScope.launch { safe(call, result, ::getGeoTiffInfo) }
"getMultiPageInfo" -> ioScope.launch { safe(call, result, ::getMultiPageInfo) }
"getPanoramaInfo" -> ioScope.launch { safe(call, result, ::getPanoramaInfo) }
"getIptc" -> ioScope.launch { safe(call, result, ::getIptc) }
@ -168,76 +173,95 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// tags
val tags = dir.tags
if (dir is ExifDirectoryBase) {
when {
dir.isGeoTiff() -> {
// split GeoTIFF tags in their own directory
val geoTiffDirMap = metadataMap["GeoTIFF"] ?: HashMap()
metadataMap["GeoTIFF"] = geoTiffDirMap
val byGeoTiff = tags.groupBy { ExifTags.isGeoTiffTag(it.tagType) }
byGeoTiff[true]?.map { exifTagMapper(it) }?.let { geoTiffDirMap.putAll(it) }
byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
}
mimeType == MimeTypes.DNG -> {
// split DNG tags in their own directory
val dngDirMap = metadataMap["DNG"] ?: HashMap()
metadataMap["DNG"] = dngDirMap
val byDng = tags.groupBy { ExifTags.isDngTag(it.tagType) }
byDng[true]?.map { exifTagMapper(it) }?.let { dngDirMap.putAll(it) }
byDng[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
}
else -> dirMap.putAll(tags.map { exifTagMapper(it) })
}
} else if (dir.isPngTextDir()) {
metadataMap.remove(thisDirName)
dirMap = metadataMap[DIR_PNG_TEXTUAL_DATA] ?: HashMap()
metadataMap[DIR_PNG_TEXTUAL_DATA] = dirMap
for (tag in tags) {
val tagType = tag.tagType
if (tagType == PngDirectory.TAG_TEXTUAL_DATA) {
val pairs = dir.getObject(tagType) as List<*>
val textPairs = pairs.map { pair ->
val kv = pair as KeyValuePair
val key = kv.key
// `PNG-iTXt` uses UTF-8, contrary to `PNG-tEXt` and `PNG-zTXt` using Latin-1 / ISO-8859-1
val charset = if (baseDirName == PNG_ITXT_DIR_NAME) {
@SuppressLint("ObsoleteSdkInt")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
StandardCharsets.UTF_8
} else {
Charset.forName("UTF-8")
}
} else {
kv.value.charset
}
val valueString = String(kv.value.bytes, charset)
val dirs = extractPngProfile(key, valueString)
if (dirs?.any() == true) {
dirs.forEach { profileDir ->
val profileDirName = "${dir.name}/${profileDir.name}"
val profileDirMap = metadataMap[profileDirName] ?: HashMap()
metadataMap[profileDirName] = profileDirMap
val profileTags = profileDir.tags
if (profileDir is ExifDirectoryBase) {
profileDirMap.putAll(profileTags.map { exifTagMapper(it) })
} else {
profileDirMap.putAll(profileTags.map { Pair(it.tagName, it.description) })
when {
dir is ExifDirectoryBase -> {
when {
dir.containsGeoTiffTags() -> {
// split GeoTIFF tags in their own directory
val geoTiffDirMap = metadataMap[DIR_EXIF_GEOTIFF] ?: HashMap()
metadataMap[DIR_EXIF_GEOTIFF] = geoTiffDirMap
val byGeoTiff = tags.groupBy { ExifTags.isGeoTiffTag(it.tagType) }
byGeoTiff[true]?.flatMap { tag ->
when (tag.tagType) {
ExifGeoTiffTags.TAG_GEO_KEY_DIRECTORY -> {
val geoTiffTags = (dir as ExifIFD0Directory).extractGeoKeys(dir.getIntArray(tag.tagType))
geoTiffTags.map { geoTag ->
val name = GeoTiffKeys.getTagName(geoTag.key) ?: "0x${geoTag.key.toString(16)}"
val value = geoTag.value
val description = if (value is DoubleArray) value.joinToString(" ") { doubleFormat.format(it) } else "$value"
Pair(name, description)
}
}
// skip `Geo double/ascii params`, as their content is split and presented through various GeoTIFF keys
ExifGeoTiffTags.TAG_GEO_DOUBLE_PARAMS,
ExifGeoTiffTags.TAG_GEO_ASCII_PARAMS -> ArrayList()
else -> listOf(exifTagMapper(tag))
}
null
} else {
Pair(key, valueString)
}
}?.let { geoTiffDirMap.putAll(it) }
byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
}
dirMap.putAll(textPairs.filterNotNull())
} else {
dirMap[tag.tagName] = tag.description
mimeType == MimeTypes.DNG -> {
// split DNG tags in their own directory
val dngDirMap = metadataMap[DIR_DNG] ?: HashMap()
metadataMap[DIR_DNG] = dngDirMap
val byDng = tags.groupBy { ExifTags.isDngTag(it.tagType) }
byDng[true]?.map { exifTagMapper(it) }?.let { dngDirMap.putAll(it) }
byDng[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) }
}
else -> dirMap.putAll(tags.map { exifTagMapper(it) })
}
}
} else {
dirMap.putAll(tags.map { Pair(it.tagName, it.description) })
dir.isPngTextDir() -> {
metadataMap.remove(thisDirName)
dirMap = metadataMap[DIR_PNG_TEXTUAL_DATA] ?: HashMap()
metadataMap[DIR_PNG_TEXTUAL_DATA] = dirMap
for (tag in tags) {
val tagType = tag.tagType
if (tagType == PngDirectory.TAG_TEXTUAL_DATA) {
val pairs = dir.getObject(tagType) as List<*>
val textPairs = pairs.map { pair ->
val kv = pair as KeyValuePair
val key = kv.key
// `PNG-iTXt` uses UTF-8, contrary to `PNG-tEXt` and `PNG-zTXt` using Latin-1 / ISO-8859-1
val charset = if (baseDirName == PNG_ITXT_DIR_NAME) {
@SuppressLint("ObsoleteSdkInt")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
StandardCharsets.UTF_8
} else {
Charset.forName("UTF-8")
}
} else {
kv.value.charset
}
val valueString = String(kv.value.bytes, charset)
val dirs = extractPngProfile(key, valueString)
if (dirs?.any() == true) {
dirs.forEach { profileDir ->
val profileDirName = "${dir.name}/${profileDir.name}"
val profileDirMap = metadataMap[profileDirName] ?: HashMap()
metadataMap[profileDirName] = profileDirMap
val profileTags = profileDir.tags
if (profileDir is ExifDirectoryBase) {
profileDirMap.putAll(profileTags.map { exifTagMapper(it) })
} else {
profileDirMap.putAll(profileTags.map { Pair(it.tagName, it.description) })
}
}
null
} else {
Pair(key, valueString)
}
}
dirMap.putAll(textPairs.filterNotNull())
} else {
dirMap[tag.tagName] = tag.description
}
}
}
else -> dirMap.putAll(tags.map { Pair(it.tagName, it.description) })
}
if (dir is XmpDirectory) {
try {
for (prop in dir.xmpMeta) {
@ -300,9 +324,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}
@ -557,7 +581,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
MimeTypes.TIFF -> {
// identification of GeoTIFF
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
if (dir.isGeoTiff()) flags = flags or MASK_IS_GEOTIFF
if (dir.containsGeoTiffTags()) flags = flags or MASK_IS_GEOTIFF
}
}
}
@ -570,9 +594,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get catalog metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to get catalog metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}
@ -696,9 +720,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}
@ -722,6 +746,43 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
result.success(metadataMap)
}
private fun getGeoTiffInfo(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getGeoTiffInfo-args", "failed because of missing arguments", null)
return
}
if (canReadWithMetadataExtractor(mimeType)) {
try {
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input)
val fields = HashMap<Int, Any?>()
for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) {
if (dir.containsGeoTiffTags()) {
fields.putAll(dir.tags.map { it.tagType }.filter { ExifTags.isGeoTiffTag(it) }.map {
val value = when (it) {
ExifGeoTiffTags.TAG_GEO_ASCII_PARAMS -> dir.getString(it)
else -> dir.getObject(it)
}
Pair(it, value)
})
}
}
result.success(fields)
return
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}
result.error("getGeoTiffInfo-empty", "failed to get info for mimeType=$mimeType uri=$uri", null)
}
private fun getMultiPageInfo(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
@ -774,12 +835,12 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
return
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to read XMP", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to read XMP", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}
result.error("getPanoramaInfo-empty", "failed to read XMP for mimeType=$mimeType uri=$uri", null)
result.error("getPanoramaInfo-empty", "failed to get info for mimeType=$mimeType uri=$uri", null)
}
private fun getIptc(call: MethodCall, result: MethodChannel.Result) {
@ -961,9 +1022,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}
@ -974,6 +1035,8 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
private val LOG_TAG = LogUtils.createTag<MetadataFetchHandler>()
const val CHANNEL = "deckers.thibault/aves/metadata_fetch"
private val doubleFormat = DecimalFormat("0.###")
private val allMetadataRedundantDirNames = setOf(
"MP4",
"MP4 Metadata",

View file

@ -44,12 +44,12 @@ object ExifTags {
TAG_GDAL_NO_DATA to "GDAL No Data",
).apply {
putAll(DngTags.tagNameMap)
putAll(GeoTiffTags.tagNameMap)
putAll(ExifGeoTiffTags.tagNameMap)
}
fun isDngTag(tag: Int) = DngTags.tags.contains(tag)
fun isGeoTiffTag(tag: Int) = GeoTiffTags.tags.contains(tag)
fun isGeoTiffTag(tag: Int) = ExifGeoTiffTags.tags.contains(tag)
fun getTagName(tag: Int): String? {
return tagNameMap[tag]

View file

@ -1,17 +1,61 @@
package deckers.thibault.aves.metadata
object GeoTiffTags {
object GeoTiffKeys {
// not a standard tag
const val GEOTIFF_VERSION = 0
private const val MODEL_TYPE = 0x0400
private const val RASTER_TYPE = 0x0401
private const val CITATION = 0x0402
private const val GEOG_TYPE = 0x0800
private const val GEOG_CITATION = 0x0801
private const val GEOG_ANGULAR_UNITS = 0x0806
private const val PROJ_CS_TYPE = 0x0c00
private const val PROJ_CS_CITATION = 0x0c01
private const val PROJECTION = 0x0c02
private const val PROJ_COORD_TRANS = 0x0c03
private const val PROJ_LINEAR_UNITS = 0x0c04
private const val PROJ_STD_PARALLEL_1 = 0x0c06
private const val PROJ_NAT_ORIGIN_LONG = 0x0c08
private const val PROJ_FALSE_EASTING = 0x0c0a
private const val PROJ_FALSE_NORTHING = 0x0c0b
private val tagNameMap = hashMapOf(
GEOTIFF_VERSION to "GeoTIFF Version",
MODEL_TYPE to "Model Type",
RASTER_TYPE to "Raster Type",
CITATION to "Citation",
GEOG_TYPE to "Geographic Type",
GEOG_CITATION to "Geographic Citation",
GEOG_ANGULAR_UNITS to "Geographic Angular Units",
PROJ_CS_TYPE to "Projected Coordinate System Type",
PROJ_CS_CITATION to "Projected Coordinate System Citation",
PROJECTION to "Projection",
PROJ_COORD_TRANS to "Projected Coordinate Transform",
PROJ_LINEAR_UNITS to "Projection Linear Units",
PROJ_STD_PARALLEL_1 to "Projection Standard Parallel 1",
PROJ_NAT_ORIGIN_LONG to "Projection Natural Origin Longitude",
PROJ_FALSE_EASTING to "Projection False Easting",
PROJ_FALSE_NORTHING to "Projection False Northing",
)
fun getTagName(tag: Int): String? {
return tagNameMap[tag]
}
}
object ExifGeoTiffTags {
// ModelPixelScaleTag (optional)
// Tag = 33550 (830E.H)
// Type = DOUBLE
// Count = 3
const val TAG_MODEL_PIXEL_SCALE = 0x830e
// ModelTiepointTag (conditional)
// ModelTiePointTag (conditional)
// Tag = 33922 (8482.H)
// Type = DOUBLE
// Count = 6*K, K = number of tiepoints
const val TAG_MODEL_TIEPOINT = 0x8482
// Count = 6*K, K = number of tie points
const val TAG_MODEL_TIE_POINT = 0x8482
// ModelTransformationTag (conditional)
// Tag = 34264 (85D8.H)
@ -29,22 +73,22 @@ object GeoTiffTags {
// Tag = 34736 (87BO.H)
// Type = DOUBLE
// Count = variable
private const val TAG_GEO_DOUBLE_PARAMS = 0x87b0
const val TAG_GEO_DOUBLE_PARAMS = 0x87b0
// GeoAsciiParamsTag (optional)
// Tag = 34737 (87B1.H)
// Type = ASCII
// Count = variable
private const val TAG_GEO_ASCII_PARAMS = 0x87b1
const val TAG_GEO_ASCII_PARAMS = 0x87b1
val tagNameMap = hashMapOf(
TAG_GEO_ASCII_PARAMS to "Geo Ascii Params",
TAG_GEO_DOUBLE_PARAMS to "Geo Double Params",
TAG_GEO_KEY_DIRECTORY to "Geo Key Directory",
TAG_MODEL_PIXEL_SCALE to "Model Pixel Scale",
TAG_MODEL_TIEPOINT to "Model Tiepoint",
TAG_MODEL_TIE_POINT to "Model Tie Points",
TAG_MODEL_TRANSFORMATION to "Model Transformation",
)
val tags = tagNameMap.keys
}
}

View file

@ -29,6 +29,8 @@ object Metadata {
const val DIR_XMP = "XMP" // from metadata-extractor
const val DIR_MEDIA = "Media" // custom
const val DIR_COVER_ART = "Cover" // custom
const val DIR_DNG = "DNG" // custom
const val DIR_EXIF_GEOTIFF = "GeoTIFF" // custom
const val DIR_PNG_TEXTUAL_DATA = "PNG Textual Data" // custom
// types of metadata

View file

@ -5,6 +5,7 @@ import com.drew.lang.ByteArrayReader
import com.drew.lang.Rational
import com.drew.lang.SequentialByteArrayReader
import com.drew.metadata.Directory
import com.drew.metadata.StringValue
import com.drew.metadata.exif.ExifDirectoryBase
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.exif.ExifReader
@ -93,19 +94,69 @@ object MetadataExtractorHelper {
- If the ModelTransformationTag is included in an IFD, then a ModelPixelScaleTag SHALL NOT be included
- If the ModelPixelScaleTag is included in an IFD, then a ModelTiepointTag SHALL also be included.
*/
fun ExifDirectoryBase.isGeoTiff(): Boolean {
if (!this.containsTag(GeoTiffTags.TAG_GEO_KEY_DIRECTORY)) return false
fun ExifDirectoryBase.containsGeoTiffTags(): Boolean {
if (!this.containsTag(ExifGeoTiffTags.TAG_GEO_KEY_DIRECTORY)) return false
val modelTiepoint = this.containsTag(GeoTiffTags.TAG_MODEL_TIEPOINT)
val modelTransformation = this.containsTag(GeoTiffTags.TAG_MODEL_TRANSFORMATION)
if (!modelTiepoint && !modelTransformation) return false
val modelTiePoints = this.containsTag(ExifGeoTiffTags.TAG_MODEL_TIE_POINT)
val modelTransformation = this.containsTag(ExifGeoTiffTags.TAG_MODEL_TRANSFORMATION)
if (!modelTiePoints && !modelTransformation) return false
val modelPixelScale = this.containsTag(GeoTiffTags.TAG_MODEL_PIXEL_SCALE)
if ((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiepoint)) return false
val modelPixelScale = this.containsTag(ExifGeoTiffTags.TAG_MODEL_PIXEL_SCALE)
if ((modelTransformation && modelPixelScale) || (modelPixelScale && !modelTiePoints)) return false
return true
}
// TODO TLAD use `GeoTiffDirectory` from the Java version of `metadata-extractor` when available
// adapted from https://github.com/drewnoakes/metadata-extractor-dotnet/blob/master/MetadataExtractor/Formats/Exif/ExifTiffHandler.cs
fun ExifIFD0Directory.extractGeoKeys(geoKeys: IntArray): HashMap<Int, Any?> {
val fields = HashMap<Int, Any?>()
if (geoKeys.size < 4) return fields
var i = 0
val directoryVersion = geoKeys[i++]
val revision = geoKeys[i++]
val minorRevision = geoKeys[i++]
val numberOfKeys = geoKeys[i++]
fields[GeoTiffKeys.GEOTIFF_VERSION] = "$directoryVersion.$revision.$minorRevision"
for (j in 0 until numberOfKeys) {
val keyId = geoKeys[i++]
val tiffTagLocation = geoKeys[i++]
val valueCount = geoKeys[i++]
val valueOffset = geoKeys[i++]
try {
if (tiffTagLocation == 0) {
fields[keyId] = valueOffset
} else {
val sourceValue = getObject(tiffTagLocation)
if (sourceValue is StringValue) {
if (valueOffset + valueCount <= sourceValue.bytes.size) {
fields[keyId] = String(sourceValue.bytes, valueOffset, valueCount).trimEnd('|')
} else {
Log.w(LOG_TAG, "GeoTIFF key $keyId with offset $valueOffset and count $valueCount extends beyond length of source value (${sourceValue.bytes.size})")
}
} else if (sourceValue.javaClass.isArray) {
val sourceArray = sourceValue as DoubleArray
if (valueOffset + valueCount < sourceArray.size) {
fields[keyId] = sourceArray.copyOfRange(valueOffset, valueOffset + valueCount)
} else {
Log.w(LOG_TAG, "GeoTIFF key $keyId with offset $valueOffset and count $valueCount extends beyond length of source value (${sourceArray.size})")
}
} else {
Log.w(LOG_TAG, "GeoTIFF key $keyId references tag $tiffTagLocation which has unsupported type of ${sourceValue?.javaClass}")
}
}
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to extract GeoTiff fields from keys", e)
}
}
return fields
}
// PNG
fun Directory.isPngTextDir(): Boolean = this is PngDirectory && setOf(PNG_ITXT_DIR_NAME, PNG_TEXT_DIR_NAME, PNG_ZTXT_DIR_NAME).contains(this.name)