diff --git a/back-end/app.js b/back-end/app.js index 02b662e..5b3a632 100644 --- a/back-end/app.js +++ b/back-end/app.js @@ -1,9 +1,42 @@ // import and instantiate express const express = require('express'); // CommonJS import style! const app = express(); // instantiate an Express object +require('dotenv').config(); // we will put some server logic here later... const cors = require('cors'); +const { createGraph, findOptimalRoute } = require('./getOptimizedRoute.js'); app.use(cors()); module.exports = app; + + + +app.get('/getRoute', (req, res) => { + const busStops = { + unionsquare: [40.73498551788369, -73.990696048825], + tompkins: [40.7251277537963, -73.98121749968648], + hamilton: [40.720213039417494, -73.980207088181], + eastbroadway: [40.7141707879376, -73.9901227463216], + chinatown: [40.71559782189394, -73.99850504010124], + financial: [40.70811320415039, -74.00798229139403], + tribeca: [40.716765448621125, -74.00913568860388], + canal: [40.721857387647354, -74.00518353611693], + soho: [40.72488113646396, -74.00162848801607], + greenwich: [40.733368703161425, -74.00412600650982], + washingtonsquare: [40.731522867853634, -73.9971283464239], + jayst: [40.69225251854892, -73.98648974152808], + dumbo: [40.70372584858276, -73.98861865993923], + ikea: [40.672611811522145, -74.01009335210519] + } + const routes = { + route1: [busStops.washingtonsquare, busStops.unionsquare, busStops.tompkins, busStops.hamilton, busStops.eastbroadway, busStops.chinatown, busStops.financial, busStops.tribeca, busStops.canal, busStops.soho, busStops.greenwich, busStops.washingtonsquare], + route2: [busStops.jayst, busStops.dumbo, busStops.ikea], + route3: [busStops.jayst, busStops.eastbroadway, busStops.washingtonsquare, busStops.chinatown, busStops.financial], + route4: [busStops.eastbroadway, busStops.washingtonsquare, busStops.unionsquare, busStops.tompkins, busStops.hamilton, busStops.eastbroadway], + } + const graph = createGraph(routes); + let optimalRoute = findOptimalRoute(graph, req.query.origin, req.query.destination); + console.log(optimalRoute); + res.send('Hello World!'); +}) \ No newline at end of file diff --git a/back-end/getOptimizedRoute.js b/back-end/getOptimizedRoute.js new file mode 100644 index 0000000..c5b9dec --- /dev/null +++ b/back-end/getOptimizedRoute.js @@ -0,0 +1,101 @@ +//this function creates a directed circular graph representation of of bus stops and the routes. +/*in the output graph, the key represents a bus stop, and the value represents all the bus stops that +are directly reachavble from the key bus stop.*/ +function createGraph(routes) { + let graph = {}; + for (let routeId in routes) { + let stops = routes[routeId]; + for ( let i = 0; i < stops.length; i++) { + let currentStop = stops[i]; + let nextStop = stops[(i + 1) % stops.length]; + let currentKey = currentStop.toString(); + + if (!graph[currentKey]) { + graph[currentKey] = []; + } + graph[currentKey].push({ coordinates: nextStop, route: routeId }); + + } + } + return graph; +} + +function getEuclideanDistance (origin, destination) { + let x1 = origin.split(',')[0]; + let y1 = origin.split(',')[1]; + let x2 = destination.split(',')[0]; + let y2 = destination.split(',')[1]; + return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2)); +} + +//find bus stops that are relatively close in term of walking distance +function findAllReachableStops(graph, origin, threshold=0.013) { + let reachableStops = []; + for (busstop in graph){ + busstop = busstop.toString(); + let distance = getEuclideanDistance(busstop, origin); + if (distance < threshold) { + console.log('reachable stop found: '+graph[busstop][0].route); + reachableStops.push({coordinates: busstop, route: graph[busstop][0].route}); + } + } + + return reachableStops; + +} +// return walking distance from point A to point B +async function getWalkingDistance(origin, destination) { + origin = origin.split(',').join('%2C'); + destination = destination.split(',').join('%2C'); + let res = await fetch(`https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&origins=${origin}&destinations=${destination}&key=${process.env.EXPRESS_APP_MAP_API_KEY}`) + let data = await res.json(); + res = data.rows[0].elements[0].duration.value; + return res; +} + +function isOnSameRoute(stop1, stop2) { + console.log(stop1.route); + return stop1.route === stop2.route; +} + +async function findOptimalRoute(graph, origin, destination) { + let minTotalDistance = Number.MAX_VALUE; + let optimalRoute = null; + + // Find all reachable stops from origin and destination + let reachableFromOrigin = findAllReachableStops(graph, origin); + let reachableFromDestination = findAllReachableStops(graph, destination); + + // Calculate route distances + for (let originStop of reachableFromOrigin) { + for (let destinationStop of reachableFromDestination) { + if (isOnSameRoute(originStop, destinationStop)) { + let distanceToOriginStop = await getWalkingDistance(origin, originStop.coordinates); + let distanceFromDestinationStop = await getWalkingDistance(destinationStop.coordinates, destination); + + let totalDistance = distanceToOriginStop + distanceFromDestinationStop; + console.log('fetched from Google Maps API, total distance: '+totalDistance); + console.log('----------------------------------') + // Check if this route is better than the current best + if (totalDistance < minTotalDistance) { + minTotalDistance = totalDistance; + optimalRoute = { originStop, destinationStop }; + } + } + } + } + console.log('optimal route: '+optimalRoute.originStop.route); + console.log('origin stop: '+optimalRoute.originStop.coordinates); + console.log('destination stop: '+optimalRoute.destinationStop.coordinates); + + return optimalRoute; +} + +module.exports = { + createGraph, + getEuclideanDistance, + findAllReachableStops, + getWalkingDistance, + isOnSameRoute, + findOptimalRoute +} \ No newline at end of file diff --git a/front-end/src/utils/transportMarker.js b/front-end/src/utils/transportMarker.js index 809c47e..52ffd00 100644 --- a/front-end/src/utils/transportMarker.js +++ b/front-end/src/utils/transportMarker.js @@ -20,7 +20,7 @@ export function updateTransportMarkers(transportData, markerRef, map) { const lat = parseFloat(transportInfo.latitude); const lng = parseFloat(transportInfo.longitude); const newPosition = new window.google.maps.LatLng(lat, lng); - const iconUpdate = marker && marker.direction !== transportInfo.calculatedCourse; + const iconUpdate = marker && (marker.direction !== transportInfo.calculatedCourse || transportInfo.route === 'Ferry Route'); if (marker) { // Update the position of the existing marker @@ -29,12 +29,12 @@ export function updateTransportMarkers(transportData, markerRef, map) { // Update the icon of the existing marker if (iconUpdate) { - const newIcon = generateTransportMarkerIcon(transportInfo.color, transportInfo.calculatedCourse); + const newIcon = generateTransportMarkerIcon(transportInfo.color, transportInfo.calculatedCourse, transportInfo.route); marker.setIcon(newIcon); } } else { // Create a new marker - const newIcon = generateTransportMarkerIcon(transportInfo.color, transportInfo.calculatedCourse); + const newIcon = generateTransportMarkerIcon(transportInfo.color, transportInfo.calculatedCourse, transportInfo.route); let transportMarker = createTransportMarker(newPosition, transportInfo, map, newIcon); markerRef.current[transport] = transportMarker; } @@ -46,11 +46,11 @@ function createTransportMarker(position, transportInfo, map) { position, map, title: String(transportInfo.busId), - icon: generateTransportMarkerIcon(transportInfo.color || '#000', transportInfo.calculatedCourse || 0), + icon: generateTransportMarkerIcon(transportInfo.color || '#000', transportInfo.calculatedCourse || 0, transportInfo.route || 0), }); let infowindow = new window.google.maps.InfoWindow({ - content: `
No.${transportInfo.busId}
Line: ${transportInfo.route}
`, + content: `
No.${transportInfo.busId}
Line: ${transportInfo.route}
Passenger Count: ${transportInfo.paxLoad}`, }); transportMarker.direction = transportInfo.calculatedCourse; @@ -108,7 +108,7 @@ function animateMarker(marker, startPosition, endPosition, duration) { // Generate custom transport marker icon (currently only support buses) // Original source of bus svg path: www.svgrepo.com -function generateTransportMarkerIcon(color, direction) { +function generateTransportMarkerIcon(color, direction, route) { const svg = ` @@ -122,9 +122,15 @@ function generateTransportMarkerIcon(color, direction) { `; + let iconImg = 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(svg); + let scaledSize = new window.google.maps.Size(40, 40); + if (route === 'Ferry Route') { + iconImg = 'busIcons/busIcon_routeFerry_Route.png'; + scaledSize= new window.google.maps.Size(30, 30); + } const icon = { - url: 'data:image/svg+xml;charset=UTF-8;base64,' + btoa(svg), - scaledSize: new window.google.maps.Size(40, 40), + url: iconImg, + scaledSize: scaledSize, }; return icon; }