import os
import base64
import requests
import gradio as gr
import pandas as pd
import folium
from src.gpx_parser import parse_gpx_file, haversine, save_enhanced_gpx
import src.llm as llm
import src.rag as rag
import src.database as db
# Initialize cache and temp folders and SQLite database
os.makedirs("./temp", exist_ok=True)
db.init_db()
# Preloaded route path — resolve relative to this file for cross-platform compatibility
_APP_DIR = os.path.dirname(os.path.abspath(__file__))
PRELOADED_ROUTE_PATH = os.path.join(_APP_DIR, "Routes", "track_5-14724236830.gpx")
# Bundled pre-parsed cache for the Trento track (includes POIs — avoids Overpass API on HF)
PRELOADED_CACHE_PATH = os.path.join(_APP_DIR, "src", "data", "trento_route_cache.json")
MAP_HTML_INITIALIZER = """
"""
MAP_INIT_JS = r"""
() => {
// Hide communication textboxes instantly
var hideElements = function() {
['hiker-pos-coords', 'live-gps-coords', 'route-data-json'].forEach(function(id) {
var el = document.getElementById(id);
if (el) el.style.setProperty("display", "none", "important");
});
};
hideElements();
var hideInterval = setInterval(hideElements, 50);
setTimeout(function() { clearInterval(hideInterval); }, 4000);
// 1. Dynamically append Leaflet CSS
if (!document.getElementById("leaflet-css")) {
var link = document.createElement("link");
link.id = "leaflet-css";
link.rel = "stylesheet";
link.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
document.head.appendChild(link);
}
// 2. Dynamically append Leaflet JS
if (!document.getElementById("leaflet-js")) {
var script = document.createElement("script");
script.id = "leaflet-js";
script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
document.head.appendChild(script);
}
// 3. Wait for Leaflet to load and initialize map
var checkExist = setInterval(function() {
if (typeof L !== 'undefined' && L.map) {
var mapContainer = document.getElementById("trailhead-leaflet-map");
if (!mapContainer) return; // Wait for Gradio to mount the container
clearInterval(checkExist);
if (window.myLeafletMap) return; // Already initialized
var map = L.map("trailhead-leaflet-map").setView([46.0734974, 11.1717214], 13);
window.myLeafletMap = map;
if (typeof L.TileLayer.OfflineFirst === 'undefined') {
L.TileLayer.OfflineFirst = L.TileLayer.extend({
createTile: function(coords, done) {
var tile = L.TileLayer.prototype.createTile.call(this, coords, done);
L.DomEvent.on(tile, 'error', function() {
var osmUrl = 'https://tile.openstreetmap.org/' + coords.z + '/' + coords.x + '/' + coords.y + '.png';
if (tile.src !== osmUrl) {
tile.src = osmUrl;
}
});
return tile;
}
});
}
window.activeTileLayer = new L.TileLayer.OfflineFirst('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
}).addTo(map);
window.mapLayers = L.layerGroup().addTo(map);
window.hikerMarker = null;
// Define window.routeDataChangeHandler
window.routeDataChangeHandler = function(routeJson) {
if (!routeJson) return;
try {
var data = JSON.parse(routeJson);
var map = window.myLeafletMap;
if (!map) return;
var tilesDir = data.tiles_dir || '';
if (tilesDir && window.activeTileLayer) {
map.removeLayer(window.activeTileLayer);
var localUrl = '/file=' + tilesDir + '/{z}/{x}/{y}.png';
window.activeTileLayer = new L.TileLayer.OfflineFirst(localUrl, {
maxZoom: 16,
minZoom: 13,
attribution: '© OpenStreetMap'
}).addTo(map);
}
if (window.mapLayers) {
window.mapLayers.clearLayers();
}
if (window.hikerMarker) {
window.hikerMarker.remove();
window.hikerMarker = null;
}
window.routePolyline = null;
var points = data.points || [];
var checkpoints = data.checkpoints || [];
var pois = data.pois || [];
if (points.length === 0) return;
var latlngs = points.map(p => [p.lat, p.lon]);
var polyline = L.polyline(latlngs, {
color: "#f59e0b",
weight: 5,
opacity: 0.85
}).addTo(window.mapLayers);
window.routePolyline = polyline;
map.fitBounds(polyline.getBounds());
checkpoints.forEach(cp => {
var color = "cadetblue";
if (cp.name === "Start") color = "green";
else if (cp.name === "End") color = "red";
var popupText = `
${cp.name}
Distance: ${cp.cum_dist.toFixed(2)} km
Elevation: ${cp.ele.toFixed(1)} m
`;
L.circleMarker([cp.lat, cp.lon], {
radius: cp.name === "Start" || cp.name === "End" ? 8 : 6,
fillColor: color,
color: "#ffffff",
weight: 1.5,
fillOpacity: 0.9
})
.bindPopup(popupText)
.addTo(window.mapLayers);
});
pois.forEach(poi => {
var color = "purple";
var type = poi.type;
if (["drinking_water", "water_point", "fountain"].includes(type)) {
color = "#3b82f6";
} else if (type === "spring") {
color = "#60a5fa";
} else if (["alpine_hut", "wilderness_hut"].includes(type)) {
color = "#047857";
} else if (type === "camp_site") {
color = "#f97316";
} else if (type === "shelter") {
color = "#10b981";
} else if (type === "viewpoint") {
color = "#a855f7";
} else if (type === "peak") {
color = "#7c3aed";
} else if (type === "phone") {
color = "#ef4444";
}
var popupText = `
${poi.name}
Type: ${type.replace(/_/g, ' ').toUpperCase()}
Distance to Route: ${poi.distance.toFixed(1)} m
`;
L.circleMarker([poi.lat, poi.lon], {
radius: 5,
fillColor: color,
color: "#ffffff",
weight: 1.2,
fillOpacity: 0.95
})
.bindPopup(popupText)
.addTo(window.mapLayers);
});
} catch(e) {
console.error("Error drawing route:", e);
}
};
// Define window.updateHikerPosHandler
window.updateHikerPosHandler = function(coords) {
if (!coords) return;
try {
var data = JSON.parse(coords);
var map = window.myLeafletMap;
if (!map) return;
var pos = [data.lat, data.lon];
if (!window.hikerMarker) {
window.hikerMarker = L.circleMarker(pos, {
radius: 9,
fillColor: "#ef4444",
color: "#ffffff",
weight: 2.5,
fillOpacity: 1.0
}).addTo(map);
} else {
window.hikerMarker.setLatLng(pos);
}
var popupText = `
Current position
Distance Walked: ${data.cum_dist.toFixed(2)} km
Altitude: ${data.ele.toFixed(1)} m
`;
window.hikerMarker.bindPopup(popupText);
if (window.routePolyline) {
var routeBounds = window.routePolyline.getBounds();
var combinedBounds = routeBounds.extend(pos);
map.fitBounds(combinedBounds, { padding: [50, 50] });
} else {
map.panTo(pos);
}
} catch(e) {
console.error("Error updating hiker position:", e);
}
};
// Trigger handlers immediately with any pending data
if (window.pendingRouteData) {
window.routeDataChangeHandler(window.pendingRouteData);
}
if (window.pendingHikerCoords) {
window.updateHikerPosHandler(window.pendingHikerCoords);
}
// --- Live GPS Tracking Logic ---
window.gpsWatchId = null;
window.lastGpsTime = -99999; // Allow immediate first fix
window.liveGpsMarker = null; // Blue pulsing dot (user location)
window.liveAccCircle = null; // Accuracy radius circle
window.liveGpsActive = false;
// Inject pulsing CSS for the live GPS dot
if (!document.getElementById('gps-pulse-style')) {
var style = document.createElement('style');
style.id = 'gps-pulse-style';
style.textContent = `
@keyframes gpsPulse {
0% { transform: scale(1); opacity: 1; }
70% { transform: scale(2.5); opacity: 0; }
100% { transform: scale(1); opacity: 0; }
}
.gps-dot-icon {
position: relative;
width: 18px; height: 18px;
}
.gps-dot-icon .pulse {
position: absolute;
top: 0; left: 0;
width: 18px; height: 18px;
border-radius: 50%;
background: rgba(66,133,244,0.4);
animation: gpsPulse 1.6s ease-out infinite;
}
.gps-dot-icon .core {
position: absolute;
top: 3px; left: 3px;
width: 12px; height: 12px;
border-radius: 50%;
background: #4285f4;
border: 2px solid #fff;
box-shadow: 0 0 6px rgba(66,133,244,0.8);
}
`;
document.head.appendChild(style);
}
window.updateLiveGPSMarker = function(lat, lon, accuracy) {
var map = window.myLeafletMap;
if (!map) return;
var pos = [lat, lon];
if (!window.liveGpsMarker) {
// Create the pulsing blue icon
var gpsDotIcon = L.divIcon({
className: '',
html: '',
iconSize: [18, 18],
iconAnchor: [9, 9]
});
window.liveGpsMarker = L.marker(pos, { icon: gpsDotIcon, zIndexOffset: 1000 })
.bindPopup('📡 Your Live Location')
.addTo(map);
} else {
window.liveGpsMarker.setLatLng(pos);
}
// Update/create accuracy circle
if (accuracy && accuracy > 0) {
if (!window.liveAccCircle) {
window.liveAccCircle = L.circle(pos, {
radius: accuracy,
color: '#4285f4',
fillColor: '#4285f4',
fillOpacity: 0.08,
weight: 1.5,
dashArray: '4,4'
}).addTo(map);
} else {
window.liveAccCircle.setLatLng(pos);
window.liveAccCircle.setRadius(accuracy);
}
}
// Google Maps style: keep user centered, show route context
if (window.routePolyline) {
var routeBounds = window.routePolyline.getBounds();
var combinedBounds = routeBounds.extend(pos);
map.fitBounds(combinedBounds, { padding: [60, 60], maxZoom: 17 });
} else {
map.setView(pos, Math.max(map.getZoom(), 15));
}
};
window.stopLiveGPSMarker = function() {
if (window.liveGpsMarker) {
window.liveGpsMarker.remove();
window.liveGpsMarker = null;
}
if (window.liveAccCircle) {
window.liveAccCircle.remove();
window.liveAccCircle = null;
}
};
window.toggleLiveGPS = function(enabled) {
window.liveGpsActive = !!enabled;
if (!enabled) {
if (window.gpsWatchId !== null) {
navigator.geolocation.clearWatch(window.gpsWatchId);
window.gpsWatchId = null;
console.log("Live GPS tracking stopped.");
}
window.stopLiveGPSMarker();
return;
}
if (enabled && "geolocation" in navigator) {
if (window.gpsWatchId !== null) {
console.log("Live GPS tracking already active.");
return;
}
window.lastGpsTime = -99999; // ensure first fix goes through
console.log("Requesting Live GPS tracking...");
// Immediate one-shot to show position fast
navigator.geolocation.getCurrentPosition(
function(position) {
var lat = position.coords.latitude;
var lon = position.coords.longitude;
var ele = position.coords.altitude || 0.0;
var acc = position.coords.accuracy;
window.updateLiveGPSMarker(lat, lon, acc);
var coords = { lat: lat, lon: lon, ele: ele, acc: acc };
var textbox = document.querySelector("#live-gps-coords textarea");
if (!textbox) textbox = document.querySelector("#live-gps-coords input");
if (textbox) {
var ts = '_t=' + Date.now(); // force unique value to trigger .change()
textbox.value = JSON.stringify(Object.assign(coords, { _ts: Date.now() }));
textbox.dispatchEvent(new Event("input", { bubbles: true }));
textbox.dispatchEvent(new Event("change", { bubbles: true }));
}
window.lastGpsTime = Date.now();
},
function(e) { console.warn("Quick GPS fix failed:", e.message); },
{ enableHighAccuracy: true, timeout: 8000, maximumAge: 0 }
);
// Continuous watch for ongoing updates
window.gpsWatchId = navigator.geolocation.watchPosition(
function(position) {
var now = Date.now();
if (now - window.lastGpsTime < 1000) return;
window.lastGpsTime = now;
var lat = position.coords.latitude;
var lon = position.coords.longitude;
var ele = position.coords.altitude || 0.0;
var acc = position.coords.accuracy;
// 1. Update map marker directly in JS (instant, no Python round-trip)
window.updateLiveGPSMarker(lat, lon, acc);
// 2. Send to Python backend for HUD / alerts update
var coords = { lat: lat, lon: lon, ele: ele, acc: acc, _ts: now };
var textbox = document.querySelector("#live-gps-coords textarea");
if (!textbox) textbox = document.querySelector("#live-gps-coords input");
if (textbox) {
textbox.value = JSON.stringify(coords);
textbox.dispatchEvent(new Event("input", { bubbles: true }));
textbox.dispatchEvent(new Event("change", { bubbles: true }));
}
},
function(error) {
console.error("GPS Watch Error:", error);
if (error.code === 1) {
alert("Location permission denied. Please allow location access in your browser settings.");
} else {
console.warn("GPS error (code " + error.code + "): " + error.message);
}
},
{
enableHighAccuracy: true,
maximumAge: 1000,
timeout: 15000
}
);
} else if (enabled) {
alert("Geolocation is not supported by this browser or not running in a secure context (use localhost or HTTPS).");
}
};
}
}, 100);
}
"""
def generate_elevation_plot(points, current_idx=None):
"""
Generate an offline-ready Plotly elevation profile chart.
If current_idx is provided, displays a vertical line or marker for the hiker's current position.
"""
try:
import plotly.graph_objects as go
except ImportError:
return None
if not points:
return None
x = [p["cum_dist"] / 1000.0 for p in points]
y = [p["ele"] for p in points]
fig = go.Figure()
# Area chart for elevation
fig.add_trace(go.Scatter(
x=x,
y=y,
mode='lines',
fill='tozeroy',
line=dict(color='#f59e0b', width=2.5),
fillcolor='rgba(245,158,11,0.15)',
name='Elevation'
))
# Hiker's current position dot
if current_idx is not None and 0 <= current_idx < len(points):
hiker_pt = points[current_idx]
h_x = hiker_pt["cum_dist"] / 1000.0
h_y = hiker_pt["ele"]
# Add hiker position dot
fig.add_trace(go.Scatter(
x=[h_x],
y=[h_y],
mode='markers',
marker=dict(color='#ef4444', size=12, symbol='circle', line=dict(color='#ffffff', width=2)),
name='You'
))
# Add vertical line
fig.add_vline(x=h_x, line_width=1, line_dash="dash", line_color="#ef4444")
fig.update_layout(
title="🏔️ ELEVATION PROFILE",
xaxis_title="Distance (km)",
yaxis_title="Elevation (m)",
plot_bgcolor='#0c1014',
paper_bgcolor='#0c1014',
font=dict(color='#f59e0b', family='Share Tech Mono, monospace'),
margin=dict(l=50, r=20, t=50, b=50),
hovermode="x unified",
showlegend=False,
height=280
)
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(245,158,11,0.1)')
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(245,158,11,0.1)')
return fig
EMERGENCY_CARD = """
## 🚨 IMMEDIATE BACKCOUNTRY EMERGENCY CARD (OFFLINE)
If you encounter a medical crisis with no cellular signal, follow these basic steps:
1. **Severe Bleeding:** Apply direct pressure with clean dressing. Elevate limb. Use tourniquet if blood is spurting.
2. **Hypothermia:** Wrap in windproof shell/sleeping bag. Replace wet clothes. Provide warm sweet drinks.
3. **Heat Stroke:** Move to shade. Actively cool by wetting skin and fanning. Sip cool water.
4. **Altitude Illness (AMS/HAPE/HACE):** Descend immediately. Do not ascend. Administer oxygen if available.
5. **Ankle Sprain (R.I.C.E):** Rest the joint. Ice or apply cold pack. Compress with elastic bandage. Elevate limb.
*Disclaimer: This guide is for offline reference only. Always carry a PLB/satellite communicator on remote trails.*
"""
def generate_folium_map(points, checkpoints, pois, hiker_pos=None):
"""
Generate interactive folium map rendering track, checkpoints, POIs, and current hiker pos.
"""
if not points:
m = folium.Map(location=[46.0734974, 11.1717214], zoom_start=13)
return m._repr_html_()
# Center map on current hiker position or middle of track
if hiker_pos:
center_lat, center_lon = hiker_pos["lat"], hiker_pos["lon"]
zoom_val = 15
else:
mid_idx = len(points) // 2
center_lat, center_lon = points[mid_idx]["lat"], points[mid_idx]["lon"]
zoom_val = 14
m = folium.Map(location=[center_lat, center_lon], zoom_start=zoom_val)
# Draw track polyline
locations = [(p["lat"], p["lon"]) for p in points]
folium.PolyLine(locations, color="#f59e0b", weight=5, opacity=0.85).add_to(m)
# Draw checkpoints
for cp in checkpoints:
name = cp["name"]
lat = cp["lat"]
lon = cp["lon"]
ele = cp["ele"]
dist = cp["cum_dist"]
if name == "Start":
color = "green"
icon = "play"
elif name == "End":
color = "red"
icon = "flag"
else:
color = "cadetblue"
icon = "info-sign"
popup_text = f"""
{name}
Distance: {dist:.2f} km
Elevation: {ele:.1f} m
"""
folium.Marker(
location=[lat, lon],
popup=popup_text,
tooltip=name,
icon=folium.Icon(color=color, icon=icon)
).add_to(m)
# Draw POIs
for poi in pois:
poi_type = poi["type"]
icon_color = "purple"
icon_name = "info-sign"
# Color coding:
# - drinking_water / water_point / fountain -> blue / glass
# - spring -> lightblue / tint
# - alpine_hut / wilderness_hut -> darkgreen / home
# - camp_site -> orange / fire
# - shelter -> green / leaf
# - viewpoint -> purple / camera
# - peak -> darkpurple / flag
# - phone -> red / phone
if poi_type in ["drinking_water", "water_point", "fountain"]:
icon_color = "blue"
icon_name = "glass"
elif poi_type == "spring":
icon_color = "lightblue"
icon_name = "tint"
elif poi_type in ["alpine_hut", "wilderness_hut"]:
icon_color = "darkgreen"
icon_name = "home"
elif poi_type == "camp_site":
icon_color = "orange"
icon_name = "fire"
elif poi_type == "shelter":
icon_color = "green"
icon_name = "leaf"
elif poi_type == "viewpoint":
icon_color = "purple"
icon_name = "camera"
elif poi_type == "peak":
icon_color = "darkpurple"
icon_name = "flag"
elif poi_type == "phone":
icon_color = "red"
icon_name = "phone"
popup_text = f"""
{poi['name']}
Type: {poi_type.replace('_', ' ').title()}
Distance to Route: {poi['distance']:.1f} m
"""
folium.Marker(
location=[poi["lat"], poi["lon"]],
popup=popup_text,
tooltip=poi['name'],
icon=folium.Icon(color=icon_color, icon=icon_name)
).add_to(m)
return m._repr_html_()
def get_map_iframe(map_html):
"""
Helper to bundle raw HTML into a secure, sandboxed base64 data URI iframe.
"""
injected_js = """
"""
if "