mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 17:11:09 -05:00
850 lines
26 KiB
JavaScript
850 lines
26 KiB
JavaScript
/**
|
|
* ThrillWiki Map Markers - Custom Marker Icons and Rich Popup System
|
|
*
|
|
* This module handles custom marker icons for different location types,
|
|
* rich popup content with location details, and performance optimization
|
|
*/
|
|
|
|
class MapMarkers {
|
|
constructor(mapInstance, options = {}) {
|
|
this.mapInstance = mapInstance;
|
|
this.options = {
|
|
enableClustering: true,
|
|
clusterDistance: 50,
|
|
enableCustomIcons: true,
|
|
enableRichPopups: true,
|
|
enableMarkerAnimation: true,
|
|
popupMaxWidth: 300,
|
|
iconTheme: 'modern', // 'modern', 'classic', 'emoji'
|
|
apiEndpoints: {
|
|
details: '/api/map/location-detail/',
|
|
media: '/api/media/'
|
|
},
|
|
...options
|
|
};
|
|
|
|
this.markerStyles = this.initializeMarkerStyles();
|
|
this.iconCache = new Map();
|
|
this.popupCache = new Map();
|
|
this.activePopup = null;
|
|
|
|
this.init();
|
|
}
|
|
|
|
/**
|
|
* Initialize the marker system
|
|
*/
|
|
init() {
|
|
this.setupMarkerStyles();
|
|
this.setupClusterStyles();
|
|
}
|
|
|
|
/**
|
|
* Initialize marker style definitions
|
|
*/
|
|
initializeMarkerStyles() {
|
|
return {
|
|
park: {
|
|
operating: {
|
|
color: '#10B981',
|
|
emoji: '🎢',
|
|
icon: 'fas fa-tree',
|
|
size: 'large'
|
|
},
|
|
closed_temp: {
|
|
color: '#F59E0B',
|
|
emoji: '🚧',
|
|
icon: 'fas fa-clock',
|
|
size: 'medium'
|
|
},
|
|
closed_perm: {
|
|
color: '#EF4444',
|
|
emoji: '❌',
|
|
icon: 'fas fa-times-circle',
|
|
size: 'medium'
|
|
},
|
|
under_construction: {
|
|
color: '#8B5CF6',
|
|
emoji: '🏗️',
|
|
icon: 'fas fa-hard-hat',
|
|
size: 'medium'
|
|
},
|
|
demolished: {
|
|
color: '#6B7280',
|
|
emoji: '🏚️',
|
|
icon: 'fas fa-ban',
|
|
size: 'small'
|
|
}
|
|
},
|
|
ride: {
|
|
operating: {
|
|
color: '#3B82F6',
|
|
emoji: '🎠',
|
|
icon: 'fas fa-rocket',
|
|
size: 'medium'
|
|
},
|
|
closed_temp: {
|
|
color: '#F59E0B',
|
|
emoji: '⏸️',
|
|
icon: 'fas fa-pause-circle',
|
|
size: 'small'
|
|
},
|
|
closed_perm: {
|
|
color: '#EF4444',
|
|
emoji: '❌',
|
|
icon: 'fas fa-times-circle',
|
|
size: 'small'
|
|
},
|
|
under_construction: {
|
|
color: '#8B5CF6',
|
|
emoji: '🔨',
|
|
icon: 'fas fa-tools',
|
|
size: 'small'
|
|
},
|
|
removed: {
|
|
color: '#6B7280',
|
|
emoji: '💔',
|
|
icon: 'fas fa-trash',
|
|
size: 'small'
|
|
}
|
|
},
|
|
company: {
|
|
manufacturer: {
|
|
color: '#8B5CF6',
|
|
emoji: '🏭',
|
|
icon: 'fas fa-industry',
|
|
size: 'medium'
|
|
},
|
|
operator: {
|
|
color: '#059669',
|
|
emoji: '🏢',
|
|
icon: 'fas fa-building',
|
|
size: 'medium'
|
|
},
|
|
designer: {
|
|
color: '#DC2626',
|
|
emoji: '🎨',
|
|
icon: 'fas fa-pencil-ruler',
|
|
size: 'medium'
|
|
}
|
|
},
|
|
user: {
|
|
current: {
|
|
color: '#3B82F6',
|
|
emoji: '📍',
|
|
icon: 'fas fa-crosshairs',
|
|
size: 'medium'
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Setup marker styles in CSS
|
|
*/
|
|
setupMarkerStyles() {
|
|
if (document.getElementById('map-marker-styles')) return;
|
|
|
|
const styles = `
|
|
<style id="map-marker-styles">
|
|
.location-marker {
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 50%;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.location-marker:hover {
|
|
transform: scale(1.1);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.location-marker-inner {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 50%;
|
|
color: white;
|
|
font-weight: bold;
|
|
font-size: 12px;
|
|
border: 3px solid white;
|
|
position: relative;
|
|
}
|
|
|
|
.location-marker-inner::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -8px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 0;
|
|
height: 0;
|
|
border-left: 6px solid transparent;
|
|
border-right: 6px solid transparent;
|
|
border-top: 8px solid inherit;
|
|
}
|
|
|
|
.location-marker.size-small .location-marker-inner {
|
|
width: 24px;
|
|
height: 24px;
|
|
font-size: 10px;
|
|
}
|
|
|
|
.location-marker.size-medium .location-marker-inner {
|
|
width: 32px;
|
|
height: 32px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.location-marker.size-large .location-marker-inner {
|
|
width: 40px;
|
|
height: 40px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.location-marker-emoji {
|
|
font-size: 1.2em;
|
|
line-height: 1;
|
|
}
|
|
|
|
.location-marker-icon {
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
/* Cluster markers */
|
|
.cluster-marker {
|
|
background: transparent;
|
|
border: none;
|
|
}
|
|
|
|
.cluster-marker-inner {
|
|
background: #3B82F6;
|
|
color: white;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: bold;
|
|
border: 3px solid white;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.cluster-marker:hover .cluster-marker-inner {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.cluster-marker-small .cluster-marker-inner {
|
|
width: 32px;
|
|
height: 32px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.cluster-marker-medium .cluster-marker-inner {
|
|
width: 40px;
|
|
height: 40px;
|
|
font-size: 14px;
|
|
background: #059669;
|
|
}
|
|
|
|
.cluster-marker-large .cluster-marker-inner {
|
|
width: 48px;
|
|
height: 48px;
|
|
font-size: 16px;
|
|
background: #DC2626;
|
|
}
|
|
|
|
/* Popup styles */
|
|
.location-popup {
|
|
min-width: 200px;
|
|
max-width: 300px;
|
|
}
|
|
|
|
.popup-header {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.popup-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
margin: 0 0 5px 0;
|
|
color: #1F2937;
|
|
}
|
|
|
|
.popup-subtitle {
|
|
font-size: 12px;
|
|
color: #6B7280;
|
|
margin: 0;
|
|
}
|
|
|
|
.popup-content {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.popup-detail {
|
|
display: flex;
|
|
align-items: center;
|
|
margin: 4px 0;
|
|
font-size: 13px;
|
|
color: #374151;
|
|
}
|
|
|
|
.popup-detail i {
|
|
width: 16px;
|
|
margin-right: 6px;
|
|
color: #6B7280;
|
|
}
|
|
|
|
.popup-actions {
|
|
display: flex;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.popup-btn {
|
|
padding: 4px 8px;
|
|
font-size: 12px;
|
|
border-radius: 4px;
|
|
border: none;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.popup-btn-primary {
|
|
background: #3B82F6;
|
|
color: white;
|
|
}
|
|
|
|
.popup-btn-primary:hover {
|
|
background: #2563EB;
|
|
}
|
|
|
|
.popup-btn-secondary {
|
|
background: #F3F4F6;
|
|
color: #374151;
|
|
}
|
|
|
|
.popup-btn-secondary:hover {
|
|
background: #E5E7EB;
|
|
}
|
|
|
|
.popup-image {
|
|
width: 100%;
|
|
max-height: 120px;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.popup-status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 6px;
|
|
border-radius: 12px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.popup-status.operating {
|
|
background: #D1FAE5;
|
|
color: #065F46;
|
|
}
|
|
|
|
.popup-status.closed {
|
|
background: #FEE2E2;
|
|
color: #991B1B;
|
|
}
|
|
|
|
.popup-status.construction {
|
|
background: #EDE9FE;
|
|
color: #5B21B6;
|
|
}
|
|
|
|
/* Dark mode styles */
|
|
.dark .popup-title {
|
|
color: #F9FAFB;
|
|
}
|
|
|
|
.dark .popup-detail {
|
|
color: #D1D5DB;
|
|
}
|
|
|
|
.dark .popup-btn-secondary {
|
|
background: #374151;
|
|
color: #D1D5DB;
|
|
}
|
|
|
|
.dark .popup-btn-secondary:hover {
|
|
background: #4B5563;
|
|
}
|
|
</style>
|
|
`;
|
|
|
|
document.head.insertAdjacentHTML('beforeend', styles);
|
|
}
|
|
|
|
/**
|
|
* Setup cluster marker styles
|
|
*/
|
|
setupClusterStyles() {
|
|
// Additional cluster-specific styles if needed
|
|
}
|
|
|
|
/**
|
|
* Create a location marker
|
|
*/
|
|
createLocationMarker(location) {
|
|
const iconData = this.getMarkerIconData(location);
|
|
const icon = this.createCustomIcon(iconData, location);
|
|
|
|
const marker = L.marker([location.latitude, location.longitude], {
|
|
icon: icon,
|
|
locationData: location,
|
|
riseOnHover: true
|
|
});
|
|
|
|
// Create popup
|
|
if (this.options.enableRichPopups) {
|
|
const popupContent = this.createPopupContent(location);
|
|
marker.bindPopup(popupContent, {
|
|
maxWidth: this.options.popupMaxWidth,
|
|
className: 'location-popup-container'
|
|
});
|
|
}
|
|
|
|
// Add click handler
|
|
marker.on('click', (e) => {
|
|
this.handleMarkerClick(marker, location);
|
|
});
|
|
|
|
// Add hover effects if animation is enabled
|
|
if (this.options.enableMarkerAnimation) {
|
|
marker.on('mouseover', () => {
|
|
const iconElement = marker.getElement();
|
|
if (iconElement) {
|
|
iconElement.style.transform = 'scale(1.1)';
|
|
iconElement.style.zIndex = '1000';
|
|
}
|
|
});
|
|
|
|
marker.on('mouseout', () => {
|
|
const iconElement = marker.getElement();
|
|
if (iconElement) {
|
|
iconElement.style.transform = 'scale(1)';
|
|
iconElement.style.zIndex = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
return marker;
|
|
}
|
|
|
|
/**
|
|
* Get marker icon data based on location type and status
|
|
*/
|
|
getMarkerIconData(location) {
|
|
const type = location.type || 'generic';
|
|
const status = location.status || 'operating';
|
|
|
|
// Get style data
|
|
const typeStyles = this.markerStyles[type];
|
|
if (!typeStyles) {
|
|
return this.markerStyles.park.operating;
|
|
}
|
|
|
|
const statusStyle = typeStyles[status.toLowerCase()];
|
|
if (!statusStyle) {
|
|
// Fallback to first available status for this type
|
|
const firstStatus = Object.keys(typeStyles)[0];
|
|
return typeStyles[firstStatus];
|
|
}
|
|
|
|
return statusStyle;
|
|
}
|
|
|
|
/**
|
|
* Create custom icon
|
|
*/
|
|
createCustomIcon(iconData, location) {
|
|
const cacheKey = `${location.type}-${location.status}-${this.options.iconTheme}`;
|
|
|
|
if (this.iconCache.has(cacheKey)) {
|
|
return this.iconCache.get(cacheKey);
|
|
}
|
|
|
|
let iconHtml;
|
|
|
|
switch (this.options.iconTheme) {
|
|
case 'emoji':
|
|
iconHtml = `<span class="location-marker-emoji">${iconData.emoji}</span>`;
|
|
break;
|
|
case 'classic':
|
|
iconHtml = `<i class="location-marker-icon ${iconData.icon}"></i>`;
|
|
break;
|
|
case 'modern':
|
|
default:
|
|
iconHtml = location.featured_image ?
|
|
`<img src="${location.featured_image}" alt="${location.name}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">` :
|
|
`<i class="location-marker-icon ${iconData.icon}"></i>`;
|
|
break;
|
|
}
|
|
|
|
const sizeClass = iconData.size || 'medium';
|
|
const size = sizeClass === 'small' ? 24 : sizeClass === 'large' ? 40 : 32;
|
|
|
|
const icon = L.divIcon({
|
|
className: `location-marker size-${sizeClass}`,
|
|
html: `<div class="location-marker-inner" style="background-color: ${iconData.color}">${iconHtml}</div>`,
|
|
iconSize: [size, size],
|
|
iconAnchor: [size / 2, size / 2],
|
|
popupAnchor: [0, -(size / 2) - 8]
|
|
});
|
|
|
|
this.iconCache.set(cacheKey, icon);
|
|
return icon;
|
|
}
|
|
|
|
/**
|
|
* Create rich popup content
|
|
*/
|
|
createPopupContent(location) {
|
|
const cacheKey = `popup-${location.type}-${location.id}`;
|
|
|
|
if (this.popupCache.has(cacheKey)) {
|
|
return this.popupCache.get(cacheKey);
|
|
}
|
|
|
|
const statusClass = this.getStatusClass(location.status);
|
|
|
|
const content = `
|
|
<div class="location-popup">
|
|
${location.featured_image ? `
|
|
<img src="${location.featured_image}" alt="${location.name}" class="popup-image">
|
|
` : ''}
|
|
|
|
<div class="popup-header">
|
|
<h3 class="popup-title">${this.escapeHtml(location.name)}</h3>
|
|
${location.type ? `<p class="popup-subtitle">${this.capitalizeFirst(location.type)}</p>` : ''}
|
|
${location.status ? `<span class="popup-status ${statusClass}">${this.formatStatus(location.status)}</span>` : ''}
|
|
</div>
|
|
|
|
<div class="popup-content">
|
|
${this.createPopupDetails(location)}
|
|
</div>
|
|
|
|
<div class="popup-actions">
|
|
${this.createPopupActions(location)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
this.popupCache.set(cacheKey, content);
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Create popup detail items
|
|
*/
|
|
createPopupDetails(location) {
|
|
const details = [];
|
|
|
|
if (location.formatted_location) {
|
|
details.push(`
|
|
<div class="popup-detail">
|
|
<i class="fas fa-map-marker-alt"></i>
|
|
<span>${this.escapeHtml(location.formatted_location)}</span>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
if (location.operator) {
|
|
details.push(`
|
|
<div class="popup-detail">
|
|
<i class="fas fa-building"></i>
|
|
<span>${this.escapeHtml(location.operator)}</span>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
if (location.ride_count && location.ride_count > 0) {
|
|
details.push(`
|
|
<div class="popup-detail">
|
|
<i class="fas fa-rocket"></i>
|
|
<span>${location.ride_count} ride${location.ride_count === 1 ? '' : 's'}</span>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
if (location.opened_date) {
|
|
details.push(`
|
|
<div class="popup-detail">
|
|
<i class="fas fa-calendar"></i>
|
|
<span>Opened ${this.formatDate(location.opened_date)}</span>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
if (location.manufacturer) {
|
|
details.push(`
|
|
<div class="popup-detail">
|
|
<i class="fas fa-industry"></i>
|
|
<span>${this.escapeHtml(location.manufacturer)}</span>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
if (location.designer) {
|
|
details.push(`
|
|
<div class="popup-detail">
|
|
<i class="fas fa-pencil-ruler"></i>
|
|
<span>${this.escapeHtml(location.designer)}</span>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
return details.join('');
|
|
}
|
|
|
|
/**
|
|
* Create popup action buttons
|
|
*/
|
|
createPopupActions(location) {
|
|
const actions = [];
|
|
|
|
// View details button
|
|
actions.push(`
|
|
<button onclick="mapMarkers.showLocationDetails('${location.type}', ${location.id})"
|
|
class="popup-btn popup-btn-primary">
|
|
<i class="fas fa-eye"></i>
|
|
View Details
|
|
</button>
|
|
`);
|
|
|
|
// Add to road trip (for parks)
|
|
if (location.type === 'park' && window.roadTripPlanner) {
|
|
actions.push(`
|
|
<button onclick="roadTripPlanner.addPark(${location.id})"
|
|
class="popup-btn popup-btn-secondary">
|
|
<i class="fas fa-route"></i>
|
|
Add to Trip
|
|
</button>
|
|
`);
|
|
}
|
|
|
|
// Get directions
|
|
if (location.latitude && location.longitude) {
|
|
const mapsUrl = `https://www.google.com/maps/dir/?api=1&destination=${location.latitude},${location.longitude}`;
|
|
actions.push(`
|
|
<a href="${mapsUrl}" target="_blank"
|
|
class="popup-btn popup-btn-secondary">
|
|
<i class="fas fa-directions"></i>
|
|
Directions
|
|
</a>
|
|
`);
|
|
}
|
|
|
|
return actions.join('');
|
|
}
|
|
|
|
/**
|
|
* Handle marker click events
|
|
*/
|
|
handleMarkerClick(marker, location) {
|
|
this.activePopup = marker.getPopup();
|
|
|
|
// Load additional data if needed
|
|
this.loadLocationDetails(location);
|
|
|
|
// Track click event
|
|
if (typeof gtag !== 'undefined') {
|
|
gtag('event', 'marker_click', {
|
|
event_category: 'map',
|
|
event_label: `${location.type}:${location.id}`,
|
|
custom_map: {
|
|
location_type: location.type,
|
|
location_name: location.name
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load additional location details
|
|
*/
|
|
async loadLocationDetails(location) {
|
|
try {
|
|
const response = await fetch(`${this.options.apiEndpoints.details}${location.type}/${location.id}/`);
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'success') {
|
|
// Update popup with additional details if popup is still open
|
|
if (this.activePopup && this.activePopup.isOpen()) {
|
|
const updatedContent = this.createPopupContent(data.data);
|
|
this.activePopup.setContent(updatedContent);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load location details:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show location details modal/page
|
|
*/
|
|
showLocationDetails(type, id) {
|
|
const url = `/${type}/${id}/`;
|
|
|
|
if (typeof htmx !== 'undefined') {
|
|
htmx.ajax('GET', url, {
|
|
target: '#location-modal',
|
|
swap: 'innerHTML'
|
|
}).then(() => {
|
|
const modal = document.getElementById('location-modal');
|
|
if (modal) {
|
|
modal.classList.remove('hidden');
|
|
}
|
|
});
|
|
} else {
|
|
window.location.href = url;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get CSS class for status
|
|
*/
|
|
getStatusClass(status) {
|
|
if (!status) return '';
|
|
|
|
const statusLower = status.toLowerCase();
|
|
|
|
if (statusLower.includes('operating') || statusLower.includes('open')) {
|
|
return 'operating';
|
|
} else if (statusLower.includes('closed') || statusLower.includes('temp')) {
|
|
return 'closed';
|
|
} else if (statusLower.includes('construction') || statusLower.includes('building')) {
|
|
return 'construction';
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Format status for display
|
|
*/
|
|
formatStatus(status) {
|
|
return status.replace(/_/g, ' ').toLowerCase().replace(/\b\w/g, l => l.toUpperCase());
|
|
}
|
|
|
|
/**
|
|
* Format date for display
|
|
*/
|
|
formatDate(dateString) {
|
|
try {
|
|
const date = new Date(dateString);
|
|
return date.getFullYear();
|
|
} catch (error) {
|
|
return dateString;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Capitalize first letter
|
|
*/
|
|
capitalizeFirst(str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
/**
|
|
* Escape HTML
|
|
*/
|
|
escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text.replace(/[&<>"']/g, m => map[m]);
|
|
}
|
|
|
|
/**
|
|
* Create cluster marker
|
|
*/
|
|
createClusterMarker(cluster) {
|
|
const count = cluster.getChildCount();
|
|
let sizeClass = 'small';
|
|
|
|
if (count > 100) sizeClass = 'large';
|
|
else if (count > 10) sizeClass = 'medium';
|
|
|
|
return L.divIcon({
|
|
html: `<div class="cluster-marker-inner">${count}</div>`,
|
|
className: `cluster-marker cluster-marker-${sizeClass}`,
|
|
iconSize: L.point(sizeClass === 'small' ? 32 : sizeClass === 'medium' ? 40 : 48,
|
|
sizeClass === 'small' ? 32 : sizeClass === 'medium' ? 40 : 48)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update marker theme
|
|
*/
|
|
setIconTheme(theme) {
|
|
this.options.iconTheme = theme;
|
|
this.iconCache.clear();
|
|
|
|
// Re-render all markers if map instance is available
|
|
if (this.mapInstance && this.mapInstance.markers) {
|
|
// This would need to be implemented in the main map class
|
|
console.log(`Icon theme changed to: ${theme}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear popup cache
|
|
*/
|
|
clearPopupCache() {
|
|
this.popupCache.clear();
|
|
}
|
|
|
|
/**
|
|
* Clear icon cache
|
|
*/
|
|
clearIconCache() {
|
|
this.iconCache.clear();
|
|
}
|
|
|
|
/**
|
|
* Get marker statistics
|
|
*/
|
|
getMarkerStats() {
|
|
return {
|
|
iconCacheSize: this.iconCache.size,
|
|
popupCacheSize: this.popupCache.size,
|
|
iconTheme: this.options.iconTheme
|
|
};
|
|
}
|
|
}
|
|
|
|
// Auto-initialize with map instance if available
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (window.thrillwikiMap) {
|
|
window.mapMarkers = new MapMarkers(window.thrillwikiMap);
|
|
}
|
|
});
|
|
|
|
// Export for use in other modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = MapMarkers;
|
|
} else {
|
|
window.MapMarkers = MapMarkers;
|
|
} |