Added Haversine Formula calculation to get the radius of circles for study groups required for rendering

This commit is contained in:
Keannu Bernasol 2023-09-09 20:45:29 +08:00
parent 85e2a13071
commit 7b9d05f84b
4 changed files with 225 additions and 159 deletions

View file

@ -1,12 +1,12 @@
import { Callout } from "react-native-maps";
import { LocationType } from "../../interfaces/Interfaces";
import { RawLocationType } from "../../interfaces/Interfaces";
import styles from "../../styles";
import { Text } from "react-native";
// Map popup for user's location
type props = {
location: LocationType;
location: RawLocationType;
studying: boolean;
subject?: string;
};

View file

@ -0,0 +1,125 @@
import * as React from "react";
import { View, Text } from "react-native";
import {
StudentStatusFilterType,
LocationType,
subjectUserMapType,
StudentStatusListType,
StudentStatusFilterTypeFlattened,
} from "../../interfaces/Interfaces";
import { Double, Float } from "react-native/Libraries/Types/CodegenTypes";
export default function ParseStudyGroupList(data: any) {
let result: any[] = [];
// Circle generation for students in a study group
// We first flatten the data to remove nested entries
console.log("Initial Data:", data);
let flattened_data = data
.filter((item: StudentStatusFilterType) => item.study_group !== "")
.map((item: StudentStatusFilterType) => ({
active: item.active,
distance: item.distance,
landmark: item.landmark,
latitude: item.location.latitude,
longitude: item.location.longitude,
study_group: item.study_group,
subject: item.subject,
user: item.user,
weight: 1,
}));
console.log("Filtered Data:", flattened_data);
// We get each unique subject
let unique_subjects = [
...new Set(
flattened_data.map((item: StudentStatusFilterType) => item.subject)
),
];
// Then append all entries belonging to that subject to its own array
unique_subjects.forEach((subject, index: number) => {
index++;
let filteredData = flattened_data.filter(
(item: StudentStatusFilterTypeFlattened) => item.subject === subject
);
console.log("Subject #", index, "-", filteredData[0].subject, filteredData);
// We get the circle's center by averaging all the points
// Calculate the average latitude and longitude
const totalLat = filteredData.reduce(
(sum: Double, point: LocationType) => sum + point.latitude,
0
);
const totalLng = filteredData.reduce(
(sum: Double, point: LocationType) => sum + point.longitude,
0
);
const avgLat = totalLat / filteredData.length;
const avgLng = totalLng / filteredData.length;
console.log("Center Latitude:", avgLat);
console.log("Center Longitude:", avgLng);
// We now calculate the radius of the circle using the Haversine Distance Formula
function haversineDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
) {
function toRad(x: number) {
return (x * Math.PI) / 180;
}
var R = 6371; // km
var x1 = lat2 - lat1;
var dLat = toRad(x1);
var x2 = lon2 - lon1;
var dLon = toRad(x2);
var a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) *
Math.cos(toRad(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
}
let circle_radius =
Math.max(
...filteredData.map((item: StudentStatusFilterTypeFlattened) =>
haversineDistance(avgLat, avgLng, item.latitude, item.longitude)
)
) * 1000;
console.log("Radius:", circle_radius);
// We now build the object
const subjectUserMap: subjectUserMapType = {
subject: "",
users: [],
latitude: 0,
longitude: 0,
radius: 0,
};
filteredData.forEach((item: StudentStatusFilterType) => {
if (!subjectUserMap["users"]) {
subjectUserMap["users"] = [];
}
subjectUserMap["subject"] = item.subject;
subjectUserMap["latitude"] = avgLat;
subjectUserMap["longitude"] = avgLng;
subjectUserMap["radius"] = circle_radius;
subjectUserMap["users"].push(item.user);
});
console.log(subjectUserMap);
result = result.concat([subjectUserMap]);
});
// console.log("Final Result:", result);
return result;
}

View file

@ -124,7 +124,7 @@ export interface PatchUserInfoType {
avatar?: string;
}
interface Location {
export interface LocationType {
latitude: Float;
longitude: Float;
}
@ -132,7 +132,7 @@ interface Location {
export interface StudentStatusType {
user?: string;
subject?: string;
location?: Location;
location?: LocationType;
landmark?: string | null;
active?: boolean;
}
@ -141,10 +141,23 @@ export interface StudentStatusFilterType {
active: boolean;
distance: number;
landmark: string | null;
location: Location;
location: LocationType;
study_group?: string;
subject: string;
user: string;
weight?: number;
}
export interface StudentStatusFilterTypeFlattened {
active: boolean;
distance: number;
landmark: string | null;
latitude: Float;
longitude: Float;
study_group?: string;
subject: string;
user: string;
weight?: number;
}
export type StudentStatusReturnType = [boolean, StudentStatusType];
@ -152,7 +165,7 @@ export type StudentStatusReturnType = [boolean, StudentStatusType];
export type StudentStatusListType = Array<StudentStatusFilterType>;
export type StudentStatusListReturnType = [boolean, StudentStatusListType];
export type LocationType = Location.LocationObject;
export type RawLocationType = Location.LocationObject;
export interface UserInfoType {
first_name: string;
@ -172,3 +185,11 @@ export interface UserInfoType {
}
export type UserInfoReturnType = [boolean, UserInfoType];
export type subjectUserMapType = {
subject: string;
users: string[];
latitude: Float;
longitude: Float;
radius: Float;
};

View file

@ -17,10 +17,11 @@ import Button from "../../components/Button/Button";
import {
RootDrawerParamList,
StudentStatusReturnType,
LocationType,
RawLocationType,
StudentStatusType,
StudentStatusListReturnType,
StudentStatusListType,
subjectUserMapType,
} from "../../interfaces/Interfaces";
import { useNavigation } from "@react-navigation/native";
import {
@ -33,12 +34,14 @@ import {
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useToast } from "react-native-toast-notifications";
import CustomMapCallout from "../../components/CustomMapCallout/CustomMapCallout";
import ParseStudentStatusList from "../../components/ParseStudyGroupList/ParseStudyGroupList";
import React from "react";
export default function Home() {
// Switch this condition to see the main map when debugging
const map_debug = true;
const navigation = useNavigation<RootDrawerParamList>();
const [location, setLocation] = useState<LocationType | null>(null);
const [location, setLocation] = useState<RawLocationType | null>(null);
const [dist, setDist] = useState<number | null>(null);
const [feedback, setFeedback] = useState(
"To continue, please allow Stud-E permission to location services"
@ -97,7 +100,7 @@ export default function Home() {
requestLocation();
}, []);
async function GetDistanceRoundedOff(location: LocationType) {
async function GetDistanceRoundedOff(location: RawLocationType) {
let dist = GetDistance(
location.coords.latitude,
location.coords.longitude,
@ -176,8 +179,8 @@ export default function Home() {
},
});
// Can probably just get the max distance from the array and use it as radius
const [student_statuses, setStudentStatuses] = useState<any>([]);
const [study_groups, setStudyGroups] = useState<subjectUserMapType[]>([]);
// Student Status List
const StudentStatusList = useQuery({
enabled: studying,
@ -191,93 +194,7 @@ export default function Home() {
},
onSuccess: (data: StudentStatusListReturnType) => {
if (data[1]) {
// Circle generation for students in a study group
// We first flatten the data to remove nested entries
let flattened_data = data[1].map((item) => ({
active: item.active,
distance: item.distance,
landmark: item.landmark,
latitude: item.location.latitude,
longitude: item.location.longitude,
study_group: "",
subject: item.subject,
user: item.user,
weight: 1,
}));
// Dummy data
flattened_data.push({
active: true,
distance: 50,
landmark: "",
latitude: 8.498837,
longitude: 124.595422,
study_group: "",
subject: "Introduction to Computing",
user: "Dummy",
weight: 1,
});
// We get each unique subject
let unique_subjects = [
...new Set(flattened_data.map((item) => item.subject)),
];
let result: any[] = [];
// Then append all entries belonging to that subject to its own array
unique_subjects.forEach((subject, index: number) => {
index++;
let filteredData = flattened_data.filter(
(item) => item.subject === subject && item.study_group === ""
);
/*console.log(
"Subject #",
index,
"-",
filteredData[0].subject,
filteredData
);*/
// We get the circle radius based on the furthest point
let circle_radius = Math.max(
...filteredData.map((item) => item.distance)
);
console.log(
"Radius of circle:",
Math.max(...filteredData.map((item) => item.distance))
);
// We get the circle's center by averaging all the points
// Calculate the average latitude and longitude
const totalLat = filteredData.reduce(
(sum, point) => sum + point.latitude,
0
);
const totalLng = filteredData.reduce(
(sum, point) => sum + point.longitude,
0
);
const avgLat = totalLat / filteredData.length;
const avgLng = totalLng / filteredData.length;
console.log("Center Latitude:", avgLat);
console.log("Center Longitude:", avgLng);
// We now build the object
const subjectUserMap: any = {};
filteredData.forEach((item) => {
if (!subjectUserMap["users"]) {
subjectUserMap["users"] = [];
}
subjectUserMap["subject"] = item.subject;
subjectUserMap["latitude"] = avgLat;
subjectUserMap["longitude"] = avgLng;
subjectUserMap["radius"] = circle_radius;
subjectUserMap["users"].push(item.user);
});
console.log(subjectUserMap);
result = result.concat([subjectUserMap]);
});
console.log("Final Result:", result);
setStudentStatuses(result);
setStudyGroups(ParseStudentStatusList(data[1]));
}
},
onError: (error: Error) => {
@ -322,73 +239,76 @@ export default function Home() {
}}
loadingBackgroundColor={colors.secondary_2}
>
{student_statuses.map((student_status: any, index: number) => {
const randomColorWithOpacity = `rgba(${Math.floor(
Math.random() * 256
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
Math.random() * 256
)}, 0.7)`;
{study_groups.map(
(student_status: subjectUserMapType, index: number) => {
const randomColorWithOpacity = `rgba(${Math.floor(
Math.random() * 256
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
Math.random() * 256
)}, 0.7)`;
return (
<>
<Marker
coordinate={student_status}
pinColor={randomColorWithOpacity}
zIndex={1000}
onPress={() => {
toast.hideAll();
toast.show(
<View
style={{
alignContent: "center",
alignSelf: "center",
justifyContent: "center",
}}
>
<Text style={styles.text_white_tiny_bold}>
Subject: {student_status.subject}
</Text>
<Text style={styles.text_white_tiny_bold}>
Students Studying: {student_status.users.length}
</Text>
<Button
onPress={() => {
toast.show("Joined successfully", {
type: "success",
placement: "top",
duration: 2000,
animationType: "slide-in",
});
return (
<React.Fragment key={index}>
<Marker
coordinate={student_status}
pinColor={randomColorWithOpacity}
zIndex={1000}
onPress={() => {
toast.hideAll();
toast.show(
<View
style={{
alignContent: "center",
alignSelf: "center",
justifyContent: "center",
}}
>
<Text style={styles.text_white_tiny_bold}>
Create Group & Invite
Subject: {student_status.subject}
</Text>
</Button>
</View>,
{
type: "normal",
placement: "top",
duration: 2000,
animationType: "slide-in",
style: {
backgroundColor: colors.secondary_2,
borderWidth: 1,
borderColor: colors.primary_1,
},
}
);
}}
/>
<Circle
key={index}
center={student_status}
radius={student_status.radius}
fillColor={randomColorWithOpacity}
/>
</>
);
})}
<Text style={styles.text_white_tiny_bold}>
Students Studying: {student_status.users.length}
</Text>
<Button
onPress={() => {
toast.show("Joined successfully", {
type: "success",
placement: "top",
duration: 2000,
animationType: "slide-in",
});
}}
>
<Text style={styles.text_white_tiny_bold}>
Create Group & Invite
</Text>
</Button>
</View>,
{
type: "normal",
placement: "top",
duration: 2000,
animationType: "slide-in",
style: {
backgroundColor: colors.secondary_2,
borderWidth: 1,
borderColor: colors.primary_1,
},
}
);
}}
/>
<Circle
center={student_status}
radius={student_status.radius}
fillColor={randomColorWithOpacity}
strokeColor="white"
zIndex={1000}
/>
</React.Fragment>
);
}
)}
<Marker
coordinate={{
latitude: location.coords.latitude,