Files
UT-Registration-Plus/src/views/components/map/utils.ts
doprz 218477404f feat: map page (#390)
* feat: add boilerplate

* feat: add working paths

* feat: improve building selection controls and add week schedule

Signed-off-by: doprz <52579214+doprz@users.noreply.github.com>

* fix: sort week schedule

Signed-off-by: doprz <52579214+doprz@users.noreply.github.com>

* feat(testing): improve pathfinding

* Revert "feat(testing): improve pathfinding"

This reverts commit 87998cedbf.

* feat: add pathfinding with building selection controls

Signed-off-by: doprz <52579214+doprz@users.noreply.github.com>

* feat: improve path finding algorithm thresholds

* feat: add DaySelector, PathStats, and WeekSchedule components

* feat: add working stats and daily schedule

* chore: refactor everything

* feat: add linear walkway node generation

* feat: add bezier curve walkway node generation

* feat: add circular walkway node generation

* docs: add docs

* feat: add individual path selection and bump version

* fix: tsdoc and updated components/utils

* chore(deps): update deps

* feat: add UTRP Map and Debug Page to Settings > Developer Mode

* chore: fix pr review comments

* chore: add showDebugNodes

* chore: add all buildings around the UT tower

* chore: add stadium POIs

* chore: add east mall buildings

* chore: update DaySelector to use proper button styling

* chore: add university ave walkway

* feat: add zoom, pan, and dev controls functionality

- Fix SVG Overlay Alignment
- Use SVG for map
- Add Dev Controls
- Fix day selector position
- Update the SVG's `preserveAspectRatio` attribute to `xMidYMid` meet to
ensure proper scaling
- Use `useCallback` for event handlers to prevent unnecessary re-renders
- Remove old PNG map

* feat: add dynamic rendering"

* feat: add dynamicRendering dev toggle and fullscreen support

* chore: update deps

* chore: disable viewport svg overlay culling if dynamic rendering is off

* chore: update pnpm-lock.yaml

* chore: add north mall buildings

* chore: add buildings next to JES

* refactor: map components into individual files

* fix: missing import

---------

Signed-off-by: doprz <52579214+doprz@users.noreply.github.com>
2025-02-27 19:44:03 -06:00

112 lines
4.2 KiB
TypeScript

import { graphNodes } from './graphNodes';
import type { Graph, MapNode, NodeCoordinates, NodeId } from './types';
import { isValidNode, NEIGHBOR_DISTANCE_THRESHOLD } from './types';
/**
* Calculates the Euclidean distance between two points.
*
* @param point1 - The coordinates of the first point.
* @param point2 - The coordinates of the second point.
* @returns The distance between the two points.
*/
export const calculateDistance = (point1: NodeCoordinates, point2: NodeCoordinates): number =>
Math.sqrt((point2.x - point1.x) ** 2 + (point2.y - point1.y) ** 2);
/**
* Finds the nearest nodes to a given node in a graph.
*
* @param nodeId - The ID of the node for which to find the nearest nodes.
* @param graph - The graph containing all nodes.
* @returns An array of node IDs representing the nearest nodes.
*
* The function first checks if the current node is valid. If not, it returns an empty array.
* It then calculates the distances to all other valid nodes in the graph.
* The nodes are sorted by distance, and the function first attempts to connect to the nearest intersections.
* If no intersections are found, it connects to the nearest buildings.
*/
const findNearestNodes = (nodeId: NodeId, graph: Graph): NodeId[] => {
const currentNode = graph[nodeId];
if (!isValidNode(currentNode)) return [];
// Calculate distances to all other nodes
const distances = Object.entries(graph)
.filter((entry): entry is [string, MapNode] => {
const [id, node] = entry;
return id !== nodeId && isValidNode(node);
})
.map(([id, node]) => ({
id,
distance: calculateDistance(currentNode, node),
isIntersection: node.type === 'intersection',
}))
.sort((a, b) => a.distance - b.distance);
// First try to connect to nearest intersections
const nearestIntersections = distances
.filter(node => node.isIntersection)
.slice(0, 2)
.map(node => node.id);
if (nearestIntersections.length > 0) {
return nearestIntersections;
}
// If no intersections found, connect to nearest buildings
return distances.slice(0, 2).map(node => node.id);
};
/**
* Retrieves the neighboring nodes of a given node within a graph.
*
* @param nodeId - The ID of the node for which neighbors are to be found.
* @param graph - The graph containing all nodes and their connections.
* @returns An array of node IDs representing the neighbors of the given node.
*
* This function first checks if the current node is valid. If not, it returns an empty array.
* It then filters the graph to find all valid neighboring nodes within a specified distance threshold.
* If no direct neighbors are found, it attempts to connect to the nearest intersection or building.
*/
export const getNeighbors = (nodeId: NodeId, graph: Graph): NodeId[] => {
const currentNode = graph[nodeId];
if (!isValidNode(currentNode)) return [];
// Get all possible neighbors within the increased threshold
const neighbors = Object.entries(graph)
.filter((entry): entry is [string, MapNode] => {
const [id, node] = entry;
if (!isValidNode(node) || id === nodeId) return false;
const distance = calculateDistance(currentNode, node);
return distance < NEIGHBOR_DISTANCE_THRESHOLD;
})
.map(([id]) => id);
// If no direct neighbors found, connect to the nearest intersection or building
if (neighbors.length === 0) {
const nearestNodes = findNearestNodes(nodeId, graph);
return nearestNodes;
}
return neighbors;
};
/**
* Calculates the midpoint between two nodes identified by their IDs.
*
* @param startId - The ID of the starting node.
* @param endId - The ID of the ending node.
* @returns An object containing the x and y coordinates of the midpoint, or null if either node is invalid.
*/
export const getMidpoint = (startId: string, endId: string) => {
const startNode = graphNodes[startId];
const endNode = graphNodes[endId];
if (!isValidNode(startNode) || !isValidNode(endNode)) {
return null;
}
return {
x: (startNode.x + endNode.x) / 2,
y: (startNode.y + endNode.y) / 2,
};
};