Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion components/Leaderboard/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ export default function Leaderboard() {
[leaderKey]
);

/* Get icon */

const getIcon = () => {
if (leaderKey === ELeaderKeys.Pins) {
return <i className="bi bi-pin-fill"></i>;
}
return <i className="bi bi-signpost-fill"></i>;
};

/* Get player distance */

const getValue = (player: Player) => {
if (leaderKey === ELeaderKeys.Distance) {
return Math.round(player.value) + ' km';
Expand All @@ -54,7 +58,7 @@ export default function Leaderboard() {
</select>
<div
className={styles.leading_info}
title="Distance values are calculated by summing the distance to the CeSIUM headquarters of all the pins from each author"
title="Distance values are calculated by summing the distance to the CeSIUM headquarters of all the pins from each author. Pins that are close together will be grouped and counted as one."
>
<i className="bi bi-info-circle-fill"></i>
</div>
Expand Down
155 changes: 116 additions & 39 deletions components/Leaderboard/utils.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,155 @@
import { sortByOldest } from '~/lib/utils';
import { sortByOldest, getDistance } from '~/lib/utils';
import { IPin } from '~/lib/types';
import styles from './style.module.css';
import { Player, User } from './types';
import { getDistance } from '~/lib/utils';
import { User, Player } from './types';

function getSet(arr: User[]) {
function getSet(arr: User[]): User[] {
return arr.filter(
(v, i, a) =>
a.findIndex((v2) => ['username'].every((k) => v2[k] === v[k])) === i
(v, i, a) => a.findIndex((v2) => v2.username === v.username) === i
);
}

function sortByDistance(a: User, b: User) {
const d1 = getDistance(a.coordinates);
const d2 = getDistance(b.coordinates);
return d2 - d1;
function distance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): number {
const R = 6371; // Radius of the earth in km
const dLat = deg2rad(lat2 - lat1);
const dLon = deg2rad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(deg2rad(lat1)) *
Math.cos(deg2rad(lat2)) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // Distance in km
}

export function makeLeaderboard(places: IPin[], type: string) {
const users = [];
const leaderboard = [];
const places_copy = places.slice();
const sortedPlaces = places_copy.sort(sortByOldest);
function deg2rad(deg: number): number {
return deg * (Math.PI / 180);
}

function groupPins(pins: IPin[], maxDistance: number): IPin[][] {
const groups: IPin[][] = []; // Array to store groups of pins

// Determine group for each pin
for (const pin of pins) {
let added = false; // Checks if the pin was added to another group

// Check if the pin belongs to a group
for (const group of groups) {
if (
group.some(
(p) =>
distance(
p.coordinates[0],
p.coordinates[1],
pin.coordinates[0],
pin.coordinates[1]
) <= maxDistance
)
) {
group.push(pin); // Add pin to the group
added = true;
break;
}
}
// If the pin isn't part of any group, create a new one
if (!added) {
groups.push([pin]);
}
}

for (var i = 0; i < sortedPlaces.length; i++) {
if (Array.isArray(sortedPlaces[i].username)) {
for (var j = 0; j < sortedPlaces[i].username.length; j++) {
return groups;
}

export function makeLeaderboard(places: IPin[], type: string): Player[] {
const users: User[] = [];
const leaderboard: Player[] = [];
const sortedPlaces = places.slice().sort(sortByOldest);

// Populate users array
for (const place of sortedPlaces) {
// More than one person
if (Array.isArray(place.username) && Array.isArray(place.author)) {
for (let j = 0; j < place.username.length; j++) {
users.push({
author: sortedPlaces[i].author[j],
username: sortedPlaces[i].username[j],
coordinates: sortedPlaces[i].coordinates
author: place.author[j],
username: place.username[j],
coordinates: place.coordinates
});
}
} else {
// If there is just one person and its username is a string
} else if (
typeof place.username === 'string' &&
typeof place.author === 'string'
) {
users.push({
author: sortedPlaces[i].author,
username: sortedPlaces[i].username,
coordinates: sortedPlaces[i].coordinates
author: place.author,
username: place.username,
coordinates: place.coordinates
});
// Invalid username
} else {
console.error('Unexpected type for username or author:', place);
}
}

// Guarantee that users don't appear more than once in leaderboard
const userSet = getSet(users);

for (var i = 0; i < userSet.length; i++) {
var acc = 0;
// Gives each user the respective pins and total distance
for (const user of userSet) {
let acc = 0;

// Logic to get the pins
const userPins = sortedPlaces.filter((place) => {
const usernames = Array.isArray(place.username)
? place.username.map((u) => u.trim().toLowerCase())
: [place.username.trim().toLowerCase()];
return usernames.includes(user.username.trim().toLowerCase());
});

switch (type) {
case 'Pins': {
for (var j = 0; j < users.length; j++) {
if (userSet[i].username === users[j].username) {
acc++;
}
}
acc = userPins.length;
break;
}
case 'Distance': {
for (var j = 0; j < users.length; j++) {
if (userSet[i].username === users[j].username) {
acc += getDistance(users[j].coordinates);
// Group pins that are close together
const groups = groupPins(userPins, 130);

// Calculates max distance within group
acc = groups.reduce((totalDistance, group) => {
if (group.length > 1) {
// For groups with multiple pins, calculate max distance
const groupMaxDistance = group.reduce(
(maxDistance, pin) =>
Math.max(maxDistance, getDistance(pin.coordinates)),
0
);
return totalDistance + groupMaxDistance;
} else {
// For isolated pins, just add their distance
return totalDistance + getDistance(group[0].coordinates);
}
}
}, 0);
break;
}
}

leaderboard.push({
author: userSet[i].author,
username: userSet[i].username,
author: user.author,
username: user.username,
value: acc
});
}

return leaderboard;
// Sort leaderboard by value in descending order
return leaderboard.sort((a, b) => b.value - a.value);
}

export function getOrdinals(num: number) {
Expand Down