I can't get the GPS metadata of an image on Android React Native, I've tested many libraries, most show 0 and some 0/1, 0/1, 0/1, but they are not the correct GPS coordinates.
i tested many libraries but it cant get the correct values of gps:
import React, { useEffect, useState } from "react";
import { Text, View, TouchableOpacity, Image, Modal } from "react-native";
import ImagePicker from "react-native-image-crop-picker";
import { requestPermission } from "../../utils/nativeFunctions";
import { getStyles } from "./styles";
import { useTheme } from "@trizyapp/sdk";
import { CameraIcon, GaleriaIcon } from "../../assets/icons";
import { parse } from "date-fns";
import { writeAsync, readAsync, ExifTags } from "@lodev09/react-native-exify";
import { PermissionsAndroid } from "react-native";
import { convertCoordinates } from "../../utils/convertCoordenadas";
interface Props {
resource: string;
setResource: (resource: any) => void;
onlyModal?: boolean;
openModal?: boolean;
handleClose?: () => void;
setOpenModal?: (openModal: boolean) => void;
}
const CustomImagePicker = ({
resource,
setResource,
onlyModal = false,
openModal = false,
handleClose,
}: Props) => {
const styles = getStyles({});
const { color: colors, spaces } = useTheme();
const [openButtonsModal, setOpenButtonsModal] = useState(false);
const handlePress = () => {
setOpenButtonsModal(true);
};
useEffect(() => {
if (openModal && !openButtonsModal) {
setOpenButtonsModal(true);
}
}, [openModal]);
const handleModalClose = () => {
setOpenButtonsModal(false);
handleClose && handleClose();
};
const extractMetadata = (exifData: any) => {
if (!exifData) {
return {
data_evidencia: null,
latitude: null,
longitude: null,
};
}
let latitude = null;
let longitude = null;
if (exifData.GPS && exifData.GPS.Latitude && exifData.GPS.Longitude) {
latitude = exifData.GPS.Latitude;
longitude = exifData.GPS.Longitude;
} else if (
exifData.GPSLatitude !== undefined &&
exifData.GPSLongitude !== undefined
) {
latitude = exifData.GPSLatitude;
longitude = exifData.GPSLongitude;
} else if (
exifData.latitude !== undefined &&
exifData.longitude !== undefined
) {
latitude = exifData.latitude;
longitude = exifData.longitude;
}
let dataEvidencia = null;
const dateFields = [
"DateTime",
"DateTimeOriginal",
"DateTimeDigitized",
"creationDate",
];
for (const field of dateFields) {
if (exifData[field]) {
try {
let dateString = exifData[field];
if (dateString.includes(":")) {
dateString = dateString.replace(
/^(\d{4}):(\d{2}):(\d{2})/,
"$1-$2-$3"
);
dataEvidencia = parse(
dateString,
"yyyy-MM-dd HH:mm:ss",
new Date()
);
} else {
dataEvidencia = new Date(dateString);
}
if (!isNaN(dataEvidencia.getTime())) {
break;
}
} catch (e) {
console.log(`Error parsing date from ${field}:`, e);
}
}
}
return {
data_evidencia: dataEvidencia,
...convertCoordinates(longitude, latitude),
};
};
const selectFile = async (tipo: string) => {
try {
let permissao = false;
if (tipo === "camera") {
permissao = await requestPermission({ tipo: "camera" });
permissao = await requestPermission({ tipo: "localizacao_midia" });
} else {
permissao = await requestPermission({ tipo: "galeria" });
permissao = await requestPermission({
tipo: "ler_armazenamento_externo",
});
permissao = await requestPermission({ tipo: "localizacao_midia" });
console.log("Permissão de localizacao_midia", permissao);
}
console.log(
"Status real das permissões:",
await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
),
await PermissionsAndroid.check(
PermissionsAndroid.PERMISSIONS.ACCESS_MEDIA_LOCATION
)
);
if (permissao) {
const options = {
width: 1000,
height: 1000,
cropping: false,
includeExif: true,
includeExtra: true,
compressImageQuality: 0.8,
forceJpg: true,
};
let result = null;
if (tipo === "camera") {
permissao = await requestPermission({ tipo: "localizacao" });
console.log("Permissão de localização:", permissao);
result = await ImagePicker.openCamera({
...options,
useFrontCamera: false,
});
} else {
result = await ImagePicker.openPicker(options);
}
if (result) {
const imagePath = result.path;
const tags = await readAsync(imagePath);
console.log(tags);
// const tags = await readAsync(imagePath);
console.log(
"Raw image result:",
JSON.stringify(
{
path: result.path,
width: result.width,
height: result.height,
mime: result.mime,
size: result.size,
exif: result.exif
? // Criar um novo objeto com pares chave-valor
Object.fromEntries(
Object.entries(result.exif).map(([key, value]) => [
key,
// Converter valores complexos para formato legível
value instanceof Object && !(value instanceof Array)
? value.toString()
: value,
])
)
: null,
},
null,
2
) // Formatação com indentação
);
const metaData = extractMetadata(result.exif);
setResource({ image: result.path, metaData });
}
handleModalClose();
}
} catch (error) {
Promise.reject(error);
}
};
const renderModal = () => {
return (
<Modal
animationType="slide"
transparent
visible={openButtonsModal}
onRequestClose={() => handleModalClose()}
>
<View style={styles.modalMain}>
<View style={styles.modalView}>
<View style={{ alignItems: "center" }}>
<TouchableOpacity onPress={() => selectFile("camera")}>
<CameraIcon
width={spaces.x4}
height={spaces.x4}
color={colors.grey600}
/>
</TouchableOpacity>
<Text style={styles.emptyText}>Câmera</Text>
</View>
<View style={{ alignItems: "center" }}>
<TouchableOpacity onPress={() => selectFile("gallery")}>
<GaleriaIcon
width={spaces.x4}
height={spaces.x4}
color={colors.grey600}
/>
</TouchableOpacity>
<Text style={styles.emptyText}>Galeria</Text>
</View>
</View>
</View>
</Modal>
);
};
return onlyModal ? (
renderModal()
) : (
<View style={styles.container}>
{resource && resource.length > 0 ? (
<Image
resizeMode="contain"
source={{ uri: resource }}
style={styles.image}
/>
) : (
<TouchableOpacity onPress={handlePress} style={styles.emptyButton}>
<GaleriaIcon
width={spaces.x6}
height={spaces.x6}
color={colors.grey}
/>
<Text style={styles.emptyText}>Insira seu arquivo aqui </Text>
<Text style={styles.emptyText}>(.jpeg, .jpg ou png)</Text>
</TouchableOpacity>
)}
{renderModal()}
</View>
);
};
export default CustomImagePicker;
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
import React, { useState } from "react";
import { View, Modal, TouchableOpacity } from "react-native";
import { getStyles } from "./styles";
import { useTheme, Button, CloseIcon, Text, SizedBox } from "@trizyapp/sdk";
import { ImagePicker, TextInput } from "../../../../components";
import { DeleteIcon, RefreshIcon, SendIcon } from "../../../../assets/icons";
interface Evidencia {
imagem_url: string;
legenda: string;
data_evidencia: string | null;
latitude?: number;
longitude?: number;
}
interface Props {
open: boolean;
handleClose: () => void;
handleRemove: () => void;
evidencias?: Evidencia[];
setEvidencias: (evidencias: Evidencia[]) => void;
}
const ModalEvidencia = ({
open,
handleClose,
handleRemove,
evidencias = [],
setEvidencias,
}: Props) => {
const styles = getStyles({});
const { color: colors, spaces } = useTheme();
const [openPicker, setOpenPicker] = useState(false);
const [files, setFiles] = useState<Evidencia[]>(
evidencias?.length > 0
? evidencias
: [{ imagem_url: "", legenda: "", data_evidencia: null }]
);
const toggleModalPicker = () => setOpenPicker((prev) => !prev);
const updateFile = (index: number, updatedData: Partial<Evidencia>) => {
console.log(`Atualizando arquivo no índice ${index}:`, updatedData);
setFiles((prevFiles) =>
prevFiles.map((file, i) =>
i === index ? { ...file, ...updatedData } : file
)
);
};
return (
<Modal
transparent
animationType="slide"
visible={open}
onRequestClose={handleClose}
>
<View style={styles.modalMain}>
<View style={styles.modalHeader}>
<TouchableOpacity onPress={handleClose}>
<CloseIcon color={"#FFF"} />
</TouchableOpacity>
<TouchableOpacity onPress={handleRemove}>
<DeleteIcon />
</TouchableOpacity>
</View>
<View style={styles.modalView}>
<View style={styles.content}>
{files.map((file, index) => (
<View key={index}>
<ImagePicker
openModal={openPicker}
handleClose={() => setOpenPicker(false)}
resource={file.imagem_url}
setResource={(newValue) =>
updateFile(index, {
imagem_url: newValue?.image,
data_evidencia: newValue?.metaData?.data_evidencia,
latitude: newValue?.metaData?.latitude,
longitude: newValue?.metaData?.longitude,
})
}
/>
<TextInput
value={file.legenda}
onChangeText={(text) => updateFile(index, { legenda: text })}
numberOfLines={2}
placeholder="Inserir legenda"
maxLength={90}
/>
</View>
))}
</View>
</View>
<SizedBox height={spaces.x1} />
<View style={styles.modalFooter}>
<View style={styles.action}>
<TouchableOpacity
style={[styles.iconButton, styles.refresh]}
onPress={toggleModalPicker}
>
<RefreshIcon />
</TouchableOpacity>
<Text fontColor="white" value="Ficou ruim" />
</View>
<View style={styles.action}>
<TouchableOpacity
style={[styles.iconButton, styles.send]}
onPress={() => setEvidencias(files)}
disabled={!files[0]?.imagem_url}
>
<SendIcon />
</TouchableOpacity>
<Text fontColor="white" value="Enviar" />
</View>
</View>
</View>
</Modal>
);
};
export default ModalEvidencia;
Example of answer:
"path": "file:///storage/emulated/0/Android/data/com.formularios/files/Pictures/3307a61e-b805-49d5-85a7-950ca6b48530.jpg", "width": 2604, "height": 4624, "mime": "image/jpeg", "size": 302829, "exif": { "SubSecTimeOriginal": null, "SubSecTimeDigitized": null, "SubSecTime": null, "DateTimeDigitized": "2025:03:28 16:35:42", "Orientation": "6", "ImageLength": "2604", "GPSProcessingMethod": null, "Make": "samsung", "GPSLongitudeRef": "", "GPSLatitudeRef": "", "Flash": "0", "GPSLatitude": "0/1,0/1,0/1", "ImageWidth": "4624", "GPSDateStamp": null, "GPSAltitudeRef": null, "WhiteBalance": "0", "GPSAltitude": null, "FocalLength": "523/100", "ExposureTime": "0.1", "ISOSpeedRatings": "2500", "Latitude": 0, "DateTime": "2025:03:28 16:35:42", "Longitude": 0, "FNumber": "1.8", "Model": "SM-M315F", "GPSTimeStamp": null, "GPSLongitude": "0/1,0/1,0/1"