PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.9
Yatra – Travel Booking & Tour Operator Software v3.0.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.9, at templates/partials/single-trip/content-location.php

893 lines 52.0 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(
154 /* translators: %s: map data provider link (e.g. OpenStreetMap). */
155 __('© %s contributors', 'yatra'),
156 '<a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
157 )); ?>,
158 maxZoom: 19,
159 minZoom: 2,
160 errorTileUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
161 crossOrigin: true
162 });
163
164 tileLayer.on('tileerror', function(error) {
165 console.warn('[Yatra Map] Tile loading error:', error);
166 });
167
168 tileLayer.addTo(map);
169
170 // Create custom icons with SVG
171 var startIcon = L.divIcon({
172 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>',
173 iconSize: [30, 30],
174 iconAnchor: [15, 30],
175 popupAnchor: [0, -30],
176 className: 'yatra-start-marker'
177 });
178
179 var endIcon = L.divIcon({
180 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>',
181 iconSize: [30, 30],
182 iconAnchor: [15, 30],
183 popupAnchor: [0, -30],
184 className: 'yatra-end-marker'
185 });
186
187 // Activity marker icon
188 var activityIcon = L.divIcon({
189 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>',
190 iconSize: [26, 26],
191 iconAnchor: [13, 26],
192 popupAnchor: [0, -26],
193 className: 'yatra-activity-marker'
194 });
195
196 var markers = [];
197 var routeCoordinates = [];
198
199 // Helper function to escape HTML
200 function escapeHtml(text) {
201 var div = document.createElement('div');
202 div.textContent = text;
203 return div.innerHTML;
204 }
205
206 // Helper function to truncate text
207 function truncateText(text, maxLength) {
208 if (!text) return '';
209 return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
210 }
211
212 // Add marker for starting location
213 if (startLat && startLng && !isNaN(startLat) && !isNaN(startLng)) {
214 var startMarker = L.marker([startLat, startLng], { icon: startIcon }).addTo(map);
215 var startPopupContent = '<div class="yatra-popup-content">' +
216 '<div class="yatra-popup-header">' +
217 '<div class="yatra-popup-icon-start">' +
218 '<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>' +
219 '</div>' +
220 '<div class="yatra-popup-title-section">' +
221 '<h4 class="yatra-popup-title">' + (startingLocation || yatraMapI18n.tripStartingPoint) + '</h4>' +
222 '<span class="yatra-popup-badge yatra-badge-start">' + yatraMapI18n.startingPoint + '</span>' +
223 '</div>' +
224 '</div>' +
225 '<div class="yatra-popup-body">' +
226 '<div class="yatra-popup-info">' +
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;"><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>' +
229 '<span class="yatra-popup-value">' + (startingLocation || yatraMapI18n.tripStart) + '</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;"><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>' +
233 '<span class="yatra-popup-value">' + startLat.toFixed(6) + ', ' + startLng.toFixed(6) + '</span>' +
234 '</div>' +
235 '<div class="yatra-popup-info-item">' +
236 '<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>' +
237 '<a href="https://www.openstreetmap.org/?mlat=' + startLat + '&mlon=' + startLng + '#map=15/' + startLat + '/' + startLng + '" target="_blank" class="yatra-popup-link">' + yatraMapI18n.viewOnOpenStreetMap + '</a>' +
238 '</div>' +
239 '</div>' +
240 '</div>' +
241 '</div>';
242 startMarker.bindPopup(startPopupContent, {
243 maxWidth: 300,
244 className: 'yatra-custom-popup'
245 });
246
247 // Add tooltip that shows on hover
248 var startTooltip = startingLocation ? startingLocation : yatraMapI18n.tripStartingPoint;
249 startMarker.bindTooltip(startTooltip, {
250 permanent: false,
251 direction: 'top',
252 className: 'yatra-marker-tooltip',
253 offset: [0, -35]
254 });
255
256 markers.push(startMarker);
257 routeCoordinates.push([startLat, startLng]);
258 }
259
260 // Add markers for all itinerary activities with validation
261 activities.forEach(function(activity, index) {
262 if (!activity || typeof activity !== 'object') {
263 console.warn('[Yatra Map] Invalid activity data at index:', index);
264 return;
265 }
266
267 if (activity.lat && activity.lng && !isNaN(activity.lat) && !isNaN(activity.lng)) {
268 var activityMarker = L.marker([activity.lat, activity.lng], { icon: activityIcon }).addTo(map);
269
270 // Create comprehensive activity popup content
271 var activityPopupContent = '<div class="yatra-popup-content">' +
272 '<div class="yatra-popup-header">' +
273 '<div class="yatra-popup-icon-activity">' +
274 '<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>' +
275 '</div>' +
276 '<div class="yatra-popup-title-section">' +
277 '<h4 class="yatra-popup-title">' + activity.title + '</h4>' +
278 '<div class="yatra-popup-badges">' +
279 '<span class="yatra-popup-badge yatra-badge-activity">' + yatraMapI18n.day + ' ' + activity.day + '</span>' +
280 (activity.day_title ? '<span class="yatra-popup-day-title">' + activity.day_title + '</span>' : '') +
281 '</div>' +
282 '</div>' +
283 '</div>' +
284 '<div class="yatra-popup-body">' +
285 '<div class="yatra-popup-info">' +
286 (activity.location ? '<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;"><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>' +
288 '<span class="yatra-popup-value">' + activity.location + '</span>' +
289 '</div>' : '') +
290 (activity.start_time && activity.end_time ? '<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"/><polyline points="12 6 12 12 16 14"/></svg>' + yatraMapI18n.labelTime + '</span>' +
292 '<span class="yatra-popup-value">' + activity.start_time + ' - ' + activity.end_time + '</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;"><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>' +
296 '<span class="yatra-popup-value">' + activity.lat.toFixed(6) + ', ' + activity.lng.toFixed(6) + '</span>' +
297 '</div>' +
298 '<div class="yatra-popup-info-item">' +
299 '<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>' +
300 '<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>' +
301 '</div>' +
302 '</div>' +
303 (activity.description ? '<div class="yatra-popup-description">' +
304 '<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>' +
305 '<div class="yatra-popup-desc-text">' +
306 (activity.description.length > 150 ? activity.description.substring(0, 150) + '...' : activity.description) +
307 '</div>' +
308 '</div>' : '') +
309 '</div>' +
310 '</div>';
311
312 activityMarker.bindPopup(activityPopupContent, {
313 maxWidth: 320,
314 className: 'yatra-custom-popup'
315 });
316
317 // Add tooltip that shows on hover
318 var activityTooltip = '<strong>' + activity.title + '</strong>';
319 if (activity.location) {
320 activityTooltip += '<br><small>' + activity.location + '</small>';
321 }
322 activityMarker.bindTooltip(activityTooltip, {
323 permanent: false,
324 direction: 'top',
325 className: 'yatra-marker-tooltip',
326 offset: [0, -32]
327 });
328
329 markers.push(activityMarker);
330 routeCoordinates.push([activity.lat, activity.lng]);
331 }
332 });
333
334 // Add marker for ending location
335 if (endLat && endLng && !isNaN(endLat) && !isNaN(endLng)) {
336 var endMarker = L.marker([endLat, endLng], { icon: endIcon }).addTo(map);
337 var endPopupContent = '<div class="yatra-popup-content">' +
338 '<div class="yatra-popup-header">' +
339 '<div class="yatra-popup-icon-end">' +
340 '<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>' +
341 '</div>' +
342 '<div class="yatra-popup-title-section">' +
343 '<h4 class="yatra-popup-title">' + (endingLocation || yatraMapI18n.tripEndingPoint) + '</h4>' +
344 '<span class="yatra-popup-badge yatra-badge-end">' + yatraMapI18n.endingPoint + '</span>' +
345 '</div>' +
346 '</div>' +
347 '<div class="yatra-popup-body">' +
348 '<div class="yatra-popup-info">' +
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;"><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>' +
351 '<span class="yatra-popup-value">' + (endingLocation || yatraMapI18n.tripEnd) + '</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;"><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>' +
355 '<span class="yatra-popup-value">' + endLat.toFixed(6) + ', ' + endLng.toFixed(6) + '</span>' +
356 '</div>' +
357 '<div class="yatra-popup-info-item">' +
358 '<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>' +
359 '<a href="https://www.openstreetmap.org/?mlat=' + endLat + '&mlon=' + endLng + '#map=15/' + endLat + '/' + endLng + '" target="_blank" class="yatra-popup-link">' + yatraMapI18n.viewOnOpenStreetMap + '</a>' +
360 '</div>' +
361 '</div>' +
362 '</div>' +
363 '</div>';
364 endMarker.bindPopup(endPopupContent, {
365 maxWidth: 300,
366 className: 'yatra-custom-popup'
367 });
368
369 // Add tooltip that shows on hover
370 var endTooltip = endingLocation ? endingLocation : yatraMapI18n.tripEndingPoint;
371 endMarker.bindTooltip(endTooltip, {
372 permanent: false,
373 direction: 'top',
374 className: 'yatra-marker-tooltip',
375 offset: [0, -35]
376 });
377
378 markers.push(endMarker);
379 routeCoordinates.push([endLat, endLng]);
380 }
381
382 // Draw complete travel route connecting all locations
383 if (routeCoordinates.length >= 2) {
384 // Route is ready for visualization
385
386 // Main route line with better visibility
387 var routeLine = L.polyline(routeCoordinates, {
388 color: '#3b82f6',
389 weight: 5,
390 opacity: 0.8,
391 smoothFactor: 1,
392 lineCap: 'round',
393 lineJoin: 'round'
394 }).addTo(map);
395
396 // Add animated dashed overlay for travel effect
397 var dashedLine = L.polyline(routeCoordinates, {
398 color: '#60a5fa',
399 weight: 3,
400 opacity: 0.9,
401 dashArray: '10, 15',
402 dashOffset: '0',
403 className: 'animated-route'
404 }).addTo(map);
405
406 // Add directional arrows to show travel direction with distance-based distribution
407 for (var i = 0; i < routeCoordinates.length - 1; i++) {
408 var start = routeCoordinates[i];
409 var end = routeCoordinates[i + 1];
410
411 // Calculate distance between points (simplified haversine formula)
412 var deltaLat = end[0] - start[0];
413 var deltaLng = end[1] - start[1];
414 var distance = Math.sqrt(deltaLat * deltaLat + deltaLng * deltaLng) * 111000; // Approximate distance in meters
415
416 // Determine number of arrows based on distance (reduced for cleaner look)
417 var arrowCount;
418 if (distance < 1000) {
419 arrowCount = 1; // Short distance: 1 arrow
420 } else if (distance < 3000) {
421 arrowCount = 1; // Medium distance: still 1 arrow
422 } else if (distance < 8000) {
423 arrowCount = 2; // Long distance: 2 arrows
424 } else {
425 arrowCount = 2; // Very long distance: max 2 arrows
426 }
427
428 // Place arrows evenly along the segment
429 for (var j = 0; j < arrowCount; j++) {
430 var fraction = (j + 1) / (arrowCount + 1); // Even distribution
431 var pointLat = start[0] + deltaLat * fraction;
432 var pointLng = start[1] + deltaLng * fraction;
433
434 // Calculate angle for arrow direction
435 var angleRad = Math.atan2(deltaLng, deltaLat);
436 var angleDeg = angleRad * 180 / Math.PI;
437
438 var arrowIcon = L.divIcon({
439 html: '<div style="width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center;">' +
440 '<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>' +
441 '</div>',
442 iconSize: [32, 32],
443 iconAnchor: [16, 16],
444 className: 'yatra-route-arrow'
445 });
446
447 var arrowMarker = L.marker([pointLat, pointLng], { icon: arrowIcon }).addTo(map);
448 }
449 }
450
451 // Fit map to show all markers with padding
452 var group = new L.featureGroup(markers);
453 map.fitBounds(group.getBounds().pad(0.15));
454 } else if (markers.length > 0) {
455 // If only one marker, center on it
456 if (markers.length === 1) {
457 markers[0].openPopup();
458 } else {
459 // Fit to show all markers
460 var group = new L.featureGroup(markers);
461 map.fitBounds(group.getBounds().pad(0.15));
462 }
463 }
464
465 // Markers are now properly added and persistent
466
467 // Add CSS animation for route only
468 var style = document.createElement('style');
469 style.textContent = `
470 @keyframes dash {
471 to {
472 stroke-dashoffset: -100;
473 }
474 }
475 .yatra-custom-marker, .yatra-start-marker, .yatra-end-marker, .yatra-activity-marker {
476 filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
477 transition: all 0.3s ease;
478 }
479 .yatra-custom-marker:hover, .yatra-start-marker:hover, .yatra-end-marker:hover, .yatra-activity-marker:hover {
480 filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
481 transform: scale(1.1);
482 }
483 .animated-route {
484 animation: dash 30s linear infinite;
485 }
486 .yatra-route-arrow {
487 transition: all 0.3s ease;
488 z-index: 600 !important;
489 }
490 .yatra-route-arrow:hover {
491 transform: scale(1.2);
492 filter: brightness(1.2);
493 }
494 `;
495 document.head.appendChild(style);
496
497 } catch (error) {
498 console.error('[Yatra Map] Initialization error:', error);
499 if (mapContainer) {
500 mapContainer.innerHTML = '<div style="padding: 40px; text-align: center; color: #ef4444;">' + yatraMapI18n.mapFailedToLoad + '</div>';
501 }
502 }
503 });
504 })();
505 </script>
506 <?php else: ?>
507 <div class="yatra-trip-map yatra-map-placeholder">
508 <p><?php echo esc_html__('Map location not available', 'yatra'); ?></p>
509 </div>
510 <?php endif; ?>
511
512 <?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))): ?>
513 <div class="yatra-location-details" style="margin-top: 24px;">
514 <div class="yatra-location-grid">
515 <?php if (!empty($trip->starting_location)): ?>
516 <div class="yatra-location-item">
517 <div class="yatra-location-icon">
518 <?php echo yatra_svg_icon('play', 'yatra-location-item-icon'); ?>
519 </div>
520 <div class="yatra-location-content">
521 <div class="yatra-location-label"><?php echo esc_html__('Starting Point', 'yatra'); ?></div>
522 <div class="yatra-location-value">
523 <?php if (!empty($trip->starting_latitude) && !empty($trip->starting_longitude)): ?>
524 <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); ?>"
525 target="_blank"
526 rel="noopener noreferrer"
527 class="yatra-coordinates-link">
528 <?php echo esc_html($trip->starting_location); ?>
529
530 </a>
531 <?php else: ?>
532 <?php echo esc_html($trip->starting_location); ?>
533 <?php endif; ?>
534 </div>
535 </div>
536 </div>
537 <?php endif; ?>
538
539 <?php if (!empty($trip->ending_location)): ?>
540 <div class="yatra-location-item">
541 <div class="yatra-location-icon">
542 <?php echo yatra_svg_icon('check', 'yatra-location-item-icon'); ?>
543 </div>
544 <div class="yatra-location-content">
545 <div class="yatra-location-label"><?php echo esc_html__('Ending Point', 'yatra'); ?></div>
546 <div class="yatra-location-value">
547 <?php if (!empty($trip->ending_latitude) && !empty($trip->ending_longitude)): ?>
548 <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); ?>"
549 target="_blank"
550 rel="noopener noreferrer"
551 class="yatra-coordinates-link">
552 <?php echo esc_html($trip->ending_location); ?>
553 </a>
554 <?php else: ?>
555 <?php echo esc_html($trip->ending_location); ?>
556 <?php endif; ?>
557 </div>
558 </div>
559 </div>
560 <?php endif; ?>
561
562 <?php if (!empty($trip->landmarks) && is_array($trip->landmarks)): ?>
563 <div class="yatra-location-item yatra-location-item-landmarks">
564 <div class="yatra-location-icon">
565 <?php echo yatra_svg_icon('map-pin', 'yatra-location-item-icon'); ?>
566 </div>
567 <div class="yatra-location-content">
568 <div class="yatra-location-label"><?php echo esc_html__('Key Landmarks', 'yatra'); ?></div>
569 <div class="yatra-location-value">
570 <div class="yatra-landmarks-list">
571 <?php foreach ($trip->landmarks as $index => $landmark): ?>
572 <span class="yatra-landmark-tag">
573 <?php echo yatra_svg_icon('map-pin', 'yatra-landmark-icon'); ?>
574 <?php echo esc_html($landmark); ?>
575 </span>
576 <?php endforeach; ?>
577 </div>
578 </div>
579 </div>
580 </div>
581 <?php endif; ?>
582 </div>
583 </div>
584
585 <style>
586 /* Enhanced popup styling */
587 .yatra-custom-popup {
588 font-family: system-ui, -apple-system, sans-serif !important;
589 }
590
591 .yatra-popup-content {
592 min-width: 280px;
593 max-width: 320px;
594 }
595
596 .yatra-popup-header {
597 display: flex;
598 align-items: flex-start;
599 gap: 12px;
600 padding-bottom: 12px;
601 border-bottom: 2px solid #e2e8f0;
602 margin-bottom: 12px;
603 }
604
605 .yatra-popup-icon-start,
606 .yatra-popup-icon-activity,
607 .yatra-popup-icon-end {
608 font-size: 24px;
609 flex-shrink: 0;
610 width: 40px;
611 height: 40px;
612 display: flex;
613 align-items: center;
614 justify-content: center;
615 border-radius: 50%;
616 }
617
618 .yatra-popup-icon-start {
619 background: linear-gradient(135deg, #10b981, #059669);
620 }
621
622 .yatra-popup-icon-activity {
623 background: linear-gradient(135deg, #3b82f6, #2563eb);
624 }
625
626 .yatra-popup-icon-end {
627 background: linear-gradient(135deg, #ef4444, #dc2626);
628 }
629
630 .yatra-popup-title-section {
631 flex: 1;
632 }
633
634 .yatra-popup-title {
635 margin: 0 0 6px 0;
636 font-size: 16px;
637 font-weight: 600;
638 color: #1e293b;
639 line-height: 1.2;
640 }
641
642 .yatra-popup-badges {
643 display: flex;
644 flex-wrap: wrap;
645 gap: 6px;
646 align-items: center;
647 }
648
649 .yatra-popup-badge {
650 display: inline-block;
651 padding: 3px 8px;
652 border-radius: 12px;
653 font-size: 11px;
654 font-weight: 600;
655 text-transform: uppercase;
656 letter-spacing: 0.5px;
657 }
658
659 .yatra-badge-start {
660 background: #dcfce7;
661 color: #166534;
662 }
663
664 .yatra-badge-activity {
665 background: #dbeafe;
666 color: #1e40af;
667 }
668
669 .yatra-badge-end {
670 background: #fee2e2;
671 color: #991b1b;
672 }
673
674 .yatra-popup-day-title {
675 color: #64748b;
676 font-size: 12px;
677 font-style: italic;
678 }
679
680 .yatra-popup-body {
681
682 }
683
684 .yatra-popup-info {
685 margin-bottom: 12px;
686 }
687
688 .yatra-popup-info-item {
689 display: flex;
690 justify-content: space-between;
691 align-items: flex-start;
692 margin-bottom: 8px;
693 gap: 8px;
694 }
695
696 .yatra-popup-label {
697 color: #64748b;
698 font-size: 12px;
699 font-weight: 500;
700 flex-shrink: 0;
701 min-width: 80px;
702 }
703
704 .yatra-popup-value {
705 color: #1e293b;
706 font-size: 12px;
707 font-weight: 500;
708 text-align: right;
709 flex: 1;
710 }
711
712 .yatra-popup-link {
713 color: #3b82f6 !important;
714 text-decoration: none !important;
715 font-size: 11px !important;
716 font-weight: 500 !important;
717 transition: color 0.2s ease !important;
718 }
719
720 .yatra-popup-link:hover {
721 color: #2563eb !important;
722 text-decoration: underline !important;
723 }
724
725 .yatra-popup-description {
726 margin-top: 12px;
727 padding-top: 12px;
728 border-top: 1px solid #e2e8f0;
729 }
730
731 .yatra-popup-desc-title {
732 color: #475569;
733 font-size: 12px;
734 font-weight: 600;
735 margin-bottom: 6px;
736 }
737
738 .yatra-popup-desc-text {
739 color: #64748b;
740 font-size: 12px;
741 line-height: 1.4;
742 }
743
744 /* Tooltip styling */
745 .yatra-marker-tooltip {
746 background: rgba(30, 41, 59, 0.95) !important;
747 border: none !important;
748 border-radius: 8px !important;
749 padding: 8px 12px !important;
750 font-family: system-ui, -apple-system, sans-serif !important;
751 font-size: 13px !important;
752 font-weight: 500 !important;
753 color: white !important;
754 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3) !important;
755 backdrop-filter: blur(10px) !important;
756 }
757
758 .yatra-marker-tooltip::before {
759 border-top-color: rgba(30, 41, 59, 0.95) !important;
760 }
761
762 .leaflet-tooltip-top.yatra-marker-tooltip::before {
763 border-top-color: rgba(30, 41, 59, 0.95) !important;
764 }
765
766 .yatra-marker-tooltip strong {
767 color: white !important;
768 font-weight: 600 !important;
769 }
770
771 .yatra-marker-tooltip small {
772 color: #cbd5e1 !important;
773 font-size: 11px !important;
774 }
775
776
777 .yatra-location-grid {
778 display: grid;
779 grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
780 gap: 16px;
781 margin-top: 24px;
782 }
783
784 .yatra-location-item {
785 display: flex;
786 align-items: flex-start;
787 gap: 12px;
788 padding: 16px;
789 background: #f8fafc;
790 border-radius: 8px;
791 border: 1px solid #e2e8f0;
792 transition: all 0.3s ease;
793 }
794
795 .yatra-location-item:hover {
796 background: #f1f5f9;
797 border-color: #cbd5e1;
798 transform: translateY(-2px);
799 }
800
801 .yatra-location-item-landmarks {
802 grid-column: 1 / -1;
803 }
804
805 .yatra-location-icon {
806 flex-shrink: 0;
807 width: 40px;
808 height: 40px;
809 display: flex;
810 align-items: center;
811 justify-content: center;
812 background: linear-gradient(135deg, #3b82f6, #2563eb);
813 color: white;
814 border-radius: 8px;
815 box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
816 }
817
818 .yatra-location-item-icon {
819 width: 20px;
820 height: 20px;
821 }
822
823 .yatra-location-content {
824 flex: 1;
825 }
826
827 .yatra-location-label {
828 font-size: 12px;
829 font-weight: 600;
830 color: #64748b;
831 text-transform: uppercase;
832 letter-spacing: 0.5px;
833 margin-bottom: 4px;
834 }
835
836 .yatra-location-value {
837 font-size: 14px;
838 color: #1e293b;
839 font-weight: 500;
840 }
841
842 .yatra-coordinates-link {
843 display: inline-flex;
844 align-items: center;
845 gap: 4px;
846 color: #3b82f6;
847 text-decoration: none;
848 transition: color 0.2s ease;
849 }
850
851 .yatra-coordinates-link:hover {
852 color: #2563eb;
853 text-decoration: underline;
854 }
855
856 .yatra-external-link-icon {
857 font-size: 12px;
858 opacity: 0.7;
859 }
860
861 .yatra-landmarks-list {
862 display: flex;
863 flex-wrap: wrap;
864 gap: 8px;
865 }
866
867 .yatra-landmark-tag {
868 display: inline-flex;
869 align-items: center;
870 gap: 4px;
871 padding: 4px 8px;
872 background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
873 color: #0369a1;
874 border: 1px solid #bae6fd;
875 border-radius: 6px;
876 font-size: 12px;
877 font-weight: 500;
878 transition: all 0.2s ease;
879 }
880
881 .yatra-landmark-tag:hover {
882 background: linear-gradient(135deg, #e0f2fe, #bae6fd);
883 border-color: #7dd3fc;
884 }
885
886 .yatra-landmark-icon {
887 width: 12px;
888 height: 12px;
889 }
890
891 </style>
892 <?php endif; ?>
893 </section>