ExifTags Module¶
Note
The source images loaded from the demos in this page is in the real-world, but I can’t redistribute those. I downloaded those to local by google searching with words “Images for jpeg contained gpsinfo sample”, so most of all of those are not free liscenced.
Keeping exif data when saving¶
- doc
https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html#jpeg, http://effbot.org/imagingbook/format-jpeg.htm
Before you use ExifTags, note that save method doesn’t preserve the exif in case of default.
>>> from io import BytesIO
>>> from PIL import Image
>>>
>>> src = Image.open("01.jpg")
>>> src.info.get('exif', b'')[:20]
'Exif\x00\x00II*\x00\x08\x00\x00\x00\x0c\x00\x0f\x01\x02\x00'
>>>
>>> #
>>> tmp = BytesIO()
>>> src.save(tmp, "JPEG") # drop exif data
>>> dst = Image.open(tmp)
>>> dst.info.get('exif', b'')[:20]
''
>>>
>>> #
>>> tmp = BytesIO()
>>> src.save(tmp, "JPEG", exif=b"") # override exif data
>>> dst = Image.open(tmp)
>>> dst.info.get('exif', b'')[:20]
''
>>>
>>> #
>>> tmp = BytesIO()
>>> src.save(tmp, "JPEG", exif=src.info["exif"]) # keep exif data
>>> dst = Image.open(tmp)
>>> dst.info.get('exif', b'')[:20]
'Exif\x00\x00II*\x00\x08\x00\x00\x00\x0c\x00\x0f\x01\x02\x00'
>>>
Exploring Exif Items As Human Readable¶
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import PIL
>>> PIL.VERSION, PIL.PILLOW_VERSION
('1.1.7', '4.1.1')
>>>
>>> from PIL import Image
>>> from PIL.ExifTags import TAGS, GPSTAGS
>>>
>>> # build reverse dicts
>>> _TAGS_r = dict(((v, k) for k, v in TAGS.items()))
>>> _GPSTAGS_r = dict(((v, k) for k, v in GPSTAGS.items()))
>>>
>>> #
>>> img = Image.open("01.jpg")
>>> img.info.keys()
dict_keys(['dpi', 'exif'])
>>> len(img.info['exif'])
13798
>>> img.info['exif'][:30] # raw exif data
b'Exif\x00\x00II*\x00\x08\x00\x00\x00\x0c\x00\x0f\x01\x02\x00\x06\x00\x00\x00\x9e\x00\x00\x00\x10\x01'
>>> #
>>> exifd = img._getexif() # this merges gpsinfo as data rather than an offset pointer
>>> type(exifd)
<class 'dict'>
>>> exifd.keys() # numeric keys
dict_keys([36864, 37377, 37378, 36867, 36868, 40965, 37510, 37383, 37385, 37386, 40962, 41486, 271, 272, 37521, 274, 531, 33432, 37380, 282, 283, 33434, 42032, 34850, 40961, 34853, 34855, 296, 41987, 37121, 34866, 33437, 34864, 42033, 306, 42036, 42037, 42034, 315, 41985, 40960, 41990, 41487, 40963, 37520, 41986, 34665, 37522, 41488, 37500])
>>> #
>>> # 1. ignore "MakerNote" and "UserComment" because these can be too long
>>> # 2. ignore unknwon tag
>>> keys = list(exifd.keys())
>>> keys.remove(_TAGS_r["MakerNote"])
>>> keys.remove(_TAGS_r["UserComment"])
>>> keys = [k for k in keys if k in TAGS]
>>> # symbolic name of keys
>>> print("\n".join([TAGS[k] for k in keys]))
ExifVersion
ShutterSpeedValue
--- snip ---
ColorSpace
GPSInfo
ISOSpeedRatings
--- snip ---
FocalPlaneResolutionUnit
>>> # each values
>>> print("\n".join([str((TAGS[k], exifd[k])) for k in keys]))
('ExifVersion', b'0230')
('ShutterSpeedValue', (417792, 65536))
--- snip ---
('ColorSpace', 1)
('GPSInfo', {0: b'\x02\x03\x00\x00', 1: 'S', 2: ((20, 1), (150146, 10000), (0, 1)), 3: 'E', 4: ((44, 1), (251558, 10000), (0, 1)), 5: b'\x00', 6: (235, 10), 7: ((14, 1), (31, 1), (59000, 1000)), 8: '12', 9: 'A', 10: '3', 11: (14, 10), 29: '2012:08:13'})
('ISOSpeedRatings', 100)
--- snip ---
>>> gpsinfo = exifd[_TAGS_r["GPSInfo"]]
>>> gpsinfo
{0: b'\x02\x03\x00\x00', 1: 'S', 2: ((20, 1), (150146, 10000), (0, 1)), 3: 'E', 4: ((44, 1), (251558, 10000), (0, 1)), 5: b'\x00', 6: (235, 10), 7: ((14, 1), (31, 1), (59000, 1000)), 8: '12', 9: 'A', 10: '3', 11: (14, 10), 29: '2012:08:13'}
>>> print("\n".join([GPSTAGS[k] for k in gpsinfo.keys()]))
GPSVersionID
GPSLatitudeRef
GPSLatitude
GPSLongitudeRef
GPSLongitude
GPSAltitudeRef
GPSAltitude
GPSTimeStamp
GPSSatellites
GPSStatus
GPSMeasureMode
GPSDOP
GPSDateStamp
>>> print("\n".join([str((GPSTAGS[k], gpsinfo[k])) for k in gpsinfo.keys()]))
('GPSVersionID', b'\x02\x03\x00\x00')
('GPSLatitudeRef', 'S')
('GPSLatitude', ((20, 1), (150146, 10000), (0, 1)))
('GPSLongitudeRef', 'E')
('GPSLongitude', ((44, 1), (251558, 10000), (0, 1)))
('GPSAltitudeRef', b'\x00')
('GPSAltitude', (235, 10))
('GPSTimeStamp', ((14, 1), (31, 1), (59000, 1000)))
('GPSSatellites', '12')
('GPSStatus', 'A')
('GPSMeasureMode', '3')
('GPSDOP', (14, 10))
('GPSDateStamp', '2012:08:13')
Python 3.9.2 (tags/v3.9.2:1a79785, Feb 19 2021, 13:44:55) [MSC v.1928 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import PIL
>>> PIL.__version__
'8.2.0'
>>> from PIL import Image
>>> from PIL.ExifTags import TAGS, GPSTAGS
>>>
>>> # build reverse dicts
>>> _TAGS_r = dict(((v, k) for k, v in TAGS.items()))
>>> _GPSTAGS_r = dict(((v, k) for k, v in GPSTAGS.items()))
>>>
>>> #
>>> img = Image.open("02.jpg")
>>> img.info.keys()
dict_keys(['jfif', 'jfif_version', 'dpi', 'jfif_unit', 'jfif_density', 'exif', 'photoshop', 'adobe', 'adobe_transform', 'progressive', 'progression', 'icc_profile'])
>>> len(img.info['exif'])
4997
>>> img.info['exif'][:30] # raw exif data
b'Exif\x00\x00II*\x00\x08\x00\x00\x00\x11\x00\x00\x01\x03\x00\x01\x00\x00\x00 \x0f\x00\x00\x01\x01'
>>> #
>>> exifd = img.getexif()._get_merged_dict() # this merges gpsinfo as data rather than an offset pointer
>>> type(exifd)
<class 'dict'>
>>> exifd.keys() # numeric keys
dict_keys([256, 257, 258, 259, 34853, 262, 296, 34665, 271, 272, 305, 274, 306, 277, 282, 283, 284, 36864, 37377, 37378, 36867, 36868, 37380, 37381, 37383, 37384, 37385, 37386, 40961, 40962, 41992, 41993, 41994, 40963, 41996, 41495, 41728, 33434, 33437, 41729, 42016, 34850, 34855, 41986, 41987])
>>> keys = list(exifd.keys())
>>> keys = [k for k in keys if k in TAGS]
>>> # symbolic name of keys
>>> print("\n".join([TAGS[k] for k in keys]))
ImageWidth
ImageLength
BitsPerSample
Compression
GPSInfo
PhotometricInterpretation
--- snip ---
WhiteBalance
>>> # each values
>>> print("\n".join([str((TAGS[k], exifd[k])) for k in keys]))
('ImageWidth', 3872)
('ImageLength', 2592)
('BitsPerSample', (8, 8, 8))
('Compression', 1)
('GPSInfo', {0: b'\x00\x00\x02\x02', 1: 'N', 2: (53.0, 20.0, 58.86), 3: 'W', 4: (6.0, 15.0, 36.29), 5: b'\x00', 6: 0.0})
('PhotometricInterpretation', 2)
--- snip ---
('WhiteBalance', 1)
>>> gpsinfo = exifd[_TAGS_r["GPSInfo"]]
>>> gpsinfo
{0: b'\x00\x00\x02\x02', 1: 'N', 2: (53.0, 20.0, 58.86), 3: 'W', 4: (6.0, 15.0, 36.29), 5: b'\x00', 6: 0.0}
>>> print("\n".join([GPSTAGS[k] for k in gpsinfo.keys()]))
GPSVersionID
GPSLatitudeRef
GPSLatitude
GPSLongitudeRef
GPSLongitude
GPSAltitudeRef
GPSAltitude
>>> print("\n".join([str((GPSTAGS[k], gpsinfo[k])) for k in gpsinfo.keys()]))
('GPSVersionID', b'\x00\x00\x02\x02')
('GPSLatitudeRef', 'N')
('GPSLatitude', (53.0, 20.0, 58.86))
('GPSLongitudeRef', 'W')
('GPSLongitude', (6.0, 15.0, 36.29))
('GPSAltitudeRef', b'\x00')
('GPSAltitude', 0.0)
Building the exif tags by hand, and saving it¶
It is too heavy to build exif data by hand with only pillow, because exif data is a binary (of course!), its specification might be larger than you think, and pillow’s supports for it are not rich enough.
Actually, the Exif tag structure is borrowed from TIFF files,
so you might think TiffImagePlugin.ImageFileDirectory*
are useful, but those are insufficient for Exif (in JPEG).
Several reasons:
TiffImagePlugin.ImageFileDirectory*
doesn’t know about value type information for non-TIFF tags.TiffImagePlugin.ImageFileDirectory*
doesn’t know about a whole Exif structure in JPEG.TiffImagePlugin.ImageFileDirectory*
doesn’t know about substructure needed by GPSInfo.
If your needs are very simple, indeed you can:
>>> from io import BytesIO
>>> from PIL import Image, TiffImagePlugin, TiffTags
>>> from PIL.ExifTags import TAGS
>>> from PIL.TiffImagePlugin import ImageFileDirectory_v2
>>>
>>> _TAGS_r = dict(((v, k) for k, v in TAGS.items()))
>>>
>>> #
>>> jpgimg1 = Image.new("RGB", (64, 64))
>>>
>>> # Image File Directory
>>> ifd = ImageFileDirectory_v2()
>>>
>>> # TiffTags knows "Artist" (0x013b)
>>> TiffTags.lookup(_TAGS_r["Artist"])
TagInfo(value=315, name='Artist', type=2, length=1, enum={})
>>> ifd[_TAGS_r["Artist"]] = u'somebody'
>>> #ifd.tagtype[_TAGS_r['Artist']] = 2 # string, but you don't have to set explicitly.
>>>
>>> # TiffTags doesn't know "LightSource" (0x9208)
>>> TiffTags.lookup(_TAGS_r["LightSource"])
TagInfo(value=37384, name='LightSource', type=None, length=0, enum={})
>>> ifd[_TAGS_r['LightSource']] = 1 # DayLight
>>> ifd.tagtype[_TAGS_r['LightSource']] = 3 # short, you must set.
>>>
>>> ##
>>> out = BytesIO()
>>> ifd.save(out)
48
>>> ## you must add magic number of exif structure
>>> exif = b"Exif\x00\x00" + out.getvalue()
>>>
>>> jpgimg1.save("out.jpg", exif=exif)
>>> jpgimg2 = Image.open("out.jpg")
>>> #
>>> jpgimg2._getexif()
{37384: 1, 315: 'somebody'}
But your needs should be more complex, and your code will lose maintainability soon.
You can use other third party library, for example piexif:
>>> from PIL import Image
>>> import piexif
>>> zeroth_ifd = {
... piexif.ImageIFD.Artist: u"someone",
... piexif.ImageIFD.XResolution: (96, 1),
... piexif.ImageIFD.YResolution: (96, 1),
... piexif.ImageIFD.Software: u"piexif"
... }
>>> exif_ifd = {
... piexif.ExifIFD.DateTimeOriginal: u"2099:09:29 10:10:10",
... piexif.ExifIFD.LensMake: u"LensMake",
... piexif.ExifIFD.Sharpness: 65535,
... piexif.ExifIFD.LensSpecification: ((1, 1), (1, 1), (1, 1), (1, 1)),
... }
>>> exif_dict = {"0th": zeroth_ifd, "Exif": exif_ifd}
>>> exif_bytes = piexif.dump(exif_dict)
>>> jpgimg1 = Image.new("RGB", (64, 64))
>>> jpgimg1.save("out.jpg", exif=exif_bytes)
>>> jpgimg2 = Image.open("out.jpg")
>>> #
>>> jpgimg2._getexif()
{36867: '2099:09:29 10:10:10', 315: 'someone', 34665: 105, 41994: 65535, 305: 'piexif', 42034: ((1, 1), (1, 1), (1, 1), (1, 1)), 42035: 'LensMake', 282: (96, 1), 283: (96, 1)}
Accessing to GeoTIFF tags by Pillow¶
A typical library that can handle GeoTIFF is related to GDAL, but GDAL tends to be difficult to introduce due to its many dependencies, and some people say “I don’t want to think about using it if possible”. I’m not the only one thinking about this, but noGDAL is probably the forefront.
Depending on how much GeoTIFF functionality you use for image processing, Pillow alone is quite possible if you just want to decipher GeoTIFF-specific tags:
# -*- coding: utf-8 -*-
import struct
from PIL import Image
_tags = {
# https://www.awaresystems.be/imaging/tiff/tifftags/modelpixelscaletag.html
33550: "ModelPixelScale", # (ScaleX, ScaleY, ScaleZ)
# https://www.awaresystems.be/imaging/tiff/tifftags/modeltiepointtag.html
33922: "ModelTiepoint", # (...,I,J,K, X,Y,Z...)
# https://www.awaresystems.be/imaging/tiff/tifftags/modeltransformationtag.html
34264: "ModelTransformation", # double*16
# https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html
34735: "GeoKeyDirectory",
# https://www.awaresystems.be/imaging/tiff/tifftags/geodoubleparamstag.html
34736: "GeoDoubleParams",
# https://www.awaresystems.be/imaging/tiff/tifftags/geoasciiparamstag.html
34737: "GeoAsciiParams",
}
_keys = {
1024: 'GTModelTypeGeoKey',
1025: 'GTRasterTypeGeoKey',
1026: 'GTCitationGeoKey',
2048: 'GeographicTypeGeoKey',
2049: 'GeogCitationGeoKey',
2050: 'GeogGeodeticDatumGeoKey',
2051: 'GeogPrimeMeridianGeoKey',
2052: 'GeogLinearUnitsGeoKey',
2053: 'GeogLinearUnitSizeGeoKey',
2054: 'GeogAngularUnitsGeoKey',
2055: 'GeogAngularUnitsSizeGeoKey',
2056: 'GeogEllipsoidGeoKey',
2057: 'GeogSemiMajorAxisGeoKey',
2058: 'GeogSemiMinorAxisGeoKey',
2059: 'GeogInvFlatteningGeoKey',
2060: 'GeogAzimuthUnitsGeoKey',
2061: 'GeogPrimeMeridianLongGeoKey',
2062: 'GeogTOWGS84GeoKey',
3059: 'ProjLinearUnitsInterpCorrectGeoKey', # GDAL
3072: 'ProjectedCSTypeGeoKey',
3073: 'PCSCitationGeoKey',
3074: 'ProjectionGeoKey',
3075: 'ProjCoordTransGeoKey',
3076: 'ProjLinearUnitsGeoKey',
3077: 'ProjLinearUnitSizeGeoKey',
3078: 'ProjStdParallel1GeoKey',
3079: 'ProjStdParallel2GeoKey',
3080: 'ProjNatOriginLongGeoKey',
3081: 'ProjNatOriginLatGeoKey',
3082: 'ProjFalseEastingGeoKey',
3083: 'ProjFalseNorthingGeoKey',
3084: 'ProjFalseOriginLongGeoKey',
3085: 'ProjFalseOriginLatGeoKey',
3086: 'ProjFalseOriginEastingGeoKey',
3087: 'ProjFalseOriginNorthingGeoKey',
3088: 'ProjCenterLongGeoKey',
3089: 'ProjCenterLatGeoKey',
3090: 'ProjCenterEastingGeoKey',
3091: 'ProjFalseOriginNorthingGeoKey',
3092: 'ProjScaleAtNatOriginGeoKey',
3093: 'ProjScaleAtCenterGeoKey',
3094: 'ProjAzimuthAngleGeoKey',
3095: 'ProjStraightVertPoleLongGeoKey',
3096: 'ProjRectifiedGridAngleGeoKey',
4096: 'VerticalCSTypeGeoKey',
4097: 'VerticalCitationGeoKey',
4098: 'VerticalDatumGeoKey',
4099: 'VerticalUnitsGeoKey',
}
def getgeotiffdata(img): # FIXME: this can't handle correctly if its endian is big-endian.
result = {}
if not hasattr(img, "tag"):
img = Image.open(img)
tagdata = img.tag.tagdata
# ModelPixelScale
if 33550 in tagdata:
result[_tags[33550]] = struct.unpack(
"<3d", tagdata[33550])
# ModelTiepoint
if 33922 in tagdata:
result[_tags[33922]] = struct.unpack(
"<{}d".format(len(tagdata[33922]) // 8), tagdata[33922])
# ModelTransformation
if 34264 in tagdata:
result[_tags[34264]] = struct.unpack(
"<{}d".format(len(tagdata[34264]) // 8), tagdata[34264])
# GeoKeyDirectory
# GeoDoubleParams
# GeoAsciiParams
if 34735 in tagdata:
inner = result[_tags[34735]] = [{}, {}]
#
gkd = struct.unpack("<{}H".format(len(tagdata[34735]) // 2), tagdata[34735])
gkd = [gkd[i:i + 4] for i in range(0, len(gkd), 4)]
KeyDirectoryVersion, KeyRevision, KeyRevisionMinor = gkd.pop(0)[:-1]
inner[0]["KeyDirectoryVersion"] = KeyDirectoryVersion
inner[0]["KeyRevision"] = KeyRevision
inner[0]["KeyRevisionMinor"] = KeyRevisionMinor
#
for keyid, tagid, count, offset in gkd:
if tagid == 0:
value = offset
else:
if tagid == 34736:
value = tagdata[tagid]
value = struct.unpack("<{}d".format(len(value) // 8), value)
elif tagid == 34737:
value = tagdata[tagid][offset:offset + count]
value = value.decode()
if value[-1] == "|":
value = value[:-1]
else:
raise NotImplementedError("sorry")
inner[1][_keys.get(keyid, keyid)] = value
return result, img.size
if __name__ == '__main__':
import sys
import json
geotiffdata, (width, height) = getgeotiffdata(sys.argv[1])
print(json.dumps(geotiffdata, indent=2))
[me@host: ~]$ python getgeotifftags.py data/manhattan.tif
{
"ModelTiepoint": [
0.0,
0.0,
0.0,
583057.357,
4516255.36,
0.0
],
"ModelPixelScale": [
0.999948245999997,
0.999948245999997,
0.0
],
"GeoKeyDirectory": [
{
"KeyRevision": 1,
"KeyDirectoryVersion": 1,
"KeyRevisionMinor": 0
},
{
"PCSCitationGeoKey": "IMAGINE GeoTIFF Support\nCopyright 1991 - 2005 by Leica Geosystems Geospatial Imaging, LLC. All Rights Reserved\n@(#)$RCSfile: egtf.c $ IMAGINE 9.0 $Revision: 10.0 $ $Date: 2005/07/26 15:10:00 EST $\nUTM Zone 18N\nEllipsoid = GRS 1980\nDatum = NAD83",
"ProjLinearUnitsGeoKey": 9001,
"ProjectedCSTypeGeoKey": 26918,
"GTModelTypeGeoKey": 1,
"GTRasterTypeGeoKey": 1,
"GTCitationGeoKey": "IMAGINE GeoTIFF Support\nCopyright 1991 - 2005 by Leica Geosystems Geospatial Imaging, LLC. All Rights Reserved\n@(#)$RCSfile: egtf.c $ IMAGINE 9.0 $Revision: 10.0 $ $Date: 2005/07/26 15:10:00 EST $\nProjection Name = UTM\nUnits = meters\nGeoTIFF Units = meters"
}
]
}
[me@host: ~]$ python getgeotifftags.py data/landsat.tif
{
"ModelTiepoint": [
0.0,
0.0,
0.0,
-13051837.419021819,
6193028.747207512,
0.0
],
"ModelPixelScale": [
45.19374321600039,
45.14032535950664,
0.0
],
"GeoKeyDirectory": [
{
"KeyRevision": 1,
"KeyDirectoryVersion": 1,
"KeyRevisionMinor": 0
},
{
"ProjLinearUnitsGeoKey": 9001,
"ProjectedCSTypeGeoKey": 3857,
"GTModelTypeGeoKey": 1,
"GeogAngularUnitsGeoKey": 9102,
"GeogCitationGeoKey": "WGS 84",
"GTRasterTypeGeoKey": 1,
"GTCitationGeoKey": "WGS 84 / Pseudo-Mercator"
}
]
}
[me@host: ~]$ python getgeotifftags.py NE1_LR_LC/NE1_LR_LC.tif
{
"ModelTiepoint": [
0.0,
0.0,
0.0,
-180.0,
90.0,
0.0
],
"ModelPixelScale": [
0.02222222222222,
0.02222222222222,
0.0
],
"GeoKeyDirectory": [
{
"KeyRevision": 1,
"KeyDirectoryVersion": 1,
"KeyRevisionMinor": 0
},
{
"GTModelTypeGeoKey": 2,
"GeogAngularUnitsGeoKey": 9102,
"GeogInvFlatteningGeoKey": [
298.257223563,
6378137.0
],
"GeogCitationGeoKey": "WGS 84",
"GTRasterTypeGeoKey": 1,
"GeographicTypeGeoKey": 4326,
"GeogSemiMajorAxisGeoKey": [
298.257223563,
6378137.0
]
}
]
}
[me@host: ~]$ python getgeotifftags.py FAA_UTM18N_NAD83.tif
{
"ModelTiepoint": [
0.5,
0.5,
0.0,
223598.2000551123,
4217895.013917857,
0.0
],
"ModelPixelScale": [
23.927070933333344,
23.927070933333344,
0.0
],
"GeoKeyDirectory": [
{
"KeyRevision": 1,
"KeyDirectoryVersion": 1,
"KeyRevisionMinor": 0
},
{
"ProjLinearUnitsGeoKey": 9001,
"GTCitationGeoKey": "#MAP_PROJECTION\n\"NAD83 / UTM zone 18N\"\nNAD83,6378137,0.0818191910428158,0\n\"Transverse Mercator\",0,-75,0.9996,500000,0\n#UNITS_LENGTH\nm,1\n#MAP_DATUM_TRANSFORM\n\"NAD83 to WGS 84 (1)\",0,0,0,0,0,0,0\n",
"ProjectedCSTypeGeoKey": 26918,
"GTRasterTypeGeoKey": 1,
"GTModelTypeGeoKey": 1
}
]
}
You may also want to consider tifffile. If you use it, you can like this:
# -*- coding: utf-8 -*-
import tifffile
def getgeotiffdata(img):
return tifffile.TiffFile(img).pages[0].geotiff_tags
if __name__ == '__main__':
import sys
import json
print(json.dumps(getgeotiffdata(sys.argv[1]), indent=2))
[me@host: ~]$ python getgeotifftags2.py NE1_LR_LC/NE1_LR_LC.tif
{
"KeyDirectoryVersion": 1,
"KeyRevision": 1,
"KeyRevisionMinor": 0,
"GTModelTypeGeoKey": 2,
"GTRasterTypeGeoKey": 1,
"GeographicTypeGeoKey": 4326,
"GeogCitationGeoKey": "WGS 84",
"GeogAngularUnitsGeoKey": 9102,
"GeogSemiMajorAxisGeoKey": 6378137.0,
"GeogInvFlatteningGeoKey": 298.257223563,
"ModelPixelScale": [
0.02222222222222,
0.02222222222222,
0.0
],
"ModelTiepoint": [
0.0,
0.0,
0.0,
-180.0,
90.0,
0.0
]
}
You can get “modeltransformation” from “modelpixelscale”, and “modeltiepoint”:
# -*- coding: utf-8 -*-
#
# ...(snip)...
#
def gettransforms(geotiffdata):
"""
build modeltransformation from modelpixelscale, and modeltiepoint
"""
transforms = []
if "ModelPixelScale" in geotiffdata and "ModelTiepoint" in geotiffdata:
# see "B.6. GeoTIFF Tags for Coordinate Transformations"
# in https://earthdata.nasa.gov/files/19-008r4.pdf.
sx, sy, sz = geotiffdata["ModelPixelScale"]
tiepoints = geotiffdata["ModelTiepoint"]
for tp in range(0, len(tiepoints), 6):
i, j, k, x, y, z = tiepoints[tp:tp + 6]
transforms.append([
[sx, 0.0, 0.0, x - i * sx],
[0.0, -sy, 0.0, y + j * sy],
[0.0, 0.0, sz, z - k * sz],
[0.0, 0.0, 0.0, 1.0]])
return transforms
if __name__ == '__main__':
import sys
import json
import numpy as np
geotiffdata, (width, height) = getgeotiffdata(sys.argv[1])
print(json.dumps(geotiffdata, indent=2))
transforms = gettransforms(geotiffdata)
print(np.dot(transforms, [0, 0, 0, 1])[0,:2])
print(np.dot(transforms, [width, height, 0, 1])[0,:2])
[me@host: ~]$ python getgeotifftags.py gm-jpn-ve_u_1_1/jpn/ve.tif
{
"ModelTiepoint": [
0.0,
0.0,
0.0,
120.00833129882812,
49.99166809767485,
0.0
],
"ModelPixelScale": [
0.008333333767950535,
0.008333333767950535,
0.0
],
"GeoKeyDirectory": [
{
"KeyRevision": 1,
"KeyDirectoryVersion": 1,
"KeyRevisionMinor": 0
},
{
"GTRasterTypeGeoKey": 2
}
]
}
[120.0083313 49.9916681]
[155.00833312 19.99166653]