PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.9
Yatra – Travel Booking & Tour Operator Software v3.0.2.9
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / templates / partials / single-trip / content-location.php

content-location.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.9, at templates/partials/single-trip/content-location.php

889 lines 51.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 // Location Section
7 // Expected variables: $trip, $tab, $itinerary_entries
8 ?>
9 <section class="yatra-trip-section" id="location" itemscope itemtype="https://schema.org/Place">
10 <h2 class="yatra-trip-section-title">
11 <?php yatra_render_tab_icon($tab->icon ?? null, 'map-pin', 'yatra-trip-section-title-icon', $tab->label ?? __('Location', 'yatra')); ?>
12 <?php echo esc_html(isset($tab->label) ? $tab->label : __('Location', 'yatra')); ?>
13 </h2>
14
15 <?php
16 // Use starting location coordinates if available
17 $map_lat = $trip->getStartingLatitude() ?? null;
18 $map_lng = $trip->getStartingLongitude() ?? null;
19
20 // Itinerary entries with coordinates are passed from controller (proper MVC pattern)
21 // This follows the separation of concerns principle
22 ?>
23
24 <?php if (!empty($map_lat) && !empty($map_lng)): ?>
25 <div class="yatra-trip-map" id="yatra-trip-map"
26 data-lat="<?php echo esc_attr($map_lat); ?>"
27 data-lng="<?php echo esc_attr($map_lng); ?>"
28 data-starting-location="<?php echo esc_attr($trip->getStartingLocation() ?? ''); ?>"
29 itemprop="hasMap" itemscope itemtype="https://schema.org/Map"
30 style="position: relative;">
31 <meta itemprop="mapType" content="OpenStreetMap">
32 <div id="yatra-openstreet-map" style="width: 100%; height: 400px; border-radius: 12px; background: white; overflow: hidden;"></div>
33 </div>
34 <div itemprop="geo" itemscope itemtype="https://schema.org/GeoCoordinates">
35 <meta itemprop="latitude" content="<?php echo esc_attr($map_lat); ?>">
36 <meta itemprop="longitude" content="<?php echo esc_attr($map_lng); ?>">
37 </div>
38
39 <!-- Load Leaflet CSS and JS -->
40 <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
41 integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
42 crossorigin=""/>
43 <script src="https://unpkg.com/[email protected]/dist/leaflet.js"
44 integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
45 crossorigin=""></script>
46
47 <script>
48 /**
49 * Yatra Trip Map Initialization
50 * Renders interactive map with trip route, markers, and directional arrows
51 * @version 1.0.0
52 */
53 (function() {
54 'use strict';
55
56 // Wait for DOM and Leaflet to be ready
57 if (typeof L === 'undefined') {
58 console.error('[Yatra Map] Leaflet library not loaded');
59 return;
60 }
61
62 document.addEventListener('DOMContentLoaded', function() {
63 try {
64 // Initialize map only if the map container exists
65 var mapContainer = document.getElementById('yatra-openstreet-map');
66 if (!mapContainer) {
67 console.warn('[Yatra Map] Map container not found');
68 return;
69 }
70
71 // Parse and validate coordinates
72 var startLat = parseFloat('<?php echo esc_js($trip->getStartingLatitude() ?? '0'); ?>');
73 var startLng = parseFloat('<?php echo esc_js($trip->getStartingLongitude() ?? '0'); ?>');
74 var endLat = parseFloat('<?php echo esc_js($trip->ending_latitude ?? '0'); ?>');
75 var endLng = parseFloat('<?php echo esc_js($trip->ending_longitude ?? '0'); ?>');
76 var startingLocation = '<?php echo esc_js($trip->getStartingLocation() ?? ''); ?>';
77 var endingLocation = '<?php echo esc_js($trip->ending_location ?? ''); ?>';
78 var yatraMapI18n = {
79 mapLocationNotAvailable: '<?php echo esc_js(__('Map location not available', 'yatra')); ?>',
80 mapFailedToLoad: '<?php echo esc_js(__('Map failed to load. Please refresh the page.', 'yatra')); ?>',
81 tripStartingPoint: '<?php echo esc_js(__('Trip Starting Point', 'yatra')); ?>',
82 tripStart: '<?php echo esc_js(__('Trip Start', 'yatra')); ?>',
83 tripEndingPoint: '<?php echo esc_js(__('Trip Ending Point', 'yatra')); ?>',
84 tripEnd: '<?php echo esc_js(__('Trip End', 'yatra')); ?>',
85 startingPoint: '<?php echo esc_js(__('Starting Point', 'yatra')); ?>',
86 endingPoint: '<?php echo esc_js(__('Ending Point', 'yatra')); ?>',
87 labelLocation: '<?php echo esc_js(__('Location:', 'yatra')); ?>',
88 labelCoordinates: '<?php echo esc_js(__('Coordinates:', 'yatra')); ?>',
89 labelMap: '<?php echo esc_js(__('Map:', 'yatra')); ?>',
90 labelTime: '<?php echo esc_js(__('Time:', 'yatra')); ?>',
91 labelDescription: '<?php echo esc_js(__('Description', 'yatra')); ?>',
92 day: '<?php echo esc_js(__('Day', 'yatra')); ?>',
93 viewOnOpenStreetMap: '<?php echo esc_js(__('View on OpenStreetMap ↗', 'yatra')); ?>'
94 };
95
96 // Validate coordinates
97 if (isNaN(startLat) || isNaN(startLng) || startLat === 0 || startLng === 0) {
98 console.warn('[Yatra Map] Invalid starting coordinates');
99 mapContainer.innerHTML = '<div style="padding: 40px; text-align: center; color: #64748b;">' + yatraMapI18n.mapLocationNotAvailable + '</div>';
100 return;
101 }
102
103 // Itinerary activities with coordinates (sanitized)
104 var activities = <?php echo wp_json_encode(array_map(function($entry) {
105 return [
106 'id' => absint($entry->id),
107 'title' => sanitize_text_field($entry->title),
108 'location' => sanitize_text_field($entry->location ?? ''),
109 'lat' => floatval($entry->location_latitude),
110 'lng' => floatval($entry->location_longitude),
111 'day' => absint($entry->day_number),
112 'day_title' => sanitize_text_field($entry->day_title ?? ''),
113 'start_time' => sanitize_text_field($entry->start_time ?? ''),
114 'end_time' => sanitize_text_field($entry->end_time ?? ''),
115 'description' => wp_kses_post($entry->description ?? '')
116 ];
117 }, $itinerary_entries ?? []), JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>;
118
119 // Validate activities data
120 if (!Array.isArray(activities)) {
121 console.warn('[Yatra Map] Invalid activities data');
122 activities = [];
123 }
124
125 // Calculate center point for better map view
126 var centerLat = (startLat + endLat) / 2;
127 var centerLng = (startLng + endLng) / 2;
128 var mapLat = startLat || centerLat;
129 var mapLng = startLng || centerLng;
130
131 // Initialize map with controls and error handling
132 var map = L.map('yatra-openstreet-map', {
133 center: [startLat || 0, startLng || 0],
134 zoom: 13,
135 zoomControl: true,
136 scrollWheelZoom: true,
137 doubleClickZoom: true,
138 touchZoom: true,
139 dragging: true,
140 tap: true,
141 maxZoom: 19,
142 minZoom: 2
143 });
144
145 // Add scale control
146 L.control.scale().addTo(map);
147
148 // Add attribution control
149 map.attributionControl.setPrefix('');
150
151 // Add OpenStreetMap tiles with error handling
152 var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
153 attribution: <?php echo wp_json_encode(sprintf(__('© %s contributors', 'yatra'), '<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>')); ?>,
154 maxZoom: 19,
155 minZoom: 2,
156 errorTileUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
157 crossOrigin: true
158 });
159
160 tileLayer.on('tileerror', function(error) {
161 console.warn('[Yatra Map] Tile loading error:', error);
162 });
163
164 tileLayer.addTo(map);
165
166 // Create custom icons with SVG
167 var startIcon = L.divIcon({
168 html: '<div style="background: linear-gradient(135deg, #10b981, #059669); color: white; width: 30px; height: 30px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); display: flex; align-items: center; justify-content: center; border: 2px solid white; box-shadow: 0 2px 6px rgba(0,0,0,0.3);"><svg style="transform: rotate(45deg); width: 14px; height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>',
169 iconSize: [30, 30],
170 iconAnchor: [15, 30],
171 popupAnchor: [0, -30],
172 className: 'yatra-start-marker'
173 });
174
175 var endIcon = L.divIcon({
176 html: '<div style="background: linear-gradient(135deg, #ef4444, #dc2626); color: white; width: 30px; height: 30px; border-radius: 50% 50% 50% 0; transform: rotate(-45deg); display: flex; align-items: center; justify-content: center; border: 2px solid white; box-shadow: 0 2px 6px rgba(0,0,0,0.3);"><svg style="transform: rotate(45deg); width: 14px; height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg></div>',
177 iconSize: [30, 30],
178 iconAnchor: [15, 30],
179 popupAnchor: [0, -30],
180 className: 'yatra-end-marker'
181 });
182
183 // Activity marker icon
184 var activityIcon = L.divIcon({
185 html: '<div style="background: linear-gradient(135deg, #3b82f6, #2563eb); color: white; width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; border: 2px solid white; box-shadow: 0 2px 6px rgba(0,0,0,0.3);"><svg width="12px" height="12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg></div>',
186 iconSize: [26, 26],
187 iconAnchor: [13, 26],
188 popupAnchor: [0, -26],
189 className: 'yatra-activity-marker'
190 });
191
192 var markers = [];
193 var routeCoordinates = [];
194
195 // Helper function to escape HTML
196 function escapeHtml(text) {
197 var div = document.createElement('div');
198 div.textContent = text;
199 return div.innerHTML;
200 }
201
202 // Helper function to truncate text
203 function truncateText(text, maxLength) {
204 if (!text) return '';
205 return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
206 }
207
208 // Add marker for starting location
209 if (startLat && startLng && !isNaN(startLat) && !isNaN(startLng)) {
210 var startMarker = L.marker([startLat, startLng], { icon: startIcon }).addTo(map);
211 var startPopupContent = '<div class="yatra-popup-content">' +
212 '<div class="yatra-popup-header">' +
213 '<div class="yatra-popup-icon-start">' +
214 '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5L20 7"/></svg>' +
215 '</div>' +
216 '<div class="yatra-popup-title-section">' +
217 '<h4 class="yatra-popup-title">' + (startingLocation || yatraMapI18n.tripStartingPoint) + '</h4>' +
218 '<span class="yatra-popup-badge yatra-badge-start">' + yatraMapI18n.startingPoint + '</span>' +
219 '</div>' +
220 '</div>' +
221 '<div class="yatra-popup-body">' +
222 '<div class="yatra-popup-info">' +
223 '<div class="yatra-popup-info-item">' +
224 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>' + yatraMapI18n.labelLocation + '</span>' +
225 '<span class="yatra-popup-value">' + (startingLocation || yatraMapI18n.tripStart) + '</span>' +
226 '</div>' +
227 '<div class="yatra-popup-info-item">' +
228 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>' + yatraMapI18n.labelCoordinates + '</span>' +
229 '<span class="yatra-popup-value">' + startLat.toFixed(6) + ', ' + startLng.toFixed(6) + '</span>' +
230 '</div>' +
231 '<div class="yatra-popup-info-item">' +
232 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><polygon points="1 6 1 22 8 18 16 22 16 6"/><polyline points="8 18 8 12 16 12"/></svg>' + yatraMapI18n.labelMap + '</span>' +
233 '<a href="https://www.openstreetmap.org/?mlat=' + startLat + '&mlon=' + startLng + '#map=15/' + startLat + '/' + startLng + '" target="_blank" class="yatra-popup-link">' + yatraMapI18n.viewOnOpenStreetMap + '</a>' +
234 '</div>' +
235 '</div>' +
236 '</div>' +
237 '</div>';
238 startMarker.bindPopup(startPopupContent, {
239 maxWidth: 300,
240 className: 'yatra-custom-popup'
241 });
242
243 // Add tooltip that shows on hover
244 var startTooltip = startingLocation ? startingLocation : yatraMapI18n.tripStartingPoint;
245 startMarker.bindTooltip(startTooltip, {
246 permanent: false,
247 direction: 'top',
248 className: 'yatra-marker-tooltip',
249 offset: [0, -35]
250 });
251
252 markers.push(startMarker);
253 routeCoordinates.push([startLat, startLng]);
254 }
255
256 // Add markers for all itinerary activities with validation
257 activities.forEach(function(activity, index) {
258 if (!activity || typeof activity !== 'object') {
259 console.warn('[Yatra Map] Invalid activity data at index:', index);
260 return;
261 }
262
263 if (activity.lat && activity.lng && !isNaN(activity.lat) && !isNaN(activity.lng)) {
264 var activityMarker = L.marker([activity.lat, activity.lng], { icon: activityIcon }).addTo(map);
265
266 // Create comprehensive activity popup content
267 var activityPopupContent = '<div class="yatra-popup-content">' +
268 '<div class="yatra-popup-header">' +
269 '<div class="yatra-popup-icon-activity">' +
270 '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>' +
271 '</div>' +
272 '<div class="yatra-popup-title-section">' +
273 '<h4 class="yatra-popup-title">' + activity.title + '</h4>' +
274 '<div class="yatra-popup-badges">' +
275 '<span class="yatra-popup-badge yatra-badge-activity">' + yatraMapI18n.day + ' ' + activity.day + '</span>' +
276 (activity.day_title ? '<span class="yatra-popup-day-title">' + activity.day_title + '</span>' : '') +
277 '</div>' +
278 '</div>' +
279 '</div>' +
280 '<div class="yatra-popup-body">' +
281 '<div class="yatra-popup-info">' +
282 (activity.location ? '<div class="yatra-popup-info-item">' +
283 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>' + yatraMapI18n.labelLocation + '</span>' +
284 '<span class="yatra-popup-value">' + activity.location + '</span>' +
285 '</div>' : '') +
286 (activity.start_time && activity.end_time ? '<div class="yatra-popup-info-item">' +
287 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>' + yatraMapI18n.labelTime + '</span>' +
288 '<span class="yatra-popup-value">' + activity.start_time + ' - ' + activity.end_time + '</span>' +
289 '</div>' : '') +
290 '<div class="yatra-popup-info-item">' +
291 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>' + yatraMapI18n.labelCoordinates + '</span>' +
292 '<span class="yatra-popup-value">' + activity.lat.toFixed(6) + ', ' + activity.lng.toFixed(6) + '</span>' +
293 '</div>' +
294 '<div class="yatra-popup-info-item">' +
295 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><polygon points="1 6 1 22 8 18 16 22 16 6"/><polyline points="8 18 8 12 16 12"/></svg>' + yatraMapI18n.labelMap + '</span>' +
296 '<a href="https://www.openstreetmap.org/?mlat=' + activity.lat + '&mlon=' + activity.lng + '#map=15/' + activity.lat + '/' + activity.lng + '" target="_blank" class="yatra-popup-link">' + yatraMapI18n.viewOnOpenStreetMap + '</a>' +
297 '</div>' +
298 '</div>' +
299 (activity.description ? '<div class="yatra-popup-description">' +
300 '<div class="yatra-popup-desc-title"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>' + yatraMapI18n.labelDescription + '</div>' +
301 '<div class="yatra-popup-desc-text">' +
302 (activity.description.length > 150 ? activity.description.substring(0, 150) + '...' : activity.description) +
303 '</div>' +
304 '</div>' : '') +
305 '</div>' +
306 '</div>';
307
308 activityMarker.bindPopup(activityPopupContent, {
309 maxWidth: 320,
310 className: 'yatra-custom-popup'
311 });
312
313 // Add tooltip that shows on hover
314 var activityTooltip = '<strong>' + activity.title + '</strong>';
315 if (activity.location) {
316 activityTooltip += '<br><small>' + activity.location + '</small>';
317 }
318 activityMarker.bindTooltip(activityTooltip, {
319 permanent: false,
320 direction: 'top',
321 className: 'yatra-marker-tooltip',
322 offset: [0, -32]
323 });
324
325 markers.push(activityMarker);
326 routeCoordinates.push([activity.lat, activity.lng]);
327 }
328 });
329
330 // Add marker for ending location
331 if (endLat && endLng && !isNaN(endLat) && !isNaN(endLng)) {
332 var endMarker = L.marker([endLat, endLng], { icon: endIcon }).addTo(map);
333 var endPopupContent = '<div class="yatra-popup-content">' +
334 '<div class="yatra-popup-header">' +
335 '<div class="yatra-popup-icon-end">' +
336 '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>' +
337 '</div>' +
338 '<div class="yatra-popup-title-section">' +
339 '<h4 class="yatra-popup-title">' + (endingLocation || yatraMapI18n.tripEndingPoint) + '</h4>' +
340 '<span class="yatra-popup-badge yatra-badge-end">' + yatraMapI18n.endingPoint + '</span>' +
341 '</div>' +
342 '</div>' +
343 '<div class="yatra-popup-body">' +
344 '<div class="yatra-popup-info">' +
345 '<div class="yatra-popup-info-item">' +
346 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>' + yatraMapI18n.labelLocation + '</span>' +
347 '<span class="yatra-popup-value">' + (endingLocation || yatraMapI18n.tripEnd) + '</span>' +
348 '</div>' +
349 '<div class="yatra-popup-info-item">' +
350 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>' + yatraMapI18n.labelCoordinates + '</span>' +
351 '<span class="yatra-popup-value">' + endLat.toFixed(6) + ', ' + endLng.toFixed(6) + '</span>' +
352 '</div>' +
353 '<div class="yatra-popup-info-item">' +
354 '<span class="yatra-popup-label"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align: middle; margin-right: 4px;"><polygon points="1 6 1 22 8 18 16 22 16 6"/><polyline points="8 18 8 12 16 12"/></svg>' + yatraMapI18n.labelMap + '</span>' +
355 '<a href="https://www.openstreetmap.org/?mlat=' + endLat + '&mlon=' + endLng + '#map=15/' + endLat + '/' + endLng + '" target="_blank" class="yatra-popup-link">' + yatraMapI18n.viewOnOpenStreetMap + '</a>' +
356 '</div>' +
357 '</div>' +
358 '</div>' +
359 '</div>';
360 endMarker.bindPopup(endPopupContent, {
361 maxWidth: 300,
362 className: 'yatra-custom-popup'
363 });
364
365 // Add tooltip that shows on hover
366 var endTooltip = endingLocation ? endingLocation : yatraMapI18n.tripEndingPoint;
367 endMarker.bindTooltip(endTooltip, {
368 permanent: false,
369 direction: 'top',
370 className: 'yatra-marker-tooltip',
371 offset: [0, -35]
372 });
373
374 markers.push(endMarker);
375 routeCoordinates.push([endLat, endLng]);
376 }
377
378 // Draw complete travel route connecting all locations
379 if (routeCoordinates.length >= 2) {
380 // Route is ready for visualization
381
382 // Main route line with better visibility
383 var routeLine = L.polyline(routeCoordinates, {
384 color: '#3b82f6',
385 weight: 5,
386 opacity: 0.8,
387 smoothFactor: 1,
388 lineCap: 'round',
389 lineJoin: 'round'
390 }).addTo(map);
391
392 // Add animated dashed overlay for travel effect
393 var dashedLine = L.polyline(routeCoordinates, {
394 color: '#60a5fa',
395 weight: 3,
396 opacity: 0.9,
397 dashArray: '10, 15',
398 dashOffset: '0',
399 className: 'animated-route'
400 }).addTo(map);
401
402 // Add directional arrows to show travel direction with distance-based distribution
403 for (var i = 0; i < routeCoordinates.length - 1; i++) {
404 var start = routeCoordinates[i];
405 var end = routeCoordinates[i + 1];
406
407 // Calculate distance between points (simplified haversine formula)
408 var deltaLat = end[0] - start[0];
409 var deltaLng = end[1] - start[1];
410 var distance = Math.sqrt(deltaLat * deltaLat + deltaLng * deltaLng) * 111000; // Approximate distance in meters
411
412 // Determine number of arrows based on distance (reduced for cleaner look)
413 var arrowCount;
414 if (distance < 1000) {
415 arrowCount = 1; // Short distance: 1 arrow
416 } else if (distance < 3000) {
417 arrowCount = 1; // Medium distance: still 1 arrow
418 } else if (distance < 8000) {
419 arrowCount = 2; // Long distance: 2 arrows
420 } else {
421 arrowCount = 2; // Very long distance: max 2 arrows
422 }
423
424 // Place arrows evenly along the segment
425 for (var j = 0; j < arrowCount; j++) {
426 var fraction = (j + 1) / (arrowCount + 1); // Even distribution
427 var pointLat = start[0] + deltaLat * fraction;
428 var pointLng = start[1] + deltaLng * fraction;
429
430 // Calculate angle for arrow direction
431 var angleRad = Math.atan2(deltaLng, deltaLat);
432 var angleDeg = angleRad * 180 / Math.PI;
433
434 var arrowIcon = L.divIcon({
435 html: '<div style="width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center;">' +
436 '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" style="transform: rotate(' + angleDeg + 'deg);"><polyline points="18 15 12 9 6 15"/></svg>' +
437 '</div>',
438 iconSize: [32, 32],
439 iconAnchor: [16, 16],
440 className: 'yatra-route-arrow'
441 });
442
443 var arrowMarker = L.marker([pointLat, pointLng], { icon: arrowIcon }).addTo(map);
444 }
445 }
446
447 // Fit map to show all markers with padding
448 var group = new L.featureGroup(markers);
449 map.fitBounds(group.getBounds().pad(0.15));
450 } else if (markers.length > 0) {
451 // If only one marker, center on it
452 if (markers.length === 1) {
453 markers[0].openPopup();
454 } else {
455 // Fit to show all markers
456 var group = new L.featureGroup(markers);
457 map.fitBounds(group.getBounds().pad(0.15));
458 }
459 }
460
461 // Markers are now properly added and persistent
462
463 // Add CSS animation for route only
464 var style = document.createElement('style');
465 style.textContent = `
466 @keyframes dash {
467 to {
468 stroke-dashoffset: -100;
469 }
470 }
471 .yatra-custom-marker, .yatra-start-marker, .yatra-end-marker, .yatra-activity-marker {
472 filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
473 transition: all 0.3s ease;
474 }
475 .yatra-custom-marker:hover, .yatra-start-marker:hover, .yatra-end-marker:hover, .yatra-activity-marker:hover {
476 filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
477 transform: scale(1.1);
478 }
479 .animated-route {
480 animation: dash 30s linear infinite;
481 }
482 .yatra-route-arrow {
483 transition: all 0.3s ease;
484 z-index: 600 !important;
485 }
486 .yatra-route-arrow:hover {
487 transform: scale(1.2);
488 filter: brightness(1.2);
489 }
490 `;
491 document.head.appendChild(style);
492
493 } catch (error) {
494 console.error('[Yatra Map] Initialization error:', error);
495 if (mapContainer) {
496 mapContainer.innerHTML = '<div style="padding: 40px; text-align: center; color: #ef4444;">' + yatraMapI18n.mapFailedToLoad + '</div>';
497 }
498 }
499 });
500 })();
501 </script>
502 <?php else: ?>
503 <div class="yatra-trip-map yatra-map-placeholder">
504 <p><?php echo esc_html__('Map location not available', 'yatra'); ?></p>
505 </div>
506 <?php endif; ?>
507
508 <?php if (!empty($trip->starting_location) || !empty($trip->ending_location) || !empty($trip->landmarks) || (!empty($trip->starting_latitude) && !empty($trip->starting_longitude)) || (!empty($trip->ending_latitude) && !empty($trip->ending_longitude))): ?>
509 <div class="yatra-location-details" style="margin-top: 24px;">
510 <div class="yatra-location-grid">
511 <?php if (!empty($trip->starting_location)): ?>
512 <div class="yatra-location-item">
513 <div class="yatra-location-icon">
514 <?php echo yatra_svg_icon('play', 'yatra-location-item-icon'); ?>
515 </div>
516 <div class="yatra-location-content">
517 <div class="yatra-location-label"><?php echo esc_html__('Starting Point', 'yatra'); ?></div>
518 <div class="yatra-location-value">
519 <?php if (!empty($trip->starting_latitude) && !empty($trip->starting_longitude)): ?>
520 <a href="https://www.openstreetmap.org/?mlat=<?php echo esc_attr($trip->starting_latitude); ?>&mlon=<?php echo esc_attr($trip->starting_longitude); ?>#map=15/<?php echo esc_attr($trip->starting_latitude); ?>/<?php echo esc_attr($trip->starting_longitude); ?>"
521 target="_blank"
522 rel="noopener noreferrer"
523 class="yatra-coordinates-link">
524 <?php echo esc_html($trip->starting_location); ?>
525
526 </a>
527 <?php else: ?>
528 <?php echo esc_html($trip->starting_location); ?>
529 <?php endif; ?>
530 </div>
531 </div>
532 </div>
533 <?php endif; ?>
534
535 <?php if (!empty($trip->ending_location)): ?>
536 <div class="yatra-location-item">
537 <div class="yatra-location-icon">
538 <?php echo yatra_svg_icon('check', 'yatra-location-item-icon'); ?>
539 </div>
540 <div class="yatra-location-content">
541 <div class="yatra-location-label"><?php echo esc_html__('Ending Point', 'yatra'); ?></div>
542 <div class="yatra-location-value">
543 <?php if (!empty($trip->ending_latitude) && !empty($trip->ending_longitude)): ?>
544 <a href="https://www.openstreetmap.org/?mlat=<?php echo esc_attr($trip->ending_latitude); ?>&mlon=<?php echo esc_attr($trip->ending_longitude); ?>#map=15/<?php echo esc_attr($trip->ending_latitude); ?>/<?php echo esc_attr($trip->ending_longitude); ?>"
545 target="_blank"
546 rel="noopener noreferrer"
547 class="yatra-coordinates-link">
548 <?php echo esc_html($trip->ending_location); ?>
549 </a>
550 <?php else: ?>
551 <?php echo esc_html($trip->ending_location); ?>
552 <?php endif; ?>
553 </div>
554 </div>
555 </div>
556 <?php endif; ?>
557
558 <?php if (!empty($trip->landmarks) && is_array($trip->landmarks)): ?>
559 <div class="yatra-location-item yatra-location-item-landmarks">
560 <div class="yatra-location-icon">
561 <?php echo yatra_svg_icon('map-pin', 'yatra-location-item-icon'); ?>
562 </div>
563 <div class="yatra-location-content">
564 <div class="yatra-location-label"><?php echo esc_html__('Key Landmarks', 'yatra'); ?></div>
565 <div class="yatra-location-value">
566 <div class="yatra-landmarks-list">
567 <?php foreach ($trip->landmarks as $index => $landmark): ?>
568 <span class="yatra-landmark-tag">
569 <?php echo yatra_svg_icon('map-pin', 'yatra-landmark-icon'); ?>
570 <?php echo esc_html($landmark); ?>
571 </span>
572 <?php endforeach; ?>
573 </div>
574 </div>
575 </div>
576 </div>
577 <?php endif; ?>
578 </div>
579 </div>
580
581 <style>
582 /* Enhanced popup styling */
583 .yatra-custom-popup {
584 font-family: system-ui, -apple-system, sans-serif !important;
585 }
586
587 .yatra-popup-content {
588 min-width: 280px;
589 max-width: 320px;
590 }
591
592 .yatra-popup-header {
593 display: flex;
594 align-items: flex-start;
595 gap: 12px;
596 padding-bottom: 12px;
597 border-bottom: 2px solid #e2e8f0;
598 margin-bottom: 12px;
599 }
600
601 .yatra-popup-icon-start,
602 .yatra-popup-icon-activity,
603 .yatra-popup-icon-end {
604 font-size: 24px;
605 flex-shrink: 0;
606 width: 40px;
607 height: 40px;
608 display: flex;
609 align-items: center;
610 justify-content: center;
611 border-radius: 50%;
612 }
613
614 .yatra-popup-icon-start {
615 background: linear-gradient(135deg, #10b981, #059669);
616 }
617
618 .yatra-popup-icon-activity {
619 background: linear-gradient(135deg, #3b82f6, #2563eb);
620 }
621
622 .yatra-popup-icon-end {
623 background: linear-gradient(135deg, #ef4444, #dc2626);
624 }
625
626 .yatra-popup-title-section {
627 flex: 1;
628 }
629
630 .yatra-popup-title {
631 margin: 0 0 6px 0;
632 font-size: 16px;
633 font-weight: 600;
634 color: #1e293b;
635 line-height: 1.2;
636 }
637
638 .yatra-popup-badges {
639 display: flex;
640 flex-wrap: wrap;
641 gap: 6px;
642 align-items: center;
643 }
644
645 .yatra-popup-badge {
646 display: inline-block;
647 padding: 3px 8px;
648 border-radius: 12px;
649 font-size: 11px;
650 font-weight: 600;
651 text-transform: uppercase;
652 letter-spacing: 0.5px;
653 }
654
655 .yatra-badge-start {
656 background: #dcfce7;
657 color: #166534;
658 }
659
660 .yatra-badge-activity {
661 background: #dbeafe;
662 color: #1e40af;
663 }
664
665 .yatra-badge-end {
666 background: #fee2e2;
667 color: #991b1b;
668 }
669
670 .yatra-popup-day-title {
671 color: #64748b;
672 font-size: 12px;
673 font-style: italic;
674 }
675
676 .yatra-popup-body {
677
678 }
679
680 .yatra-popup-info {
681 margin-bottom: 12px;
682 }
683
684 .yatra-popup-info-item {
685 display: flex;
686 justify-content: space-between;
687 align-items: flex-start;
688 margin-bottom: 8px;
689 gap: 8px;
690 }
691
692 .yatra-popup-label {
693 color: #64748b;
694 font-size: 12px;
695 font-weight: 500;
696 flex-shrink: 0;
697 min-width: 80px;
698 }
699
700 .yatra-popup-value {
701 color: #1e293b;
702 font-size: 12px;
703 font-weight: 500;
704 text-align: right;
705 flex: 1;
706 }
707
708 .yatra-popup-link {
709 color: #3b82f6 !important;
710 text-decoration: none !important;
711 font-size: 11px !important;
712 font-weight: 500 !important;
713 transition: color 0.2s ease !important;
714 }
715
716 .yatra-popup-link:hover {
717 color: #2563eb !important;
718 text-decoration: underline !important;
719 }
720
721 .yatra-popup-description {
722 margin-top: 12px;
723 padding-top: 12px;
724 border-top: 1px solid #e2e8f0;
725 }
726
727 .yatra-popup-desc-title {
728 color: #475569;
729 font-size: 12px;
730 font-weight: 600;
731 margin-bottom: 6px;
732 }
733
734 .yatra-popup-desc-text {
735 color: #64748b;
736 font-size: 12px;
737 line-height: 1.4;
738 }
739
740 /* Tooltip styling */
741 .yatra-marker-tooltip {
742 background: rgba(30, 41, 59, 0.95) !important;
743 border: none !important;
744 border-radius: 8px !important;
745 padding: 8px 12px !important;
746 font-family: system-ui, -apple-system, sans-serif !important;
747 font-size: 13px !important;
748 font-weight: 500 !important;
749 color: white !important;
750 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
751 backdrop-filter: blur(10px) !important;
752 }
753
754 .yatra-marker-tooltip::before {
755 border-top-color: rgba(30, 41, 59, 0.95) !important;
756 }
757
758 .leaflet-tooltip-top.yatra-marker-tooltip::before {
759 border-top-color: rgba(30, 41, 59, 0.95) !important;
760 }
761
762 .yatra-marker-tooltip strong {
763 color: white !important;
764 font-weight: 600 !important;
765 }
766
767 .yatra-marker-tooltip small {
768 color: #cbd5e1 !important;
769 font-size: 11px !important;
770 }
771
772
773 .yatra-location-grid {
774 display: grid;
775 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
776 gap: 16px;
777 margin-top: 24px;
778 }
779
780 .yatra-location-item {
781 display: flex;
782 align-items: flex-start;
783 gap: 12px;
784 padding: 16px;
785 background: #f8fafc;
786 border-radius: 8px;
787 border: 1px solid #e2e8f0;
788 transition: all 0.3s ease;
789 }
790
791 .yatra-location-item:hover {
792 background: #f1f5f9;
793 border-color: #cbd5e1;
794 transform: translateY(-2px);
795 }
796
797 .yatra-location-item-landmarks {
798 grid-column: 1 / -1;
799 }
800
801 .yatra-location-icon {
802 flex-shrink: 0;
803 width: 40px;
804 height: 40px;
805 display: flex;
806 align-items: center;
807 justify-content: center;
808 background: linear-gradient(135deg, #3b82f6, #2563eb);
809 color: white;
810 border-radius: 8px;
811 box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
812 }
813
814 .yatra-location-item-icon {
815 width: 20px;
816 height: 20px;
817 }
818
819 .yatra-location-content {
820 flex: 1;
821 }
822
823 .yatra-location-label {
824 font-size: 12px;
825 font-weight: 600;
826 color: #64748b;
827 text-transform: uppercase;
828 letter-spacing: 0.5px;
829 margin-bottom: 4px;
830 }
831
832 .yatra-location-value {
833 font-size: 14px;
834 color: #1e293b;
835 font-weight: 500;
836 }
837
838 .yatra-coordinates-link {
839 display: inline-flex;
840 align-items: center;
841 gap: 4px;
842 color: #3b82f6;
843 text-decoration: none;
844 transition: color 0.2s ease;
845 }
846
847 .yatra-coordinates-link:hover {
848 color: #2563eb;
849 text-decoration: underline;
850 }
851
852 .yatra-external-link-icon {
853 font-size: 12px;
854 opacity: 0.7;
855 }
856
857 .yatra-landmarks-list {
858 display: flex;
859 flex-wrap: wrap;
860 gap: 8px;
861 }
862
863 .yatra-landmark-tag {
864 display: inline-flex;
865 align-items: center;
866 gap: 4px;
867 padding: 4px 8px;
868 background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
869 color: #0369a1;
870 border: 1px solid #bae6fd;
871 border-radius: 6px;
872 font-size: 12px;
873 font-weight: 500;
874 transition: all 0.2s ease;
875 }
876
877 .yatra-landmark-tag:hover {
878 background: linear-gradient(135deg, #e0f2fe, #bae6fd);
879 border-color: #7dd3fc;
880 }
881
882 .yatra-landmark-icon {
883 width: 12px;
884 height: 12px;
885 }
886
887 </style>
888 <?php endif; ?>
889 </section>