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 { Callout } from "react-native-maps";
|
||||||
import { LocationType } from "../../interfaces/Interfaces";
|
import { RawLocationType } from "../../interfaces/Interfaces";
|
||||||
import styles from "../../styles";
|
import styles from "../../styles";
|
||||||
import { Text } from "react-native";
|
import { Text } from "react-native";
|
||||||
|
|
||||||
// Map popup for user's location
|
// Map popup for user's location
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
location: LocationType;
|
location: RawLocationType;
|
||||||
studying: boolean;
|
studying: boolean;
|
||||||
subject?: string;
|
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;
|
avatar?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Location {
|
export interface LocationType {
|
||||||
latitude: Float;
|
latitude: Float;
|
||||||
longitude: Float;
|
longitude: Float;
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ interface Location {
|
||||||
export interface StudentStatusType {
|
export interface StudentStatusType {
|
||||||
user?: string;
|
user?: string;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
location?: Location;
|
location?: LocationType;
|
||||||
landmark?: string | null;
|
landmark?: string | null;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -141,10 +141,23 @@ export interface StudentStatusFilterType {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
distance: number;
|
distance: number;
|
||||||
landmark: string | null;
|
landmark: string | null;
|
||||||
location: Location;
|
location: LocationType;
|
||||||
study_group?: string;
|
study_group?: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
user: 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];
|
export type StudentStatusReturnType = [boolean, StudentStatusType];
|
||||||
|
@ -152,7 +165,7 @@ export type StudentStatusReturnType = [boolean, StudentStatusType];
|
||||||
export type StudentStatusListType = Array<StudentStatusFilterType>;
|
export type StudentStatusListType = Array<StudentStatusFilterType>;
|
||||||
export type StudentStatusListReturnType = [boolean, StudentStatusListType];
|
export type StudentStatusListReturnType = [boolean, StudentStatusListType];
|
||||||
|
|
||||||
export type LocationType = Location.LocationObject;
|
export type RawLocationType = Location.LocationObject;
|
||||||
|
|
||||||
export interface UserInfoType {
|
export interface UserInfoType {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
|
@ -172,3 +185,11 @@ export interface UserInfoType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserInfoReturnType = [boolean, 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 {
|
import {
|
||||||
RootDrawerParamList,
|
RootDrawerParamList,
|
||||||
StudentStatusReturnType,
|
StudentStatusReturnType,
|
||||||
LocationType,
|
RawLocationType,
|
||||||
StudentStatusType,
|
StudentStatusType,
|
||||||
StudentStatusListReturnType,
|
StudentStatusListReturnType,
|
||||||
StudentStatusListType,
|
StudentStatusListType,
|
||||||
|
subjectUserMapType,
|
||||||
} from "../../interfaces/Interfaces";
|
} from "../../interfaces/Interfaces";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import {
|
import {
|
||||||
|
@ -33,12 +34,14 @@ import {
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useToast } from "react-native-toast-notifications";
|
import { useToast } from "react-native-toast-notifications";
|
||||||
import CustomMapCallout from "../../components/CustomMapCallout/CustomMapCallout";
|
import CustomMapCallout from "../../components/CustomMapCallout/CustomMapCallout";
|
||||||
|
import ParseStudentStatusList from "../../components/ParseStudyGroupList/ParseStudyGroupList";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
// Switch this condition to see the main map when debugging
|
// Switch this condition to see the main map when debugging
|
||||||
const map_debug = true;
|
const map_debug = true;
|
||||||
const navigation = useNavigation<RootDrawerParamList>();
|
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 [dist, setDist] = useState<number | null>(null);
|
||||||
const [feedback, setFeedback] = useState(
|
const [feedback, setFeedback] = useState(
|
||||||
"To continue, please allow Stud-E permission to location services"
|
"To continue, please allow Stud-E permission to location services"
|
||||||
|
@ -97,7 +100,7 @@ export default function Home() {
|
||||||
requestLocation();
|
requestLocation();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function GetDistanceRoundedOff(location: LocationType) {
|
async function GetDistanceRoundedOff(location: RawLocationType) {
|
||||||
let dist = GetDistance(
|
let dist = GetDistance(
|
||||||
location.coords.latitude,
|
location.coords.latitude,
|
||||||
location.coords.longitude,
|
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 [student_statuses, setStudentStatuses] = useState<any>([]);
|
||||||
|
const [study_groups, setStudyGroups] = useState<subjectUserMapType[]>([]);
|
||||||
// Student Status List
|
// Student Status List
|
||||||
const StudentStatusList = useQuery({
|
const StudentStatusList = useQuery({
|
||||||
enabled: studying,
|
enabled: studying,
|
||||||
|
@ -191,93 +194,7 @@ export default function Home() {
|
||||||
},
|
},
|
||||||
onSuccess: (data: StudentStatusListReturnType) => {
|
onSuccess: (data: StudentStatusListReturnType) => {
|
||||||
if (data[1]) {
|
if (data[1]) {
|
||||||
// Circle generation for students in a study group
|
setStudyGroups(ParseStudentStatusList(data[1]));
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
|
@ -322,73 +239,76 @@ export default function Home() {
|
||||||
}}
|
}}
|
||||||
loadingBackgroundColor={colors.secondary_2}
|
loadingBackgroundColor={colors.secondary_2}
|
||||||
>
|
>
|
||||||
{student_statuses.map((student_status: any, index: number) => {
|
{study_groups.map(
|
||||||
const randomColorWithOpacity = `rgba(${Math.floor(
|
(student_status: subjectUserMapType, index: number) => {
|
||||||
Math.random() * 256
|
const randomColorWithOpacity = `rgba(${Math.floor(
|
||||||
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
|
Math.random() * 256
|
||||||
Math.random() * 256
|
)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(
|
||||||
)}, 0.7)`;
|
Math.random() * 256
|
||||||
|
)}, 0.7)`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<React.Fragment key={index}>
|
||||||
<Marker
|
<Marker
|
||||||
coordinate={student_status}
|
coordinate={student_status}
|
||||||
pinColor={randomColorWithOpacity}
|
pinColor={randomColorWithOpacity}
|
||||||
zIndex={1000}
|
zIndex={1000}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
toast.hideAll();
|
toast.hideAll();
|
||||||
toast.show(
|
toast.show(
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
alignContent: "center",
|
alignContent: "center",
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
justifyContent: "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",
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={styles.text_white_tiny_bold}>
|
<Text style={styles.text_white_tiny_bold}>
|
||||||
Create Group & Invite
|
Subject: {student_status.subject}
|
||||||
</Text>
|
</Text>
|
||||||
</Button>
|
<Text style={styles.text_white_tiny_bold}>
|
||||||
</View>,
|
Students Studying: {student_status.users.length}
|
||||||
{
|
</Text>
|
||||||
type: "normal",
|
<Button
|
||||||
placement: "top",
|
onPress={() => {
|
||||||
duration: 2000,
|
toast.show("Joined successfully", {
|
||||||
animationType: "slide-in",
|
type: "success",
|
||||||
style: {
|
placement: "top",
|
||||||
backgroundColor: colors.secondary_2,
|
duration: 2000,
|
||||||
borderWidth: 1,
|
animationType: "slide-in",
|
||||||
borderColor: colors.primary_1,
|
});
|
||||||
},
|
}}
|
||||||
}
|
>
|
||||||
);
|
<Text style={styles.text_white_tiny_bold}>
|
||||||
}}
|
Create Group & Invite
|
||||||
/>
|
</Text>
|
||||||
<Circle
|
</Button>
|
||||||
key={index}
|
</View>,
|
||||||
center={student_status}
|
{
|
||||||
radius={student_status.radius}
|
type: "normal",
|
||||||
fillColor={randomColorWithOpacity}
|
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
|
<Marker
|
||||||
coordinate={{
|
coordinate={{
|
||||||
latitude: location.coords.latitude,
|
latitude: location.coords.latitude,
|
||||||
|
|
Loading…
Reference in a new issue