#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Patch tourism.html files to dynamically load all 3,695 sovereign landmarks from the NestJS API and static JSON fallback. """ import re file_path = "apps/web/public/tourism.html" with open(file_path, "r", encoding="utf-8") as f: html = f.read() # 1. Update Category Chips to include historic_shrine and religious_heritage old_cat_bar = """
""" new_cat_bar = """
""" if old_cat_bar in html: html = html.replace(old_cat_bar, new_cat_bar) print("Updated category chips bar.") # 2. Change const LANDMARKS = [ to let LANDMARKS = [ html = html.replace("const LANDMARKS = [", "let LANDMARKS = [") # 3. Update country tab click listeners to flyTo country centers old_country_listener = """ document.querySelectorAll('.country-btn').forEach(btn => { btn.addEventListener('click', (e) => { document.querySelectorAll('.country-btn').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); activeCountry = e.target.getAttribute('data-country'); renderMarkers(); }); });""" new_country_listener = """ const COUNTRY_CENTERS = { 'all': { center: [38.0, 31.5], zoom: 5.5 }, 'العراق': { center: [44.3661, 33.3152], zoom: 6.5 }, 'الأردن': { center: [35.9284, 31.9454], zoom: 7.5 }, 'سوريا': { center: [36.7136, 34.7325], zoom: 7.2 }, 'مصر': { center: [31.2357, 29.5000], zoom: 6.5 } }; document.querySelectorAll('.country-btn').forEach(btn => { btn.addEventListener('click', (e) => { document.querySelectorAll('.country-btn').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); activeCountry = e.target.getAttribute('data-country'); if (COUNTRY_CENTERS[activeCountry]) { map.flyTo({ center: COUNTRY_CENTERS[activeCountry].center, zoom: COUNTRY_CENTERS[activeCountry].zoom, duration: 1800 }); } renderMarkers(); }); });""" if old_country_listener in html: html = html.replace(old_country_listener, new_country_listener) print("Updated country tab listener with flyTo.") # 4. Enhance renderMarkers & renderSlider for smooth performance with thousands of items old_render = """ // Render Markers for Landmarks function renderMarkers() { // Clear existing markers currentMarkers.forEach(m => m.remove()); currentMarkers = []; const filtered = LANDMARKS.filter(lm => { const matchC = activeCountry === 'all' || lm.country === activeCountry; const matchCat = activeCategory === 'all' || lm.category === activeCategory; const matchS = !searchQuery || lm.name_ar.includes(searchQuery) || lm.name_en.toLowerCase().includes(searchQuery.toLowerCase()); return matchC && matchCat && matchS; }); filtered.forEach(lm => { // 1. Centroid Marker const el = document.createElement('div'); el.className = 'marker-centroid'; el.innerHTML = '🏛️'; el.title = lm.name_ar; el.addEventListener('click', () => selectLandmark(lm)); const m = new maplibregl.Marker({ element: el }) .setLngLat(lm.centroid) .addTo(map); currentMarkers.push(m); // If this landmark is selected, also render its decoupled points (Gate, Parking, Ticket) if (selectedLandmark && selectedLandmark.slug === lm.slug) { // Gate const gateEl = document.createElement('div'); gateEl.className = 'marker-gate'; gateEl.innerHTML = '🚪'; gateEl.title = `بوابة دخول: ${lm.name_ar}`; gateEl.addEventListener('click', () => selectLandmark(lm)); currentMarkers.push(new maplibregl.Marker({ element: gateEl }).setLngLat(lm.gate).addTo(map)); // Parking const parkEl = document.createElement('div'); parkEl.className = 'marker-parking'; parkEl.innerHTML = 'P'; parkEl.title = `مواقف سيارات: ${lm.name_ar}`; currentMarkers.push(new maplibregl.Marker({ element: parkEl }).setLngLat(lm.parking).addTo(map)); // Ticket const ticketEl = document.createElement('div'); ticketEl.className = 'marker-ticket'; ticketEl.innerHTML = '🎟️'; ticketEl.title = `مكتب تذاكر: ${lm.name_ar}`; currentMarkers.push(new maplibregl.Marker({ element: ticketEl }).setLngLat(lm.ticket).addTo(map)); } }); renderSlider(filtered); } // Render Bottom Slider function renderSlider(list) { const slider = document.getElementById('landmarkSlider'); slider.innerHTML = ''; list.forEach(lm => { const card = document.createElement('div'); card.className = 'slider-card' + (selectedLandmark?.slug === lm.slug ? ' active' : ''); card.innerHTML = ` ${lm.name_ar}
${lm.name_ar}
📍 ${lm.governorate} • ${lm.era.split('/')[0]}
`; card.addEventListener('click', () => selectLandmark(lm)); slider.appendChild(card); }); }""" new_render = """ // Render Markers for Landmarks (Optimized for 3,600+ nodes) function renderMarkers() { currentMarkers.forEach(m => m.remove()); currentMarkers = []; const filtered = LANDMARKS.filter(lm => { const matchC = activeCountry === 'all' || lm.country === activeCountry; const matchCat = activeCategory === 'all' || lm.category === activeCategory; const matchS = !searchQuery || lm.name_ar.includes(searchQuery) || (lm.name_en && lm.name_en.toLowerCase().includes(searchQuery.toLowerCase())); return matchC && matchCat && matchS; }); // Update Header Subtitle Counter const subtitleEl = document.querySelector('.header-title small'); if (subtitleEl) { subtitleEl.textContent = `🏛️ خريطة التراث والآثار السيادية (${filtered.length.toLocaleString()} معلماً معتمداً)`; } // If activeCountry is all and no search, render top 200 landmarks to maintain silky 60fps const toRender = (activeCountry === 'all' && !searchQuery) ? filtered.slice(0, 250) : filtered; const categoryIcons = { 'archaeological_site': '🏛️', 'historic_shrine': '🕌', 'religious_heritage': '⛪', 'historic_palace': '🏰', 'monument': '🗿', 'museum': '🏺', 'cultural_trail': '🌿' }; toRender.forEach(lm => { const el = document.createElement('div'); el.className = 'marker-centroid'; el.innerHTML = categoryIcons[lm.category] || '🏛️'; el.title = `${lm.name_ar} (${lm.governorate})`; el.addEventListener('click', () => selectLandmark(lm)); const m = new maplibregl.Marker({ element: el }) .setLngLat(lm.centroid) .addTo(map); currentMarkers.push(m); if (selectedLandmark && selectedLandmark.slug === lm.slug) { const gateEl = document.createElement('div'); gateEl.className = 'marker-gate'; gateEl.innerHTML = '🚪'; gateEl.title = `بوابة دخول: ${lm.name_ar}`; gateEl.addEventListener('click', () => selectLandmark(lm)); currentMarkers.push(new maplibregl.Marker({ element: gateEl }).setLngLat(lm.gate).addTo(map)); const parkEl = document.createElement('div'); parkEl.className = 'marker-parking'; parkEl.innerHTML = 'P'; parkEl.title = `مواقف سيارات: ${lm.name_ar}`; currentMarkers.push(new maplibregl.Marker({ element: parkEl }).setLngLat(lm.parking).addTo(map)); const ticketEl = document.createElement('div'); ticketEl.className = 'marker-ticket'; ticketEl.innerHTML = '🎟️'; ticketEl.title = `مكتب تذاكر: ${lm.name_ar}`; currentMarkers.push(new maplibregl.Marker({ element: ticketEl }).setLngLat(lm.ticket).addTo(map)); } }); renderSlider(filtered); } // Render Bottom Slider (Virtual slice for performance) function renderSlider(list) { const slider = document.getElementById('landmarkSlider'); slider.innerHTML = ''; const displaySlice = list.slice(0, 100); displaySlice.forEach(lm => { const card = document.createElement('div'); card.className = 'slider-card' + (selectedLandmark?.slug === lm.slug ? ' active' : ''); card.innerHTML = ` ${lm.name_ar}
${lm.name_ar}
📍 ${lm.governorate} • ${lm.era ? lm.era.split('/')[0] : ''}
`; card.addEventListener('click', () => selectLandmark(lm)); slider.appendChild(card); }); } // Dynamic Live Loader for All 3,695 Sovereign Landmarks async function loadAllSovereignLandmarks() { try { const isServerEnv = window.location.hostname.includes('intaleqapp.com') || window.location.port === '3201' || window.location.port === '3204'; const apiUrl = isServerEnv ? '/api/v1/heritage/landmarks' : 'http://188.68.36.205:3200/api/v1/heritage/landmarks'; const res = await fetch(apiUrl, { headers: { 'x-api-key': SOVEREIGN_MAP_API_KEY } }); if (res.ok) { const apiData = await res.json(); if (Array.isArray(apiData) && apiData.length > 50) { LANDMARKS = apiData.map(item => ({ slug: item.slug, name_ar: item.name_ar, name_en: item.name_en || item.name_ar, country: item.metadata?.country || (item.governorate?.includes('معان') || item.governorate?.includes('عمان') || item.governorate?.includes('الكرك') || item.governorate?.includes('إربد') || item.governorate?.includes('البلقاء') || item.governorate?.includes('عجلون') || item.governorate?.includes('جرش') || item.governorate?.includes('مادبا') || item.governorate?.includes('الطفيلة') || item.governorate?.includes('العقبة') || item.governorate?.includes('المفرق') || item.governorate?.includes('الزرقاء') ? 'الأردن' : (item.governorate?.includes('دمشق') || item.governorate?.includes('ريف دمشق') || item.governorate?.includes('حلب') || item.governorate?.includes('حمص') || item.governorate?.includes('حماة') || item.governorate?.includes('اللاذقية') || item.governorate?.includes('طرطوس') || item.governorate?.includes('إدلب') || item.governorate?.includes('درعا') || item.governorate?.includes('السويداء') || item.governorate?.includes('القنيطرة') || item.governorate?.includes('دير الزور') || item.governorate?.includes('الرقة') || item.governorate?.includes('الحسكة') ? 'سوريا' : (item.governorate?.includes('القاهرة') || item.governorate?.includes('الجيزة') || item.governorate?.includes('الإسكندرية') || item.governorate?.includes('الأقصر') || item.governorate?.includes('أسوان') || item.governorate?.includes('سوهاج') || item.governorate?.includes('قنا') || item.governorate?.includes('المنيا') || item.governorate?.includes('أسيوط') || item.governorate?.includes('الغربية') || item.governorate?.includes('الشرقية') || item.governorate?.includes('الدقهلية') || item.governorate?.includes('البحيرة') || item.governorate?.includes('كفر الشيخ') || item.governorate?.includes('المنوفية') || item.governorate?.includes('دمياط') || item.governorate?.includes('مطروح') || item.governorate?.includes('الوادي الجديد') || item.governorate?.includes('البحر الأحمر') || item.governorate?.includes('شمال سيناء') || item.governorate?.includes('جنوب سيناء') || item.governorate?.includes('بني سويف') || item.governorate?.includes('الفيوم') || item.governorate?.includes('القليوبية') || item.governorate?.includes('بورسعيد') || item.governorate?.includes('الإسماعيلية') || item.governorate?.includes('السويس') ? 'مصر' : 'العراق'))), governorate: item.governorate, category: item.category, era: item.era || 'عصور تاريخية عريقة', narrative: item.narrative_ar || item.narrative_en || '', centroid: [item.coordinates.centroid.lng, item.coordinates.centroid.lat], gate: item.coordinates.access_gate ? [item.coordinates.access_gate.lng, item.coordinates.access_gate.lat] : [item.coordinates.centroid.lng + 0.0006, item.coordinates.centroid.lat + 0.0004], parking: item.coordinates.parking ? [item.coordinates.parking.lng, item.coordinates.parking.lat] : [item.coordinates.centroid.lng + 0.0012, item.coordinates.centroid.lat + 0.0008], ticket: item.coordinates.ticket_office ? [item.coordinates.ticket_office.lng, item.coordinates.ticket_office.lat] : [item.coordinates.centroid.lng + 0.0005, item.coordinates.centroid.lat + 0.0003], image: item.image_url || 'https://upload.wikimedia.org/wikipedia/commons/7/78/Ishtar_Gate_Babylon_Iraq.jpg', unesco: item.metadata?.unesco_id ? `اليونسكو #${item.metadata.unesco_id}` : (item.metadata?.unesco || 'تراث وطني تاريخي مسجل'), curator: item.metadata?.curator || 'الهيئة العامة للآثار والتراث', hours: item.metadata?.opening_hours || '08:00 - 18:00 يومياً' })); console.log(`✅ Loaded ${LANDMARKS.length} landmarks live from PostGIS API!`); renderMarkers(); return; } } } catch (err) { console.warn('API live fetch fallback triggered...', err); } // Static JSON Fallback try { const jsonRes = await fetch('/data/landmarks_mena_full_4000.json'); if (jsonRes.ok) { const jsonData = await jsonRes.json(); if (Array.isArray(jsonData) && jsonData.length > 0) { LANDMARKS = jsonData; console.log(`✅ Loaded ${LANDMARKS.length} landmarks from static JSON!`); renderMarkers(); } } } catch (e) { console.error('Failed to load JSON', e); } }""" if old_render in html: html = html.replace(old_render, new_render) print("Updated renderMarkers & renderSlider with live loader.") # 5. Call loadAllSovereignLandmarks() right after map.on('load') old_map_load = """ map.on('load', () => { renderMarkers(); });""" new_map_load = """ map.on('load', () => { renderMarkers(); loadAllSovereignLandmarks(); });""" if old_map_load in html: html = html.replace(old_map_load, new_map_load) print("Connected loadAllSovereignLandmarks to map load event.") # Save back to all 4 destinations targets = [ "apps/web/public/tourism.html", "apps/web/public/tourism/index.html", "apps/dashboard/tourism.html", "apps/dashboard/tourism/index.html" ] for t in targets: with open(t, "w", encoding="utf-8") as f: f.write(html) print(f"Successfully patched {t}") print("All tourism.html files updated successfully!")