Skip to content

Commit d7a6d41

Browse files
authored
[TIKA-4781] Map EXIF GPS altitude to the existing geo:alt property (#2941)
The GeotagHandler mapped latitude, longitude and the GPS timestamp from the EXIF GpsDirectory, but not the altitude, although tika-core defines Geographic.ALTITUDE and metadata-extractor exposes the value in the same directory. Consumers only got the raw description string ("GPS:GPS Altitude" = "227.4 metres"). Set geo:alt as a signed value honoring GPSAltitudeRef (1 = below sea level = negative), formatted with the same DecimalFormat as latitude and longitude. Analogous to TIKA-4425, which added the normalized GPS timestamp from the same directory; see TIKA-2861 for the corresponding ISO 6709 altitude handling in MP4/QuickTime. The existing IMG_1034.heic fixture already carries an altitude, so HeifParserTest now asserts it end to end; handler-level tests cover the above and below sea level cases.
1 parent 6f2aa85 commit d7a6d41

3 files changed

Lines changed: 38 additions & 2 deletions

File tree

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,10 +684,10 @@ public boolean supports(Class<? extends Directory> directoryType) {
684684
}
685685

686686
public void handle(Directory directory, Metadata metadata) throws MetadataException {
687+
DecimalFormat geoDecimalFormat = new DecimalFormat(GEO_DECIMAL_FORMAT_STRING,
688+
new DecimalFormatSymbols(Locale.ENGLISH));
687689
GeoLocation geoLocation = ((GpsDirectory) directory).getGeoLocation();
688690
if (geoLocation != null) {
689-
DecimalFormat geoDecimalFormat = new DecimalFormat(GEO_DECIMAL_FORMAT_STRING,
690-
new DecimalFormatSymbols(Locale.ENGLISH));
691691
metadata.set(TikaCoreProperties.LATITUDE,
692692
geoDecimalFormat.format(geoLocation.getLatitude()));
693693
metadata.set(TikaCoreProperties.LONGITUDE,
@@ -697,6 +697,17 @@ public void handle(Directory directory, Metadata metadata) throws MetadataExcept
697697
if (gpsDate != null) {
698698
metadata.set(Geographic.TIMESTAMP, gpsDate);
699699
}
700+
Rational altitude = ((GpsDirectory) directory).getRational(GpsDirectory.TAG_ALTITUDE);
701+
if (altitude != null) {
702+
double metres = altitude.doubleValue();
703+
//GPSAltitudeRef 1 means below sea level
704+
Integer altitudeRef =
705+
((GpsDirectory) directory).getInteger(GpsDirectory.TAG_ALTITUDE_REF);
706+
if (altitudeRef != null && altitudeRef == 1) {
707+
metres = -metres;
708+
}
709+
metadata.set(TikaCoreProperties.ALTITUDE, geoDecimalFormat.format(metres));
710+
}
700711
}
701712
}
702713

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/HeifParserTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public void testSimple() throws Exception {
5151
assertEquals("image/heic", metadata.get(Metadata.CONTENT_TYPE));
5252
assertEquals("23.177917", metadata.get(Metadata.LATITUDE));
5353
assertEquals("113.394317", metadata.get(Metadata.LONGITUDE));
54+
assertEquals("42.810337", metadata.get(Geographic.ALTITUDE));
5455

5556
assertEquals("2018-02-05T07:11:43Z", metadata.get(Geographic.TIMESTAMP));
5657
}

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/ImageMetadataExtractorTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import java.util.Locale;
2929
import java.util.TimeZone;
3030

31+
import com.drew.lang.Rational;
3132
import com.drew.metadata.Directory;
3233
import com.drew.metadata.MetadataException;
3334
import com.drew.metadata.Tag;
3435
import com.drew.metadata.exif.ExifIFD0Directory;
3536
import com.drew.metadata.exif.ExifSubIFDDirectory;
37+
import com.drew.metadata.exif.GpsDirectory;
3638
import com.drew.metadata.jpeg.JpegCommentDirectory;
3739
import org.junit.jupiter.api.Test;
3840
import org.mockito.Mockito;
@@ -85,6 +87,28 @@ public void testExifHandlerParseDate() throws MetadataException {
8587
"Should be ISO date without time zone");
8688
}
8789

90+
@Test
91+
public void testGeotagHandlerAltitude() throws MetadataException {
92+
GpsDirectory gps = Mockito.mock(GpsDirectory.class);
93+
Mockito.when(gps.getRational(GpsDirectory.TAG_ALTITUDE)).thenReturn(new Rational(2274, 10));
94+
Mockito.when(gps.getInteger(GpsDirectory.TAG_ALTITUDE_REF)).thenReturn(0);
95+
Metadata metadata = new Metadata();
96+
97+
new ImageMetadataExtractor.GeotagHandler().handle(gps, metadata);
98+
assertEquals("227.4", metadata.get(TikaCoreProperties.ALTITUDE));
99+
}
100+
101+
@Test
102+
public void testGeotagHandlerAltitudeBelowSeaLevel() throws MetadataException {
103+
GpsDirectory gps = Mockito.mock(GpsDirectory.class);
104+
Mockito.when(gps.getRational(GpsDirectory.TAG_ALTITUDE)).thenReturn(new Rational(2274, 10));
105+
Mockito.when(gps.getInteger(GpsDirectory.TAG_ALTITUDE_REF)).thenReturn(1);
106+
Metadata metadata = new Metadata();
107+
108+
new ImageMetadataExtractor.GeotagHandler().handle(gps, metadata);
109+
assertEquals("-227.4", metadata.get(TikaCoreProperties.ALTITUDE));
110+
}
111+
88112
@Test
89113
public void testExifHandlerParseDateFallback() throws MetadataException {
90114
ExifIFD0Directory exif = Mockito.mock(ExifIFD0Directory.class);

0 commit comments

Comments
 (0)