1export const MAP_CONFIG = { 2 accessToken: 'pk.eyJ1IjoiY29sdW1iaWFzbG91Z2giLCJhIjoiY201MWFrbTBmMHN1aTJwcHd1dHloMGs4YyJ9.kQj7ux3XSeQiOBwxzM5B9g', 3 style: 'mapbox://styles/mapbox/satellite-streets-v11', 4 bounds: [[-122.85, 45.53], [-122.35, 45.6]], 5 maxBounds: [[-123, 45.3], [-122.1, 45.8]], 6 bearing: 16 7}; 8 9export const LAYER_STYLES = { 10 polygons: { 11 paint: { 12 'fill-color': '#33ff99', 13 'fill-opacity': 0.3, 14 'fill-outline-color': '#d7f531' 15 }, 16 minzoom: 13 17 }, 18 lines: { 19 paint: { 20 'line-color': '#ff5773', 21 'line-width': 2 22 }, 23 minzoom: 13 24 }, 25 points: { 26 paint: { 27 'circle-radius': 8, 28 'circle-color': '#ff0000', 29 'circle-opacity': 0.7 30 }, 31 minzoom: 13 32 }, 33 poi: { 34 circles: { 35 paint: { 36 'circle-radius': 9, 37 'circle-color': '#dc9c3c', 38 'circle-opacity': 1 39 } 40 }, 41 labels: { 42 layout: { 43 'text-field': ['get', 'labelId'], 44 'text-size': 10, 45 'text-font': ['Roboto Bold', 'Open Sans Bold', 'Arial Unicode MS Bold'], 46 'text-anchor': 'center', 47 'text-allow-overlap': true, 48 'text-ignore-placement': true, 49 'symbol-placement': 'point' 50 }, 51 paint: { 52 'text-color': 'white', 53 'text-halo-color': 'black', 54 'text-halo-width': 0.6 55 } 56 }, 57 pointLabels: { 58 layout: { 59 'text-size': 14, 60 'text-font': ['Roboto Bold', 'Open Sans Bold', 'Arial Unicode MS Bold'], 61 'text-anchor': 'bottom-left', 62 'text-offset': [0.5, -0.5], 63 'text-allow-overlap': false, 64 'text-ignore-placement': false, 65 'symbol-placement': 'point', 66 'visibility': 'none' 67 }, 68 paint: { 69 'text-color': 'white', 70 'text-halo-color': 'black', 71 'text-halo-width': 2 72 } 73 } 74 } 75}; 76 77export function initializeMapBase(mapboxgl) { 78 mapboxgl.accessToken = MAP_CONFIG.accessToken; 79 const map = new mapboxgl.Map({ 80 container: 'map', 81 style: MAP_CONFIG.style, 82 bounds: MAP_CONFIG.bounds, 83 maxBounds: MAP_CONFIG.maxBounds, 84 minZoom: 2 85 }); 86 map.setBearing(MAP_CONFIG.bearing); 87 return map; 88} 89 90export function addPolygonsLayer(map, data) { 91 if (!data || !data.features || data.features.length === 0) { 92 console.warn('No polygon features to add'); 93 return; 94 } 95 96 map.addSource('polygons', { 97 type: 'geojson', 98 data: data 99 }); 100 101 map.addLayer({ 102 id: 'polygons-layer', 103 type: 'fill', 104 source: 'polygons', 105 paint: LAYER_STYLES.polygons.paint, 106 layout: { visibility: 'none' }, 107 minzoom: LAYER_STYLES.polygons.minzoom 108 }); 109} 110 111export function addLinesLayer(map, data) { 112 if (!data || !data.features || data.features.length === 0) { 113 console.warn('No line features to add'); 114 return; 115 } 116 117 map.addSource('lines', { 118 type: 'geojson', 119 data: data 120 }); 121 122 map.addLayer({ 123 id: 'lines-layer', 124 type: 'line', 125 source: 'lines', 126 paint: LAYER_STYLES.lines.paint, 127 layout: { visibility: 'none' }, 128 minzoom: LAYER_STYLES.lines.minzoom 129 }); 130} 131 132export function addPointsLayer(map, data, iconNames = ['restrooms', 'parking', 'boats']) { 133 if (!data || !data.features || data.features.length === 0) { 134 console.warn('No point features to add'); 135 return Promise.resolve(); 136 } 137 138 console.log('Points data:', data); 139 140 const loadIcon = (iconName) => { 141 return new Promise((resolve, reject) => { 142 map.loadImage(`resources/icons/${iconName}.png`, (error, image) => { 143 if (error) { 144 console.error(`Error loading icon ${iconName}:`, error); 145 reject(error); 146 return; 147 } 148 if (!map.hasImage(iconName)) { 149 map.addImage(iconName, image); 150 } 151 resolve(); 152 }); 153 }); 154 }; 155 156 return Promise.all(iconNames.map(icon => loadIcon(icon))) 157 .then(() => { 158 map.addSource('points', { 159 type: 'geojson', 160 data: data 161 }); 162 163 map.addLayer({ 164 id: 'points-layer', 165 type: 'symbol', 166 source: 'points', 167 layout: { 168 'icon-image': ['get', 'icon_type'], 169 'icon-size': 0.09, 170 'visibility': 'none' 171 }, 172 minzoom: LAYER_STYLES.points.minzoom 173 }); 174 }) 175 .catch(error => console.error('Error loading point icons:', error)); 176} 177 178export function addPOILayer(map, data, currentLanguage = 'en') { 179 if (!data || !data.features || !Array.isArray(data.features)) { 180 console.error('Invalid POI data structure:', data); 181 return; 182 } 183 184 map.addSource('poi', { 185 type: 'geojson', 186 data: data 187 }); 188 189 map.addLayer({ 190 id: 'poi-circles', 191 type: 'circle', 192 source: 'poi', 193 paint: LAYER_STYLES.poi.circles.paint 194 }); 195 196 map.addLayer({ 197 id: 'poi-labels', 198 type: 'symbol', 199 source: 'poi', 200 layout: LAYER_STYLES.poi.labels.layout, 201 paint: LAYER_STYLES.poi.labels.paint 202 }); 203 204 map.addLayer({ 205 id: 'poi-point-labels', 206 type: 'symbol', 207 source: 'poi', 208 layout: { 209 ...LAYER_STYLES.poi.pointLabels.layout, 210 'text-field': ['get', `name_${currentLanguage}`] 211 }, 212 paint: LAYER_STYLES.poi.pointLabels.paint 213 }); 214} 215 216export function addMaskLayer(map, data) { 217 if (!data || !data.features || data.features.length === 0) { 218 console.warn('No mask features to add'); 219 return; 220 } 221 222 map.addSource('mask', { 223 type: 'geojson', 224 data: data 225 }); 226 227 map.addLayer({ 228 id: 'mask-layer', 229 type: 'fill', 230 source: 'mask', 231 paint: { 232 'fill-color': '#cfcfcf', 233 'fill-opacity': 0.5, 234 'fill-outline-color': '#cccccc' 235 }, 236 layout: { visibility: 'visible' } 237 }); 238} 239 240export function parseImages(imagesString) { 241 if (!imagesString) return []; 242 return imagesString.split(',').map(img =>
242 img.trim()).filter(Boolean); 243} 244 245export function hasRealImages(images) { 246 return images.length > 0 && images[0] !== 'image0' && images[0].startsWith('http'); 247} 248 249export function getImageUrl(imageUrl) { 250 if (imageUrl && imageUrl.startsWith('http')) { 251 return imageUrl; 252 } 253} 254 255export function filterAndShowLayer(map, sourceId, layerId, data, property, filterValue) { 256 if (!data || !data.features) { 257 console.log(`No data available for ${sourceId}`); 258 return; 259 } 260 261 const source = map.getSource(sourceId); 262 if (!source) { 263 console.log(`Source ${sourceId} not found`); 264 return; 265 } 266 267 try { 268 const matchingFeatures = data.features.filter( 269 feature => feature.properties[property] === filterValue 270 ); 271 272 if (matchingFeatures.length > 0) { 273 const filteredData = { 274 type: 'FeatureCollection', 275 features: matchingFeatures 276 }; 277 source.setData(filteredData); 278 map.setLayoutProperty(layerId, 'visibility', 'visible'); 279 console.log(`Showing ${layerId} with ${matchingFeatures.length} features`); 280 } else { 281 map.setLayoutProperty(layerId, 'visibility', 'none'); 282 } 283 } catch (error) { 284 console.error(`Error filtering ${sourceId}:`, error); 285 } 286} 287 288export function updatePOILanguage(map, language) { 289 if (map.getLayer('poi-point-labels')) { 290 map.setLayoutProperty('poi-point-labels', 'text-field', ['get', `name_${language}`]); 291 } 292} 293 294export function getPopupImageHtml(images, pointId, isEditable = false) { 295 const realImages = hasRealImages(images); 296 297 if (realImages) { 298 return `<img id="current-image" src="${images[0]}" alt="picture" class="popup-image" />`; 299 } else { 300 return `<p class="no-images-message">No images available</p>`; 301 } 302} 303 304export function getIconsHtml(tagsInput) { 305 if (!tagsInput) return ''; 306 307 const tags = Array.isArray(tagsInput) 308 ? tagsInput 309 : tagsInput.split(',').map(tag => tag.trim()).filter(Boolean); 310 311 return tags.map(tag => ` 312 <div class="icon-container" title="${tag}"> 313 <img src="resources/icons/${tag}.svg" alt="${tag}" class="icon-image"> 314 </div> 315 `).join(''); 316} 317 318export function getMapLinksHtml(latitude, longitude) { 319 return ` 320 <div class="map-icons"> 321 <div class="map-icon-container" title="Open in Google Maps"> 322 <a href="https://maps.google.com/?q=${latitude},${longitude}" target="_blank"> 323 <img src="https://www.vectorlogo.zone/logos/google_maps/google_maps-icon.svg" class="icon-image"> 324 </a> 325 </div> 326 <div class="map-icon-container" title="Open in Apple Maps"> 327 <a href="http://maps.apple.com/?q=${latitude},${longitude}" target="_blank"> 328 <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQk4kU3uCXeJ2uIbJL0bZQbm1KNRjnI7vW3Ww&s" class="icon-image"> 329 </a> 330 </div> 331 <div class="map-icon-container" title="Open in Bing Maps"> 332 <a href="https://bing.com/maps/default.aspx?cp=${latitude}~${longitude}&lvl=15" target="_blank"> 333 <img src="https://download.logo.wine/logo/Bing_Maps_Platform/Bing_Maps_Platform-Logo.wine.png" class="icon-image"> 334 </a> 335 </div> 336 </div> 337 `; 338}
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.