1import BaseMapController from 'controllers/mapbox/base/base_controller' 2import { SpiderfyManager } from 'utils/SpiderfyManager'; 3import { ClusterManager } from 'utils/ClusterManager'; 4import { LocationTooltipManager } from 'utils/LocationTooltipManager'; 5 6/** 7 * Base controller for clustered data maps 8 * 9 * Handles maps that display multiple similar objects with: 10 * - Clustering for performance 11 * - Spiderfy functionality for overlapping points 12 * - Tooltip management 13 * - Base layer switching 14 * 15 * Used by: Wind Resource Grids, Time Series Locations, Projects overview 16 */ 17export default class extends BaseMapController { 18 static targets = ["container"] 19 20 initializeData() { 21 this.sites = this.parseDataAttribute("sites", []); 22 this.iconUrl = this.loadDataAttribute("url-icon-image", '/assets/logo_blue.png'); 23 this.zoomLevel = parseInt(this.loadDataAttribute("zoom-level", 4)); 24 this.mapType = this.loadDataAttribute("map-type", 'generic'); 25 this.defaultCenter = this.parseDataAttribute("company-center", [2.168, 41.38]); 26 } 27 28 calculateCenter() { 29 // Calculate center from valid sites 30 return this.calculateCenterFromSites(this.sites); 31 } 32 33 addLayers() { 34 // Add sites if available 35 if (this.sites && this.sites.length > 0) { 36 this.addSites(this.map, this.sites); 37 } 38 } 39 40 addSites(map, sites) { 41 // Filter valid sites using base class method 42 const validSites = this.filterValidSites(sites); 43 44 if (validSites.length === 0) { 45 console.warn('No valid sites found, skipping site addition'); 46 return; 47 } 48 49 if (validSites.length < sites.length) { 50 console.warn(`Filtered out ${sites.length - validSites.length} invalid sites out of ${sites.length} total sites`); 51 } 52 53 // Add icon image 54 const icon = new Image(30, 30); 55 icon.onload = () => map.addImage(this.getIconImageName(), icon); 56 icon.src = this.iconUrl; 57 58 if (!map.getSource(this.getSitesSourceId())) { 59 this.setupClusteringAndSpiderfy(map, validSites); 60 } 61 } 62 63 setupClusteringAndSpiderfy(map, validSites) { 64 // Build location groups for spiderfy functionality 65 const locationGroups = {};
66 validSites.forEach((site, index) => { 67 const coordKey = `${site[0]},${site[1]}`; 68 if (!locationGroups[coordKey]) { 69 locationGroups[coordKey] = []; 70 } 71 locationGroups[coordKey].push(this.createSiteProperties(site, index)); 72 }); 73 74 // Initialize tooltip manager with map-specific configuration 75 this.tooltipManager = new LocationTooltipManager({ 76 mapType: this.mapType 77 }); 78 79 // Initialize SpiderfyManager 80 this.spiderfyManager = new SpiderfyManager(map, { 81 iconUrl: this.iconUrl, 82 iconImage: this.getIconImageName(), 83 sourceId: this.getSitesSourceId(), 84 clusterLayer: this.getClusterLayerId(), 85 sitesLayer: this.getSitesLayerId(), 86 clusterCountLayer: this.getClusterCountLayerId(), 87 onLocationClick: (coordinates, properties) => { 88 this.tooltipManager.showLocationPopup(map, coordinates, properties); 89 }, 90 onLocationHover: (coordinates, properties, type) => { 91 this.handleSpiderfyHover(coordinates, properties, type); 92 } 93 }); 94 95 // Set location groups 96 this.spiderfyManager.setLocationGroups(locationGroups); 97 98 // Create features for clustering 99 const features = validSites.map((site, index) => ({ 100 type: 'Feature', 101 geometry: { 102 type: 'Point', 103 coordinates: [site[1], site[0]] // [longitude, latitude] for Mapbox 104 }, 105 properties: { 106 ...this.createSiteProperties(site, index), 107 coordKey: `${site[0]},${site[1]}` 108 } 109 })); 110 111 // Initialize ClusterManager 112 this.clusterManager = new ClusterManager(map, { 113 sourceId: this.getSitesSourceId(), 114 clusterId: this.getClusterLayerId(), 115 clusterCountId: this.getClusterCountLayerId(), 116 sitesLayerId: this.getSitesLayerId(), 117 iconImage: this.getIconImageName(), 118 spiderfyManager: this.spiderfyManager, 119 tooltipOptions: { 120 mapType: this.mapType 121 }, 122 onSingleLocationClick: (coordinates, properties) => { 123 this.tooltipManager.showLocationPopup(map, coordinates, properties); 124 } 125 }); 126 127 // Setup clustering 128 this.clusterManager.setupClustering(features); 129 } 130 131 // ID generation methods using prefix 132 getSitesSourceId() { 133 return `${this.idPrefix}-sites-source`; 134 } 135 136 getClusterLayerId() { 137 return `${this.idPrefix}-clusters`; 138 } 139 140 getSitesLayerId() { 141 return `${this.idPrefix}-sites-layer`; 142 } 143 144 getClusterCountLayerId() { 145 return `${this.idPrefix}-cluster-count`; 146 } 147 148 getIconImageName() { 149 return `${this.idPrefix}-icon`; 150 } 151 152 createSiteProperties(site, index) { 153 // Default implementation - override in child classes for specific properties 154 return { 155 id: index, 156 name: site[2] || 'Unnamed Location', 157 latitude: parseFloat(site[0]), 158 longitude: parseFloat(site[1]) 159 }; 160 } 161 162 getReAddCallback() { 163 return 'addLayers'; 164 } 165 166 // SpiderfyManager callback for handling spiderfied marker hover 167 handleSpiderfyHover(coordinates, properties, type) { 168 if (type === 'leave') { 169 this.tooltipManager.hideHoverTooltip(); 170 return; 171 } 172 173 // Show single location info for spiderfied marker 174 this.tooltipManager.showHoverTooltip(this.map, coordinates, properties); 175 } 176}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.