mirror of
https://github.com/lemeow125/StudE-Frontend.git
synced 2024-11-17 06:19:25 +08:00
Added Haversine Formula calculation to get the radius of circles for study groups required for rendering
This commit is contained in:
parent
85e2a13071
commit
7b9d05f84b
4 changed files with 225 additions and 159 deletions
|
@ -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;
|
||||
};
|
||||
|
|
125
src/components/ParseStudyGroupList/ParseStudyGroupList.tsx
Normal file
125
src/components/ParseStudyGroupList/ParseStudyGroupList.tsx
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue