mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-05-08 07:36:48 -04:00
* Use latest react-native-exif-reader package version; use writeLocation instead of writeExif * Use react-native-exif-reader v0.4.0
107 lines
3.3 KiB
JavaScript
107 lines
3.3 KiB
JavaScript
// @flow
|
|
|
|
import { utcToZonedTime } from "date-fns-tz";
|
|
import { readExif, writeLocation } from "react-native-exif-reader";
|
|
import * as RNLocalize from "react-native-localize";
|
|
import { formatISONoTimezone } from "sharedHelpers/dateAndTime";
|
|
|
|
import { log } from "../../react-native-logs.config";
|
|
|
|
class UsePhotoExifDateFormatError extends Error {}
|
|
|
|
// https://wbinnssmith.com/blog/subclassing-error-in-modern-javascript/
|
|
Object.defineProperty( UsePhotoExifDateFormatError.prototype, "name", {
|
|
value: "UsePhotoExifDateFormatError"
|
|
} );
|
|
|
|
const logger = log.extend( "parseExif" );
|
|
|
|
// Parses EXIF date time into a date object
|
|
export const parseExifDateToLocalTimezone = ( datetime: string ): ?Date => {
|
|
if ( !datetime ) return null;
|
|
|
|
const isoDate = `${datetime}Z`;
|
|
|
|
// Use local timezone from device
|
|
const timeZone = RNLocalize.getTimeZone( );
|
|
const zonedDate = utcToZonedTime( isoDate, timeZone );
|
|
|
|
if ( !zonedDate || zonedDate.toString( ).match( /invalid/i ) ) {
|
|
throw new UsePhotoExifDateFormatError( "Date was not formatted correctly" );
|
|
}
|
|
|
|
return zonedDate;
|
|
};
|
|
|
|
// Parses EXIF date time into a date object
|
|
export const parseExif = async ( photoUri: ?string ): Promise<Object> => {
|
|
try {
|
|
return readExif( photoUri );
|
|
} catch ( e ) {
|
|
console.error( e, "Couldn't parse EXIF" );
|
|
return null;
|
|
}
|
|
};
|
|
|
|
interface ExifToWrite {
|
|
latitude?: number;
|
|
longitude?: number;
|
|
positional_accuracy?: number;
|
|
}
|
|
|
|
export const writeExifToFile = async ( photoUri: ?string, exif: ExifToWrite ): Promise<Object> => {
|
|
logger.debug( "writeExifToFile, photoUri: ", photoUri );
|
|
try {
|
|
return writeLocation( photoUri, exif );
|
|
} catch ( e ) {
|
|
console.error( e, "Couldn't write EXIF" );
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const formatExifDateAsString = ( datetime: string ): string => {
|
|
const zonedDate = parseExifDateToLocalTimezone( datetime );
|
|
// this returns a string, in the same format as photos which fall back to the
|
|
// photo timestamp instead of exif data
|
|
return formatISONoTimezone( zonedDate );
|
|
};
|
|
|
|
// Parse the EXIF of all photos - fill out details (lat/lng/date) from all of these,
|
|
// in case the first photo is missing EXIF
|
|
export const readExifFromMultiplePhotos = async ( photoUris: Array<string> ): Promise<Object> => {
|
|
const unifiedExif = {};
|
|
|
|
const responses = await Promise.allSettled( photoUris.map( parseExif ) );
|
|
const allExifPhotos: Array<{
|
|
latitude: number,
|
|
longitude: number,
|
|
positional_accuracy: number,
|
|
date: string
|
|
// Flow will complain that value is undefined, but the filter call ensures
|
|
// that it isn't
|
|
// $FlowIgnore
|
|
}> = responses.filter( r => r.value ).map( r => r.value );
|
|
|
|
allExifPhotos.filter( x => x ).forEach(
|
|
currentPhotoExif => {
|
|
const {
|
|
latitude, longitude, positional_accuracy: positionalAccuracy, date
|
|
} = currentPhotoExif;
|
|
|
|
if ( !unifiedExif.latitude ) {
|
|
unifiedExif.latitude = latitude;
|
|
}
|
|
if ( !unifiedExif.longitude ) {
|
|
unifiedExif.longitude = longitude;
|
|
}
|
|
if ( !unifiedExif.observed_on_string ) {
|
|
unifiedExif.observed_on_string = formatExifDateAsString( date ) || null;
|
|
}
|
|
if ( positionalAccuracy && !unifiedExif.positional_accuracy ) {
|
|
unifiedExif.positional_accuracy = positionalAccuracy;
|
|
}
|
|
}
|
|
);
|
|
return unifiedExif;
|
|
};
|