/**
* 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 = `
`;
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 = `${iconData.emoji}`;
break;
case 'classic':
iconHtml = ``;
break;
case 'modern':
default:
iconHtml = location.featured_image ?
`` :
``;
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: `
${this.capitalizeFirst(location.type)}
` : ''} ${location.status ? `${this.formatStatus(location.status)}` : ''}