diff --git a/src/consts/datasets.json b/src/consts/datasets.json index 81b722416..ec7ec570a 100644 --- a/src/consts/datasets.json +++ b/src/consts/datasets.json @@ -1,5 +1,6 @@ { "kofi-supporters": "https://raw.githubusercontent.com/PapillonApp/datasets/main/kofi-supporters.json", "changelog": "https://raw.githubusercontent.com/PapillonApp/datasets/main/updates/[version].json", - "illustrations": "https://raw.githubusercontent.com/PapillonApp/datasets/refs/heads/main/illustrations/index.json" + "illustrations": "https://raw.githubusercontent.com/PapillonApp/datasets/refs/heads/main/illustrations/index.json", + "establishment": "https://raw.githubusercontent.com/PapillonApp/datasets/refs/heads/main/establishment/[postcode].json" } \ No newline at end of file diff --git a/src/services/pronote/dataset_geolocation.ts b/src/services/pronote/dataset_geolocation.ts new file mode 100644 index 000000000..28af52024 --- /dev/null +++ b/src/services/pronote/dataset_geolocation.ts @@ -0,0 +1,63 @@ +import pronote from "pawnote"; +import datasets from "../../consts/datasets.json"; + +const getInstancesFromDataset = async (longitude: number, latitude: number): Promise => { + let adress_api_fetch = await fetch(`https://api-adresse.data.gouv.fr/reverse/?lon=${longitude}&lat=${latitude}&limit=1`); + try { + let adress_api = await adress_api_fetch.json(); + if (adress_api.features.length === 0) { + return []; + } + let postcode = adress_api.features[0].properties.postcode; + postcode = postcode[0] + postcode[1] + "000"; + + let instances_fetch = await fetch(datasets.establishment.replace("[postcode]", postcode)); + try { + let instances = await instances_fetch.json(); + + const calculateHaversineDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => { + const toRadians = (degrees: number) => degrees * (Math.PI / 180); + const R = 6371; // Earth's radius in kilometers + + const dLat = toRadians(lat2 - lat1); + const dLon = toRadians(lon2 - lon1); + + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return R * c; + }; + + return instances.map((instance: any) => { + const distance = calculateHaversineDistance( + latitude, + longitude, + instance.lat, + instance.long + ); + + console.log("User location:", { latitude, longitude }); + console.log("Instance location:", { latitude: instance.lat, longitude: instance.long }); + console.log("Calculated distance:", distance); + + return { + name: instance.name.toUpperCase(), + url: instance.url, + distance, + longitude: instance.long, + latitude: instance.lat, + }; + }); + } catch (error) { + console.error("Error fetching instances:", error); + return []; + } + } catch (error) { + console.error("Error fetching address:", error); + return []; + } +}; + +export default getInstancesFromDataset; \ No newline at end of file diff --git a/src/views/login/pronote/PronoteGeolocation.tsx b/src/views/login/pronote/PronoteGeolocation.tsx index c61bacaed..8276c6c76 100644 --- a/src/views/login/pronote/PronoteGeolocation.tsx +++ b/src/views/login/pronote/PronoteGeolocation.tsx @@ -70,7 +70,8 @@ const PronoteGeolocation: Screen<"PronoteGeolocation"> = ({ navigation }) => { style={[styles.terms_text, { color: colors.text + "59" }]} > Votre position est nécessaire pour trouver les instances PRONOTE à proximité. - Elle n'est pas stockée et ne sera pas partagée. + Elle sera envoyée à Pronote et à l'api adresse du gouvernement pour trouver les établissements. + Elle n'est pas stockée. ); diff --git a/src/views/login/pronote/PronoteInstanceSelector.tsx b/src/views/login/pronote/PronoteInstanceSelector.tsx index b73d6cf6a..866a964b2 100644 --- a/src/views/login/pronote/PronoteInstanceSelector.tsx +++ b/src/views/login/pronote/PronoteInstanceSelector.tsx @@ -1,6 +1,15 @@ import React, { useEffect, useState } from "react"; import type { Screen } from "@/router/helpers/types"; -import { TextInput, TouchableOpacity, View, StyleSheet, ActivityIndicator, Keyboard, KeyboardEvent } from "react-native"; +import { + TextInput, + TouchableOpacity, + View, + StyleSheet, + ActivityIndicator, + Keyboard, + KeyboardEvent, + Text +} from "react-native"; import pronote from "pawnote"; import Reanimated, { LinearTransition, FlipInXDown, FadeInUp, FadeOutUp, ZoomIn, ZoomOut, Easing, ZoomInEasyDown } from "react-native-reanimated"; import determinateAuthenticationView from "@/services/pronote/determinate-authentication-view"; @@ -14,6 +23,7 @@ import { useTheme } from "@react-navigation/native"; import { Search, X, GraduationCap, } from "lucide-react-native"; import { useAlert } from "@/providers/AlertProvider"; import { Audio } from "expo-av"; +import getInstancesFromDataset from "@/services/pronote/dataset_geolocation"; const PronoteInstanceSelector: Screen<"PronoteInstanceSelector"> = ({ route: { params }, @@ -74,15 +84,48 @@ const PronoteInstanceSelector: Screen<"PronoteInstanceSelector"> = ({ }; }, []); - const playSound = () => sound?.replayAsync(); - useEffect(() => { + useEffect(() => { if (params) { void async function () { - const instances = await pronote.geolocation(params); + const dataset_instances = await getInstancesFromDataset(params.longitude, params.latitude); + const pronote_instances = await pronote.geolocation(params); + + // On calcule la distance entre les instances et l'utilisateur. + let instances = pronote_instances.map((instance) => { + const toRadians = (degrees: number) => degrees * (Math.PI / 180); + const R = 6371; // Earth's radius in kilometers + + const lat1 = toRadians(params.latitude); + const lon1 = toRadians(params.longitude); + const lat2 = toRadians(instance.latitude); + const lon2 = toRadians(instance.longitude); + + const dLat = lat2 - lat1; + const dLon = lon2 - lon1; + + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(lat1) * Math.cos(lat2) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const distance = R * c; + + return { + ...instance, + distance, // Distance in kilometers + }; + }); + // On limite à 20 instances. instances.splice(20); + // On ajoute les instances trouvées par l'API adresse. + instances.push(...dataset_instances); + + // On trie par distance. + instances.sort((a, b) => a.distance - b.distance); + + // On met à jour les instances. setInstances(instances); setOriginalInstances(instances); }(); @@ -248,7 +291,6 @@ const PronoteInstanceSelector: Screen<"PronoteInstanceSelector"> = ({ color={colors.text + "88"} /> } - text={instance.name} onPress={async () => { determinateAuthenticationView( instance.url, @@ -256,7 +298,32 @@ const PronoteInstanceSelector: Screen<"PronoteInstanceSelector"> = ({ showAlert ); }} - /> + > + + {instance.name} + + + {`à ${instance.distance.toFixed(2)}km de toi`} + + ))}