ayushsahu45 commited on
Commit
bd2b21f
·
verified ·
1 Parent(s): fa8a0ea

Upload 4 files

Browse files
src/lib/notifications.ts ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export async function requestNotificationPermission(): Promise<boolean> {
2
+ if (!('Notification' in window)) {
3
+ console.warn('This browser does not support notifications');
4
+ return false;
5
+ }
6
+
7
+ if (Notification.permission === 'granted') {
8
+ return true;
9
+ }
10
+
11
+ if (Notification.permission !== 'denied') {
12
+ const permission = await Notification.requestPermission();
13
+ return permission === 'granted';
14
+ }
15
+
16
+ return false;
17
+ }
18
+
19
+ export function showNotification(title: string, options?: NotificationOptions): Notification | null {
20
+ if (!('Notification' in window) || Notification.permission !== 'granted') {
21
+ return null;
22
+ }
23
+
24
+ const notification = new Notification(title, {
25
+ icon: '/favicon.ico',
26
+ badge: '/favicon.ico',
27
+ ...options
28
+ });
29
+
30
+ setTimeout(() => notification.close(), 5000);
31
+
32
+ return notification;
33
+ }
34
+
35
+ export function showWeatherAlert(weather: string, temp: number): void {
36
+ const badWeather = ['Rain', 'Drizzle', 'Snow', 'Thunderstorm', 'Fog', 'Mist'];
37
+ if (!badWeather.includes(weather)) return;
38
+
39
+ showNotification('Weather Alert', {
40
+ body: `${weather} detected! Temperature: ${Math.round(temp - 273.15)}°C. Drive safely!`,
41
+ tag: 'weather-alert',
42
+ requireInteraction: false
43
+ });
44
+ }
45
+
46
+ export function showRouteAlert(distance: string, duration: string, safetyScore: number): void {
47
+ const emoji = safetyScore >= 70 ? '✅' : safetyScore >= 40 ? '⚠️' : '🚨';
48
+ showNotification('Route Ready', {
49
+ body: `${emoji} ${distance} • ${duration} • Safety: ${safetyScore}%`,
50
+ tag: 'route-ready'
51
+ });
52
+ }
53
+
54
+ export function showIncidentAlert(incidentType: string, severity: string): void {
55
+ showNotification('Incident Nearby', {
56
+ body: `${incidentType} reported - ${severity} severity. Please drive carefully.`,
57
+ tag: 'incident-alert',
58
+ requireInteraction: true
59
+ });
60
+ }
src/lib/orsApi.ts ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from 'axios';
2
+
3
+ const API_KEY = import.meta.env.VITE_OPENROUTESERVICE_API_KEY || '';
4
+
5
+ export async function getORSRoute(
6
+ start: [number, number],
7
+ end: [number, number],
8
+ profile: string = 'driving-car'
9
+ ): Promise<any> {
10
+ const res = await axios.post(
11
+ `https://api.openrouteservice.org/v2/directions/${profile}`,
12
+ {
13
+ coordinates: [
14
+ [start[0], start[1]],
15
+ [end[0], end[1]]
16
+ ]
17
+ },
18
+ {
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ 'Authorization': API_KEY
22
+ },
23
+ params: {
24
+ geometries: 'geojson',
25
+ overview: 'full',
26
+ steps: true
27
+ }
28
+ }
29
+ );
30
+
31
+ return res.data;
32
+ }
33
+
34
+ export async function getORSRouteWithWaypoints(
35
+ coordinates: [number, number][],
36
+ profile: string = 'driving-car'
37
+ ): Promise<any> {
38
+ const res = await axios.post(
39
+ `https://api.openrouteservice.org/v2/directions/${profile}`,
40
+ {
41
+ coordinates: coordinates.map(([lng, lat]) => [lng, lat])
42
+ },
43
+ {
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ 'Authorization': API_KEY
47
+ },
48
+ params: {
49
+ geometries: 'geojson',
50
+ overview: 'full',
51
+ steps: true
52
+ }
53
+ }
54
+ );
55
+
56
+ return res.data;
57
+ }
58
+
59
+ export async function searchLocation(query: string) {
60
+ const res = await axios.get(
61
+ `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=10`
62
+ );
63
+ return res.data;
64
+ }
65
+
66
+ export async function reverseGeocode(lat: number, lon: number) {
67
+ const res = await axios.get(
68
+ `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}`
69
+ );
70
+ return res.data;
71
+ }
72
+
73
+ export function decodePolyline(encoded: string): [number, number][] {
74
+ const poly: [number, number][] = [];
75
+ let index = 0;
76
+ const len = encoded.length;
77
+ let lat = 0;
78
+ let lng = 0;
79
+
80
+ while (index < len) {
81
+ let b;
82
+ let shift = 0;
83
+ let result = 0;
84
+
85
+ do {
86
+ b = encoded.charCodeAt(index++) - 63;
87
+ result |= (b & 0x1f) << shift;
88
+ shift += 5;
89
+ } while (b >= 0x20);
90
+
91
+ const dlat = (result & 1) !== 0 ? ~(result >> 1) : (result >> 1);
92
+ lat += dlat;
93
+
94
+ shift = 0;
95
+ result = 0;
96
+
97
+ do {
98
+ b = encoded.charCodeAt(index++) - 63;
99
+ result |= (b & 0x1f) << shift;
100
+ shift += 5;
101
+ } while (b >= 0x20);
102
+
103
+ const dlng = (result & 1) !== 0 ? ~(result >> 1) : (result >> 1);
104
+ lng += dlng;
105
+
106
+ poly.push([lat / 1e5, lng / 1e5]);
107
+ }
108
+
109
+ return poly;
110
+ }
111
+
112
+ export function coordsToLeaflet(coords: [number, number][]): [number, number][] {
113
+ return coords.map(([lng, lat]) => [lat, lng] as [number, number]);
114
+ }
src/lib/routeCache.ts ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ interface CacheEntry<T> {
2
+ data: T;
3
+ timestamp: number;
4
+ }
5
+
6
+ const cache = new Map<string, CacheEntry<unknown>>();
7
+ const DEFAULT_TTL = 3 * 60 * 1000; // 3 minutes
8
+
9
+ export function getCached<T>(key: string): T | null {
10
+ const entry = cache.get(key) as CacheEntry<T> | undefined;
11
+ if (!entry) return null;
12
+
13
+ const age = Date.now() - entry.timestamp;
14
+ if (age > DEFAULT_TTL) {
15
+ cache.delete(key);
16
+ return null;
17
+ }
18
+
19
+ return entry.data;
20
+ }
21
+
22
+ export function setCache<T>(key: string, data: T, ttl = DEFAULT_TTL): void {
23
+ if (!data || (Array.isArray(data) && data.length === 0)) {
24
+ return;
25
+ }
26
+ cache.set(key, { data, timestamp: Date.now() });
27
+ }
28
+
29
+ export function clearCache(): void {
30
+ cache.clear();
31
+ }
32
+
33
+ export function getCacheKey(prefix: string, ...params: (string | number | boolean)[]): string {
34
+ return `${prefix}:${params.join(':')}`;
35
+ }
36
+
37
+ export function generateRouteCacheKey(start: [number, number], end: [number, number], profile: string): string {
38
+ return getCacheKey('route', start.join(','), end.join(','), profile);
39
+ }
40
+
41
+ export function generateGeocodeCacheKey(query: string): string {
42
+ return getCacheKey('geocode', query.toLowerCase().trim());
43
+ }
44
+
45
+ export function generateWeatherCacheKey(lat: number, lon: number): string {
46
+ return getCacheKey('weather', lat.toFixed(3), lon.toFixed(3));
47
+ }
src/lib/voiceNavigation.ts ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let utterance: SpeechSynthesisUtterance | null = null;
2
+
3
+ export function speak(text: string): void {
4
+ if (!('speechSynthesis' in window)) {
5
+ console.warn('Speech synthesis not supported');
6
+ return;
7
+ }
8
+
9
+ window.speechSynthesis.cancel();
10
+
11
+ utterance = new SpeechSynthesisUtterance(text);
12
+ utterance.rate = 1.0;
13
+ utterance.pitch = 1.0;
14
+ utterance.volume = 1.0;
15
+
16
+ const voices = window.speechSynthesis.getVoices();
17
+ const englishVoice = voices.find(v => v.lang.startsWith('en') && v.name.includes('Google')) ||
18
+ voices.find(v => v.lang.startsWith('en'));
19
+ if (englishVoice) {
20
+ utterance.voice = englishVoice;
21
+ }
22
+
23
+ window.speechSynthesis.speak(utterance);
24
+ }
25
+
26
+ export function stopSpeaking(): void {
27
+ if ('speechSynthesis' in window) {
28
+ window.speechSynthesis.cancel();
29
+ }
30
+ }
31
+
32
+ export function isSpeaking(): boolean {
33
+ if (!('speechSynthesis' in window)) return false;
34
+ return window.speechSynthesis.speaking;
35
+ }
36
+
37
+ export function getAvailableVoices(): SpeechSynthesisVoice[] {
38
+ if (!('speechSynthesis' in window)) return [];
39
+ return window.speechSynthesis.getVoices().filter(v => v.lang.startsWith('en'));
40
+ }
41
+
42
+ export function formatDirectionForSpeech(instruction: string): string {
43
+ return instruction
44
+ .replace(/onto/gi, 'onto')
45
+ .replace(/Continue/gi, 'Continue')
46
+ .replace(/Turn left/gi, 'Turn left')
47
+ .replace(/Turn right/gi, 'Turn right')
48
+ .replace(/Slight left/gi, 'Bear left')
49
+ .replace(/Slight right/gi, 'Bear right')
50
+ .replace(/km/g, 'kilometers')
51
+ .replace(/m/g, 'meters')
52
+ .trim();
53
+ }
54
+
55
+ export function speakDirection(step: { maneuver: { type?: string; modifier?: string; instruction?: string; name?: string }; distance?: number }): void {
56
+ let text = '';
57
+
58
+ if (step.maneuver.instruction) {
59
+ text = formatDirectionForSpeech(step.maneuver.instruction);
60
+ } else {
61
+ const type = step.maneuver.type || '';
62
+ const modifier = step.maneuver.modifier || '';
63
+ const name = step.maneuver.name || '';
64
+ text = `${type} ${modifier}`.trim();
65
+ if (name) text += ` onto ${name}`;
66
+ }
67
+
68
+ if (step.distance && step.distance > 0) {
69
+ const distText = step.distance >= 1000
70
+ ? `${(step.distance / 1000).toFixed(1)} kilometers`
71
+ : `${Math.round(step.distance)} meters`;
72
+ text += `. Then go ${distText}.`;
73
+ }
74
+
75
+ speak(text);
76
+ }