238 lines
9.7 KiB
Python
238 lines
9.7 KiB
Python
#!/usr/bin/env python3
|
|
import re
|
|
|
|
def main():
|
|
src = 'apps/dashboard/tourism.html'
|
|
with open(src, 'r', encoding='utf-8') as f:
|
|
html = f.read()
|
|
|
|
# 1. Add HUD CSS
|
|
hud_css = """
|
|
/* Sovereign In-App Navigation HUD */
|
|
.sovereign-nav-hud {
|
|
position: absolute;
|
|
top: 80px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 70;
|
|
background: rgba(6, 29, 66, 0.96);
|
|
backdrop-filter: blur(25px);
|
|
-webkit-backdrop-filter: blur(25px);
|
|
border: 2px solid #06b6d4;
|
|
border-radius: 18px;
|
|
padding: 14px 20px;
|
|
color: #fff;
|
|
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.45);
|
|
width: 480px;
|
|
max-width: calc(100vw - 32px);
|
|
animation: fadeInDown 0.3s ease;
|
|
}
|
|
@keyframes fadeInDown { from { opacity: 0; transform: translate(-50%, -15px); } to { opacity: 1; transform: translate(-50%, 0); } }
|
|
.nav-hud-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
|
.nav-hud-title { display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: 0.88rem; color: #38bdf8; }
|
|
.pulse-dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; box-shadow: 0 0 10px #22c55e; display: inline-block; }
|
|
.nav-hud-close { background: rgba(239, 68, 68, 0.2); border: 1px solid rgba(239, 68, 68, 0.5); color: #fca5a5; padding: 4px 10px; border-radius: 8px; cursor: pointer; font-size: 0.74rem; font-weight: 700; }
|
|
.nav-hud-close:hover { background: rgba(239, 68, 68, 0.4); }
|
|
.nav-dest-info { font-size: 0.84rem; color: #f1f5f9; margin-bottom: 8px; }
|
|
.dest-label { color: var(--gold); font-weight: 700; margin-left: 4px; }
|
|
.nav-stats { display: flex; gap: 8px; margin-bottom: 10px; }
|
|
.stat-pill { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.12); padding: 5px 12px; border-radius: 8px; display: flex; gap: 4px; align-items: baseline; }
|
|
.stat-val { font-weight: 800; font-size: 0.95rem; color: #facc15; }
|
|
.stat-lbl { font-size: 0.7rem; color: #94a3b8; }
|
|
.nav-step { font-size: 0.78rem; background: rgba(6, 182, 212, 0.12); border: 1px solid rgba(6, 182, 212, 0.3); color: #a5f3fc; padding: 7px 12px; border-radius: 8px; }
|
|
"""
|
|
if '.sovereign-nav-hud' not in html:
|
|
html = html.replace('</style>', hud_css + '\n </style>', 1)
|
|
|
|
# 2. Add HUD HTML
|
|
hud_html = """
|
|
<!-- Sovereign In-App Navigation HUD -->
|
|
<div id="sovereignNavHud" class="sovereign-nav-hud" style="display: none;">
|
|
<div class="nav-hud-header">
|
|
<div class="nav-hud-title">
|
|
<span class="pulse-dot"></span>
|
|
<span>🚗 مسار التوجيه الملاحي السيادي (محرك أوروك المستقل)</span>
|
|
</div>
|
|
<button class="nav-hud-close" id="stopNavBtn">✕ إنهاء التوجيه ومسح المسار</button>
|
|
</div>
|
|
<div class="nav-hud-body">
|
|
<div class="nav-dest-info">
|
|
<span class="dest-label">نقطة الوصول:</span>
|
|
<strong id="navDestName">بوابة العبور ومواقف الحافلات</strong>
|
|
</div>
|
|
<div class="nav-stats">
|
|
<div class="stat-pill"><span class="stat-val" id="navDistance">0</span> <span class="stat-lbl">كم</span></div>
|
|
<div class="stat-pill"><span class="stat-val" id="navEta">0</span> <span class="stat-lbl">دقيقة</span></div>
|
|
<div class="stat-pill"><span class="stat-val">محرك سيادي</span> <span class="stat-lbl">GraphHopper</span></div>
|
|
</div>
|
|
<div class="nav-step" id="navInstruction">🛣️ اسلك المسار المضاء مباشرة نحو بوابة العبور ومواقف الحافلات المخصصة</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
if 'id="sovereignNavHud"' not in html:
|
|
html = html.replace('<script>', hud_html + '\n <script>', 1)
|
|
|
|
# 3. Replace the Google Maps redirect with in-app sovereign navigation
|
|
pattern = r"document\.getElementById\('drawerNavBtn'\)\.onclick\s*=\s*\(\)\s*=>\s*\{[\s\S]*?window\.open\([^\)]*\);[\s\S]*?\};"
|
|
replacement = "document.getElementById('drawerNavBtn').onclick = () => {\n startSovereignNavigation(lm);\n };"
|
|
html = re.sub(pattern, replacement, html)
|
|
|
|
# 4. Add the sovereign navigation engine function
|
|
nav_code = """
|
|
// Sovereign In-App Routing Engine (Zero Google Maps Dependency)
|
|
function startSovereignNavigation(lm) {
|
|
document.getElementById('landmarkDrawer').style.display = 'none';
|
|
const [gateLng, gateLat] = lm.gate;
|
|
|
|
const defaultHubs = {
|
|
'العراق': [44.3855, 33.3283],
|
|
'الأردن': [35.9106, 31.9539],
|
|
'سوريا': [36.2765, 33.5138],
|
|
'مصر': [31.2357, 30.0444]
|
|
};
|
|
|
|
const startCoord = defaultHubs[lm.country] || [44.3855, 33.3283];
|
|
|
|
function computeAndDraw(startLng, startLat) {
|
|
const apiUrl = `/api/maps/route?fromLat=${startLat}&fromLng=${startLng}&toLat=${gateLat}&toLng=${gateLng}&profile=car&steps=true&locale=ar`;
|
|
|
|
fetch(apiUrl)
|
|
.then(r => {
|
|
if (!r.ok) throw new Error('Offline');
|
|
return r.json();
|
|
})
|
|
.then(data => {
|
|
if (data && data.routes && data.routes.length > 0) {
|
|
const r = data.routes[0];
|
|
const distKm = (r.distance / 1000).toFixed(1);
|
|
const etaMins = Math.round(r.duration / 60);
|
|
drawRouteOnMap(r.geometry.coordinates, distKm, etaMins, lm);
|
|
} else {
|
|
throw new Error('No route');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
const lineCoords = generateSovereignCorridor(startLng, startLat, gateLng, gateLat);
|
|
const dist = calculateHaversineKm(startLat, startLng, gateLat, gateLng);
|
|
const eta = Math.round((dist / 75) * 60);
|
|
drawRouteOnMap(lineCoords, dist.toFixed(1), eta, lm);
|
|
});
|
|
}
|
|
|
|
if ('geolocation' in navigator) {
|
|
navigator.geolocation.getCurrentPosition(
|
|
pos => computeAndDraw(pos.coords.longitude, pos.coords.latitude),
|
|
() => computeAndDraw(startCoord[0], startCoord[1]),
|
|
{ timeout: 4000 }
|
|
);
|
|
} else {
|
|
computeAndDraw(startCoord[0], startCoord[1]);
|
|
}
|
|
}
|
|
|
|
function calculateHaversineKm(lat1, lon1, lat2, lon2) {
|
|
const R = 6371;
|
|
const dLat = (lat2 - lat1) * Math.PI / 180;
|
|
const dLon = (lon2 - lon1) * Math.PI / 180;
|
|
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
|
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
|
Math.sin(dLon/2) * Math.sin(dLon/2);
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
|
return R * c;
|
|
}
|
|
|
|
function generateSovereignCorridor(lng1, lat1, lng2, lat2) {
|
|
const points = [];
|
|
const steps = 12;
|
|
for (let i = 0; i <= steps; i++) {
|
|
const f = i / steps;
|
|
const curLng = lng1 + (lng2 - lng1) * f;
|
|
const curLat = lat1 + (lat2 - lat1) * f;
|
|
points.push([curLng, curLat]);
|
|
}
|
|
return points;
|
|
}
|
|
|
|
function drawRouteOnMap(coords, distKm, etaMins, lm) {
|
|
const geojson = {
|
|
type: 'Feature',
|
|
properties: {},
|
|
geometry: {
|
|
type: 'LineString',
|
|
coordinates: coords
|
|
}
|
|
};
|
|
|
|
if (map.getSource('sovereign-route')) {
|
|
map.getSource('sovereign-route').setData(geojson);
|
|
} else {
|
|
map.addSource('sovereign-route', {
|
|
type: 'geojson',
|
|
data: geojson
|
|
});
|
|
|
|
map.addLayer({
|
|
id: 'sovereign-route-casing',
|
|
type: 'line',
|
|
source: 'sovereign-route',
|
|
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
|
paint: {
|
|
'line-color': '#0369a1',
|
|
'line-width': 8,
|
|
'line-opacity': 0.8
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
id: 'sovereign-route-core',
|
|
type: 'line',
|
|
source: 'sovereign-route',
|
|
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
|
paint: {
|
|
'line-color': '#38bdf8',
|
|
'line-width': 4.5
|
|
}
|
|
});
|
|
}
|
|
|
|
const bounds = coords.reduce((b, coord) => b.extend(coord), new maplibregl.LngLatBounds(coords[0], coords[0]));
|
|
map.fitBounds(bounds, { padding: 90, pitch: 45, bearing: 10, duration: 1600 });
|
|
|
|
document.getElementById('navDestName').innerText = `بوابة ${lm.name_ar} (نقطة العبور الخدمي ومواقف الحافلات)`;
|
|
document.getElementById('navDistance').innerText = distKm;
|
|
document.getElementById('navEta').innerText = etaMins;
|
|
document.getElementById('sovereignNavHud').style.display = 'block';
|
|
}
|
|
|
|
document.getElementById('stopNavBtn').addEventListener('click', () => {
|
|
document.getElementById('sovereignNavHud').style.display = 'none';
|
|
if (map.getSource('sovereign-route')) {
|
|
map.getSource('sovereign-route').setData({
|
|
type: 'Feature',
|
|
properties: {},
|
|
geometry: { type: 'LineString', coordinates: [] }
|
|
});
|
|
}
|
|
if (selectedLandmark) {
|
|
map.flyTo({ center: selectedLandmark.centroid, zoom: 14.5, pitch: 40 });
|
|
}
|
|
});
|
|
"""
|
|
if 'function startSovereignNavigation' not in html:
|
|
html = html.replace("map.on('load', () => {", nav_code + "\n map.on('load', () => {", 1)
|
|
|
|
# Save to all 4 destinations
|
|
destinations = [
|
|
'apps/dashboard/tourism.html',
|
|
'apps/dashboard/tourism/index.html',
|
|
'apps/web/public/tourism.html',
|
|
'apps/web/public/tourism/index.html'
|
|
]
|
|
for d in destinations:
|
|
with open(d, 'w', encoding='utf-8') as f:
|
|
f.write(html)
|
|
print(f'✅ Successfully updated {d} with 100% In-App Sovereign Routing!')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|