Repository: cristina-trofimov/concordia_campus_navigation
Pull request #150: Shuttle Bus real time data
Author: login: Bou1103
Created: 2025-03-09T10:05:18Z | Merged: 2025-03-13T01:53:30Z

## Description
Fetched real-time shuttle bus data from the Concordia API (refresh every 15 s) formatted it and displayed it on the map through an icon

## Related Issue
#110 

## Changes Made

- Added interface for ShuttleBusLocation.tsx
-  Added component for ShuttleBusTracker.tsx
- Added ShuttleBus-Icon in resources
- Refactored Map.tsx to display the real-time location of shuttle

## Checklist
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings or errors
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [x] I have updated the documentation accordingly

## Screenshots (if applicable)
<img width="408" alt="Screenshot 2025-03-10 at 2 58 00 AM" src="https://github.com/user-attachments/assets/3a46f4f8-7b5a-4cce-924b-4d7579df9929" />
<img width="423" alt="Screenshot 2025-03-10 at 2 50 51 PM" src="https://github.com/user-attachments/assets/2f23a1f8-f45a-4142-a7fa-2939cb1e6d22" />


Buses are currently off-campus (in a parking) cause screenshoot is taken on Sunday, will get a better one on Monday

## Additional Notes
next step would be to use the data for navigation (will be implemented next sprint)
If you have a suggestion for a better icon in dark red let me know in the comments, I couldn't find much choice



Diff:
diff --git a/package-lock.json b/package-lock.json
index bd743ae1..b8cbe994 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,6 @@
         "@fortawesome/react-native-fontawesome": "^0.3.2",
         "@howljs/calendar-kit": "^2.2.1",
         "@mapbox/polyline": "^1.2.1",
-        "@react-native-mapbox-gl/maps": "^8.6.0-beta.0",
         "@react-native/gradle-plugin": "^0.77.0",
         "@react-navigation/drawer": "^7.1.2",
         "@react-navigation/native": "^7.0.15",
@@ -43,7 +42,7 @@
         "react-native-gesture-handler": "~2.20.2",
         "react-native-get-random-values": "^1.11.0",
         "react-native-google-places-autocomplete": "^2.5.7",
-        "react-native-maps": "1.18.0",
+        "react-native-maps": "^1.18.0",
         "react-native-maps-directions": "^1.9.0",
         "react-native-modal": "^13.0.1",
         "react-native-reanimated": "~3.16.1",
@@ -4141,15 +4140,6 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
-    "node_modules/@mapbox/geo-viewport": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/@mapbox/geo-viewport/-/geo-viewport-0.5.0.tgz",
-      "integrity": "sha512-h0b10JU+lSxw8/TLXGdzcVTPxMN9Ikv0os8sCo0OAHXUiSDkQs5fx4WWLJeQTnC++qaGFl6/Ssr+H5N6NIvE5g==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "@mapbox/sphericalmercator": "^1.2.0"
-      }
-    },
     "node_modules/@mapbox/polyline": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-1.2.1.tgz",
@@ -4161,17 +4151,6 @@
         "polyline": "bin/polyline.bin.js"
       }
     },
-    "node_modules/@mapbox/sphericalmercator": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz",
-      "integrity": "sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ==",
-      "bin": {
-        "bbox": "bin/bbox.js",
-        "to4326": "bin/to4326.js",
-        "to900913": "bin/to900913.js",
-        "xyz": "bin/xyz.js"
-      }
-    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -4466,156 +4445,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/@react-native-mapbox-gl/maps": {
-      "version": "8.6.0-beta.0",
-      "resolved": "https://registry.npmjs.org/@react-native-mapbox-gl/maps/-/maps-8.6.0-beta.0.tgz",
-      "integrity": "sha512-UpHpP7Bc0oUMhqMVda6tTCdv2UYr4JhyQS8D+oqItVYr9W52KBpKVDGXDb6/nRdkypSMUqZJuwu7XfaxeJgchg==",
-      "deprecated": "React Native Mapbox Maps moved to new organization: http://github.com/rnmapbox/maps",
-      "license": "MIT",
-      "dependencies": {
-        "@expo/config-plugins": "^4.0.3",
-        "@mapbox/geo-viewport": ">= 0.4.0",
-        "@turf/along": "6.5.0",
-        "@turf/distance": "6.5.0",
-        "@turf/helpers": "6.5.0",
-        "@turf/length": "6.5.0",
-        "@turf/nearest-point-on-line": "6.5.0",
-        "@types/geojson": "^7946.0.7",
-        "debounce": "^1.2.0"
-      },
-      "peerDependencies": {
-        "prop-types": ">=15.5.8",
-        "react": ">=16.6.1",
-        "react-native": ">=0.59.9"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/@babel/code-frame": {
-      "version": "7.10.4",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
-      "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/highlight": "^7.10.4"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/@expo/config-plugins": {
-      "version": "4.1.5",
-      "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-4.1.5.tgz",
-      "integrity": "sha512-RVvU40RtZt12HavuDAe+LDIq9lHj7sheOfMEHdmpJ/uTA8pgvkbc56XF6JHQD+yRr6+uhhb+JnAasGq49dsQbw==",
-      "license": "MIT",
-      "dependencies": {
-        "@expo/config-types": "^45.0.0",
-        "@expo/json-file": "8.2.36",
-        "@expo/plist": "0.0.18",
-        "@expo/sdk-runtime-versions": "^1.0.0",
-        "@react-native/normalize-color": "^2.0.0",
-        "chalk": "^4.1.2",
-        "debug": "^4.3.1",
-        "find-up": "~5.0.0",
-        "getenv": "^1.0.0",
-        "glob": "7.1.6",
-        "resolve-from": "^5.0.0",
-        "semver": "^7.3.5",
-        "slash": "^3.0.0",
-        "xcode": "^3.0.1",
-        "xml2js": "0.4.23"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/@expo/config-types": {
-      "version": "45.0.0",
-      "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-45.0.0.tgz",
-      "integrity": "sha512-/QGhhLWyaGautgEyU50UJr5YqKJix5t77ePTwreOVAhmZH+ff3nrrtYTTnccx+qF08ZNQmfAyYMCD3rQfzpiJA==",
-      "license": "MIT"
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/@expo/json-file": {
-      "version": "8.2.36",
-      "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.2.36.tgz",
-      "integrity": "sha512-tOZfTiIFA5KmMpdW9KF7bc6CFiGjb0xnbieJhTGlHrLL+ps2G0OkqmuZ3pFEXBOMnJYUVpnSy++52LFxvpa5ZQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "~7.10.4",
-        "json5": "^1.0.1",
-        "write-file-atomic": "^2.3.0"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/@expo/plist": {
-      "version": "0.0.18",
-      "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.0.18.tgz",
-      "integrity": "sha512-+48gRqUiz65R21CZ/IXa7RNBXgAI/uPSdvJqoN9x1hfL44DNbUoWHgHiEXTx7XelcATpDwNTz6sHLfy0iNqf+w==",
-      "license": "MIT",
-      "dependencies": {
-        "@xmldom/xmldom": "~0.7.0",
-        "base64-js": "^1.2.3",
-        "xmlbuilder": "^14.0.0"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/glob": {
-      "version": "7.1.6",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
-      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
-      "deprecated": "Glob versions prior to v9 are no longer supported",
-      "license": "ISC",
-      "dependencies": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.0.4",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      },
-      "engines": {
-        "node": "*"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/json5": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
-      "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
-      "license": "MIT",
-      "dependencies": {
-        "minimist": "^1.2.0"
-      },
-      "bin": {
-        "json5": "lib/cli.js"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/semver": {
-      "version": "7.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
-      "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/xml2js": {
-      "version": "0.4.23",
-      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
-      "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
-      "license": "MIT",
-      "dependencies": {
-        "sax": ">=0.6.0",
-        "xmlbuilder": "~11.0.0"
-      },
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
-    "node_modules/@react-native-mapbox-gl/maps/node_modules/xml2js/node_modules/xmlbuilder": {
-      "version": "11.0.1",
-      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
-      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
     "node_modules/@react-native/assets-registry": {
       "version": "0.76.7",
       "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.76.7.tgz",
@@ -4880,12 +4709,6 @@
         "@babel/core": "*"
       }
     },
-    "node_modules/@react-native/normalize-color": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz",
-      "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==",
-      "license": "MIT"
-    },
     "node_modules/@react-native/normalize-colors": {
       "version": "0.76.7",
       "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.7.tgz",
@@ -18968,6 +18791,7 @@
       "version": "2.20.2",
       "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz",
       "integrity": "sha512-HqzFpFczV4qCnwKlvSAvpzEXisL+Z9fsR08YV5LfJDkzuArMhBu2sOoSPUF/K62PCoAb+ObGlTC83TKHfUd0vg==",
+      "license": "MIT",
       "dependencies": {
         "@egjs/hammerjs": "^2.0.17",
         "hoist-non-react-statics": "^3.3.0",
@@ -19023,6 +18847,7 @@
       "version": "1.18.0",
       "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.18.0.tgz",
       "integrity": "sha512-S17nYUqeMptgIPaAZuVRo+eRelPreBBYQWw6jsxU7qQ12p+THSfFaqabcNn7fBmsXhT3T27iIl8ek8v1H8BaGw==",
+      "license": "MIT",
       "dependencies": {
         "@types/geojson": "^7946.0.13"
       },
diff --git a/package.json b/package.json
index 03f78092..20353209 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,6 @@
     "@react-navigation/native": "^7.0.15",
     "@react-navigation/native-stack": "^7.2.1",
     "@react-navigation/stack": "^7.1.2",
-    "@react-native-mapbox-gl/maps": "^8.6.0-beta.0",
     "@react-native/gradle-plugin": "^0.77.0",
     "@rneui/base": "^4.0.0-rc.7",
     "@rneui/themed": "^4.0.0-rc.8",
diff --git a/src/components/Map.tsx b/src/components/Map.tsx
index 7e0ff3e3..37378515 100644
--- a/src/components/Map.tsx
+++ b/src/components/Map.tsx
@@ -15,6 +15,8 @@ import { HighlightBuilding } from './BuildingCoordinates';
 import BuildingInformation from './BuildingInformation.tsx';
 import { BuildingProperties } from '../interfaces/BuildingProperties.ts';
 import BuildingLocation from '../interfaces/buildingLocation.ts';
+import ShuttleBusTracker from './ShuttleBusTracker.tsx';
+
 
 const MAPBOX_TOKEN = 'sk.eyJ1IjoibWlkZHkiLCJhIjoiY202c2ZqdW03MDhjMzJxcTUybTZ6d3k3cyJ9.xPp9kFl0VC1SDnlp_ln2qA';
 
@@ -259,7 +261,8 @@ export default function Map({ drawerHeight }: { drawerHeight: Animated.Value })
           </Mapbox.ShapeSource>
         )}
 
-
+        {/* Add ShuttleBusMarkers component */}
+        <ShuttleBusTracker />
 
       </MapView>
       
@@ -348,6 +351,4 @@ const styles = StyleSheet.create({
   calloutDescription: {
     fontSize: 14,
   },
-});
-
-
+});
\ No newline at end of file
diff --git a/src/components/ShuttleBusTracker.tsx b/src/components/ShuttleBusTracker.tsx
new file mode 100644
index 00000000..8ef99e59
--- /dev/null
+++ b/src/components/ShuttleBusTracker.tsx
@@ -0,0 +1,89 @@
+import React, { useEffect, useState } from 'react';
+import { View, ActivityIndicator, StyleSheet, Image, Text } from 'react-native';
+import { PointAnnotation } from '@rnmapbox/maps';
+import axios from 'axios';
+import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
+import { GoogleObject, BusDataResponse } from '../interfaces/ShuttleBusLocation'; 
+
+const ShuttleBusTracker: React.FC = () => {
+    const [busData, setBusData] = useState<GoogleObject | null>(null);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [error, setError] = useState<string | null>(null);
+
+    const fetchBusData = async () => {
+        try {
+             // Step 1: First make a GET request to get session cookies
+            await axios.get('https://shuttle.concordia.ca/concordiabusmap/Map.aspx', {
+                headers: {
+                    'Host': 'shuttle.concordia.ca'
+                }
+            });
+      
+            // Step 2: Then make the POST request with cookies from the previous request
+            const response = await axios.post<BusDataResponse>(
+                'https://shuttle.concordia.ca/concordiabusmap/WebService/GService.asmx/GetGoogleObject',
+                {}, 
+                {
+                    headers: {
+                        'Host': 'shuttle.concordia.ca',
+                        'Content-Length': '0',
+                        'Content-Type': 'application/json; charset=UTF-8'
+                    }
+                }
+            );
+
+            setBusData(response.data.d);
+
+            setLoading(false);
+
+        } catch (err) {
+            console.error('Error fetching bus data:', err);
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchBusData();
+
+        // Set up an interval to fetch data every 15 seconds
+        const interval = setInterval(fetchBusData, 15000);
+
+        // Cleanup interval on component unmount
+        return () => clearInterval(interval);
+    }, []);
+
+    if (loading) {
+        return (
+            <View >
+                <ActivityIndicator size="large" color="#0000ff" />
+            </View>
+        );
+    }
+
+    if (error) {
+        return null;
+    }
+
+    if (!busData || !busData.Points) {
+        return null;
+    }
+
+    // Filter the Points array to include only buses (IDs starting with "BUS")
+    const busPoints = busData.Points.filter((point) => point.ID.startsWith('BUS'));
+
+    return (
+        <>
+            {busPoints.map((point) => (
+                <PointAnnotation
+                    key={point.ID}
+                    id={point.ID}
+                    coordinate={[point.Longitude, point.Latitude]}
+                >
+                    <MaterialCommunityIcons name="bus-side" size={62} color="#912338" />
+                </PointAnnotation>
+            ))}
+        </>
+    );
+};
+
+export default ShuttleBusTracker;
diff --git a/src/interfaces/ShuttleBusLocation.ts b/src/interfaces/ShuttleBusLocation.ts
new file mode 100644
index 00000000..1275f675
--- /dev/null
+++ b/src/interfaces/ShuttleBusLocation.ts
@@ -0,0 +1,52 @@
+export interface GoogleObject {
+    __type: string;
+    Directions: {
+        Addresses: any[];
+        Locale: string;
+        ShowDirectionInstructions: boolean;
+        HideMarkers: boolean;
+        PolylineOpacity: number;
+        PolylineWeight: number;
+        PolylineColor: string;
+    };
+    Points: Point[];
+    Polylines: any[];
+    Polygons: any[];
+    CenterPoint: Point;
+    ZoomLevel: number;
+    ShowZoomControl: boolean;
+    RecenterMap: boolean;
+    AutomaticBoundaryAndZoom: boolean;
+    ShowTraffic: boolean;
+    ShowMapTypesControl: boolean;
+    Width: string;
+    Height: string;
+    MapType: string;
+    APIKey: string;
+    APIVersion: string;
+}
+
+interface Point {
+    PointStatus: string;
+    Address: string;
+    ID: string;
+    IconImage: string;
+    IconShadowImage: string;
+    IconImageWidth: number;
+    IconShadowWidth: number;
+    IconShadowHeight: number;
+    IconAnchor_posX: number;
+    IconAnchor_posY: number;
+    InfoWindowAnchor_posX: number;
+    InfoWindowAnchor_posY: number;
+    Draggable: boolean;
+    IconImageHeight: number;
+    Latitude: number;
+    Longitude: number;
+    InfoHTML: string;
+    ToolTip: string;
+}
+
+export interface BusDataResponse {
+    d: GoogleObject;
+}


Reviews:
user: login: lawrence122
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T14:23:38Z
user: login: Julia-Trinh
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-09T19:46:04Z
user: login: Bou1103
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T07:20:06Z
user: login: Bou1103
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T07:20:30Z
user: login: Bou1103
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T07:33:41Z
user: login: lawrence122
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-10T13:34:48Z
user: login: Julia-Trinh
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-10T16:21:44Z

Review comments:
diff_hunk: @@ -0,0 +1,101 @@
+import React, { useEffect, useState } from 'react';
+import { View, ActivityIndicator, StyleSheet, Image, Text } from 'react-native';
+import { PointAnnotation } from '@rnmapbox/maps';
+import axios from 'axios';
+import { GoogleObject, BusDataResponse } from '../interfaces/ShuttleBusLocation'; // Import the types
+
+const ShuttleBusTracker: React.FC = () => {
+    const [busData, setBusData] = useState<GoogleObject | null>(null);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [error, setError] = useState<string | null>(null);
+
+    const fetchBusData = async () => {
+        try {
+            // Make a POST request to get the bus data
+            const postResponse = await axios.post<BusDataResponse>(
+                'https://shuttle.concordia.ca/concordiabusmap/WebService/GService.asmx/GetGoogleObject',
+                {},
+                {
+                    headers: {
+                        'Host': 'shuttle.concordia.ca',
+                        'Content-Length': '0',
+                        'Content-Type': 'application/json; charset=UTF-8',
+                    }
+                }
+            );
+
+            // Set the bus data to state
+            setBusData(postResponse.data.d);
+            setLoading(false);
+        } catch (err) {
+            console.error('Error fetching bus data:', err);
+            setError('Error fetching bus data');
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchBusData();
+
+        // Set up an interval to fetch data every 15 seconds
+        const interval = setInterval(fetchBusData, 15000);
+
+        // Cleanup interval on component unmount
+        return () => clearInterval(interval);
+    }, []);
+
+    if (loading) {
+        return (
+            <View style={styles.container}>
+                <ActivityIndicator size="large" color="#0000ff" />
+            </View>
+        );
+    }
+
+    if (error) {
+        return (
+            <View style={styles.container}>
+                <Text style={styles.errorText}>Error: {error}</Text>
+            </View>
+        );
+    }
+
+    if (!busData || !busData.Points) {
+        return null;
+    }
+
+    // Filter the Points array to include only buses (IDs starting with "BUS")
+    const busPoints = busData.Points.filter((point) => point.ID.startsWith('BUS'));
+
+    return (
+        <>
+            {busPoints.map((point) => (
+                <PointAnnotation
+                    key={point.ID}
+                    id={point.ID}
+                    coordinate={[point.Longitude, point.Latitude]}
+                >
+                    <Image
+                        source={require('../resources/images/ShuttleBus-Icon.png')}
+                        style={{ width: 100, height: 40 }}
+                    />
+                </PointAnnotation>
+            ))}
+        </>
+    );
+};
+
+// Styles
+const styles = StyleSheet.create({
+    container: {
+        flex: 1,
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    errorText: {
+        fontSize: 18,
+        color: 'red',
+    },
+});
path: src/components/ShuttleBusTracker.tsx
user: login: lawrence122
body: This should be in its own file in the styles folder
created_at: 2025-03-09T14:09:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+import React, { useEffect, useState } from 'react';
+import { View, ActivityIndicator, StyleSheet, Image, Text } from 'react-native';
+import { PointAnnotation } from '@rnmapbox/maps';
+import axios from 'axios';
+import { GoogleObject, BusDataResponse } from '../interfaces/ShuttleBusLocation'; // Import the types
+
+const ShuttleBusTracker: React.FC = () => {
+    const [busData, setBusData] = useState<GoogleObject | null>(null);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [error, setError] = useState<string | null>(null);
+
+    const fetchBusData = async () => {
+        try {
+            // Make a POST request to get the bus data
+            const postResponse = await axios.post<BusDataResponse>(
+                'https://shuttle.concordia.ca/concordiabusmap/WebService/GService.asmx/GetGoogleObject',
+                {},
+                {
+                    headers: {
+                        'Host': 'shuttle.concordia.ca',
+                        'Content-Length': '0',
+                        'Content-Type': 'application/json; charset=UTF-8',
+                    }
+                }
+            );
+
+            // Set the bus data to state
+            setBusData(postResponse.data.d);
+            setLoading(false);
+        } catch (err) {
+            console.error('Error fetching bus data:', err);
+            setError('Error fetching bus data');
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchBusData();
+
+        // Set up an interval to fetch data every 15 seconds
+        const interval = setInterval(fetchBusData, 15000);
+
+        // Cleanup interval on component unmount
+        return () => clearInterval(interval);
+    }, []);
+
+    if (loading) {
+        return (
+            <View style={styles.container}>
+                <ActivityIndicator size="large" color="#0000ff" />
+            </View>
+        );
+    }
+
+    if (error) {
+        return (
+            <View style={styles.container}>
+                <Text style={styles.errorText}>Error: {error}</Text>
+            </View>
+        );
+    }
+
+    if (!busData || !busData.Points) {
+        return null;
+    }
+
+    // Filter the Points array to include only buses (IDs starting with "BUS")
+    const busPoints = busData.Points.filter((point) => point.ID.startsWith('BUS'));
+
+    return (
+        <>
+            {busPoints.map((point) => (
+                <PointAnnotation
+                    key={point.ID}
+                    id={point.ID}
+                    coordinate={[point.Longitude, point.Latitude]}
+                >
+                    <Image
+                        source={require('../resources/images/ShuttleBus-Icon.png')}
+                        style={{ width: 100, height: 40 }}
+                    />
path: src/components/ShuttleBusTracker.tsx
user: login: lawrence122
body: For future icons, we should look them up in the a react or expo package and import them as vector-icons.
You can look them up on [here](https://icons.expo.fyi/Index). I remember seeing some bus ones. You could use #912338 as the bus color
created_at: 2025-03-09T14:20:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,84 @@
+import React, { useEffect, useState } from 'react';
+import { View, ActivityIndicator, StyleSheet, Image, Text } from 'react-native';
+import { PointAnnotation } from '@rnmapbox/maps';
+import axios from 'axios';
+import { GoogleObject, BusDataResponse } from '../interfaces/ShuttleBusLocation'; // Import the types
path: src/components/ShuttleBusTracker.tsx
user: login: Julia-Trinh
body: remove useless comments
created_at: 2025-03-09T19:45:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+import React, { useEffect, useState } from 'react';
+import { View, ActivityIndicator, StyleSheet, Image, Text } from 'react-native';
+import { PointAnnotation } from '@rnmapbox/maps';
+import axios from 'axios';
+import { GoogleObject, BusDataResponse } from '../interfaces/ShuttleBusLocation'; // Import the types
+
+const ShuttleBusTracker: React.FC = () => {
+    const [busData, setBusData] = useState<GoogleObject | null>(null);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [error, setError] = useState<string | null>(null);
+
+    const fetchBusData = async () => {
+        try {
+            // Make a POST request to get the bus data
+            const postResponse = await axios.post<BusDataResponse>(
+                'https://shuttle.concordia.ca/concordiabusmap/WebService/GService.asmx/GetGoogleObject',
+                {},
+                {
+                    headers: {
+                        'Host': 'shuttle.concordia.ca',
+                        'Content-Length': '0',
+                        'Content-Type': 'application/json; charset=UTF-8',
+                    }
+                }
+            );
+
+            // Set the bus data to state
+            setBusData(postResponse.data.d);
+            setLoading(false);
+        } catch (err) {
+            console.error('Error fetching bus data:', err);
+            setError('Error fetching bus data');
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchBusData();
+
+        // Set up an interval to fetch data every 15 seconds
+        const interval = setInterval(fetchBusData, 15000);
+
+        // Cleanup interval on component unmount
+        return () => clearInterval(interval);
+    }, []);
+
+    if (loading) {
+        return (
+            <View style={styles.container}>
+                <ActivityIndicator size="large" color="#0000ff" />
+            </View>
+        );
+    }
+
+    if (error) {
+        return (
+            <View style={styles.container}>
+                <Text style={styles.errorText}>Error: {error}</Text>
+            </View>
+        );
+    }
+
+    if (!busData || !busData.Points) {
+        return null;
+    }
+
+    // Filter the Points array to include only buses (IDs starting with "BUS")
+    const busPoints = busData.Points.filter((point) => point.ID.startsWith('BUS'));
+
+    return (
+        <>
+            {busPoints.map((point) => (
+                <PointAnnotation
+                    key={point.ID}
+                    id={point.ID}
+                    coordinate={[point.Longitude, point.Latitude]}
+                >
+                    <Image
+                        source={require('../resources/images/ShuttleBus-Icon.png')}
+                        style={{ width: 100, height: 40 }}
+                    />
path: src/components/ShuttleBusTracker.tsx
user: login: Bou1103
body: done i changed it with a new one from the link you provided (check updated screenshot of PR)
created_at: 2025-03-10T07:20:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,84 @@
+import React, { useEffect, useState } from 'react';
+import { View, ActivityIndicator, StyleSheet, Image, Text } from 'react-native';
+import { PointAnnotation } from '@rnmapbox/maps';
+import axios from 'axios';
+import { GoogleObject, BusDataResponse } from '../interfaces/ShuttleBusLocation'; // Import the types
path: src/components/ShuttleBusTracker.tsx
user: login: Bou1103
body: done, just kept the comments that explain api use 
created_at: 2025-03-10T07:20:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,101 @@
+import React, { useEffect, useState } from 'react';
+import { View, ActivityIndicator, StyleSheet, Image, Text } from 'react-native';
+import { PointAnnotation } from '@rnmapbox/maps';
+import axios from 'axios';
+import { GoogleObject, BusDataResponse } from '../interfaces/ShuttleBusLocation'; // Import the types
+
+const ShuttleBusTracker: React.FC = () => {
+    const [busData, setBusData] = useState<GoogleObject | null>(null);
+    const [loading, setLoading] = useState<boolean>(true);
+    const [error, setError] = useState<string | null>(null);
+
+    const fetchBusData = async () => {
+        try {
+            // Make a POST request to get the bus data
+            const postResponse = await axios.post<BusDataResponse>(
+                'https://shuttle.concordia.ca/concordiabusmap/WebService/GService.asmx/GetGoogleObject',
+                {},
+                {
+                    headers: {
+                        'Host': 'shuttle.concordia.ca',
+                        'Content-Length': '0',
+                        'Content-Type': 'application/json; charset=UTF-8',
+                    }
+                }
+            );
+
+            // Set the bus data to state
+            setBusData(postResponse.data.d);
+            setLoading(false);
+        } catch (err) {
+            console.error('Error fetching bus data:', err);
+            setError('Error fetching bus data');
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        fetchBusData();
+
+        // Set up an interval to fetch data every 15 seconds
+        const interval = setInterval(fetchBusData, 15000);
+
+        // Cleanup interval on component unmount
+        return () => clearInterval(interval);
+    }, []);
+
+    if (loading) {
+        return (
+            <View style={styles.container}>
+                <ActivityIndicator size="large" color="#0000ff" />
+            </View>
+        );
+    }
+
+    if (error) {
+        return (
+            <View style={styles.container}>
+                <Text style={styles.errorText}>Error: {error}</Text>
+            </View>
+        );
+    }
+
+    if (!busData || !busData.Points) {
+        return null;
+    }
+
+    // Filter the Points array to include only buses (IDs starting with "BUS")
+    const busPoints = busData.Points.filter((point) => point.ID.startsWith('BUS'));
+
+    return (
+        <>
+            {busPoints.map((point) => (
+                <PointAnnotation
+                    key={point.ID}
+                    id={point.ID}
+                    coordinate={[point.Longitude, point.Latitude]}
+                >
+                    <Image
+                        source={require('../resources/images/ShuttleBus-Icon.png')}
+                        style={{ width: 100, height: 40 }}
+                    />
+                </PointAnnotation>
+            ))}
+        </>
+    );
+};
+
+// Styles
+const styles = StyleSheet.create({
+    container: {
+        flex: 1,
+        justifyContent: 'center',
+        alignItems: 'center',
+    },
+    errorText: {
+        fontSize: 18,
+        color: 'red',
+    },
+});
path: src/components/ShuttleBusTracker.tsx
user: login: Bou1103
body: actually i deleted the styles section cause these were for the error message when fetching is failing so useless to have style for that text
created_at: 2025-03-10T07:33:40Z
author_association: COLLABORATOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-09T22:14:28Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=cristina-trofimov_concordia_campus_navigation&pullRequest=150) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=cristina-trofimov_concordia_campus_navigation&pullRequest=150&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=cristina-trofimov_concordia_campus_navigation&pullRequest=150)


author_association: NONE