1// import { MarkerClusterer } from "@googlemaps/markerclusterer"; 2import { MarkerClusterer } from "https://cdn.jsdelivr.net/npm/@googlemaps/[email protected]/+esm"; 3//OLD https://cdn.skypack.dev/@googlemaps/[email protected] 4 5 6async function initMap() { 7 // Request needed libraries. 8 const { Map, InfoWindow } = await google.maps.importLibrary("maps"); 9 const { AdvancedMarkerElement, PinElement } = await google.maps.importLibrary("marker"); 10 const { AutocompleteSuggestion } = await google.maps.importLibrary("places"); 11 var hotelStyledMap = new google.maps.StyledMapType( 12 [ 13 { 14 "featureType": "poi.business", 15 "stylers": [ 16 { 17 "visibility": "off" 18 } 19 ] 20 }, 21 { 22 "featureType": "poi.government", 23 "stylers": [ 24 { 25 "visibility": "off" 26 } 27 ] 28 }, 29 { 30 "featureType": "poi.school", 31 "stylers": [ 32 { 33 "visibility": "off" 34 } 35 ] 36 }, 37 { 38 elementType: "geometry", 39 stylers: [{ color: "#ecede8" }], 40 }, 41 { 42 elementType: "labels.icon", 43 stylers: [{ visibility: "off" }], 44 }, 45 { 46 elementType: "labels.text.fill", 47 stylers: [{ color: "#706e6e" }], 48 }, 49 { 50 elementType: "labels.text.stroke", 51 stylers: [{ color: "#f5f5f5" }], 52 }, 53 { 54 featureType: "administrative.land_parcel", 55 elementType: "labels.text.fill", 56 stylers: [{ color: "#bdbdbd" }], 57 }, 58 { 59 featureType: "poi", 60 elementType: "geometry", 61 stylers: [{ color: "#eeeeee" }], 62 }, 63 { 64 featureType: "poi", 65 elementType: "labels.text.fill", 66 stylers: [{ color: "#d8b05c" }], 67 }, 68 { 69 featureType: "poi.park", 70 elementType: "geometry", 71 stylers: [{ color: "#e5e5e5" }], 72 }, 73 { 74 featureType: "poi.park", 75 elementType: "labels.text.fill", 76 stylers: [{ color: "#B64733" }], 77 }, 78 { 79 featureType: "road", 80 elementType: "geometry", 81 stylers: [{ color: "#ffffff" }], 82 }, 83 { 84 featureType: "road.arterial", 85 elementType: "labels.text.fill", 86 stylers: [{ color: "#d8b05c" }], 87 }, 88 { 89 featureType: "road.highway", 90 elementType: "geometry", 91 stylers: [{ color: "#dadada" }], 92 }, 93 { 94 featureType: "road.highway", 95 elementType: "labels.text.fill", 96 stylers: [{ color: "#CF2E2E" }], 97 }, 98 { 99 featureType: "road.local", 100 elementType: "labels.text.fill", 101 stylers: [{ color: "#B64733" }], 102 }, 103 { 104 featureType: "transit.line", 105 elementType: "geometry", 106 stylers: [{ color: "#e5e5e5" }], 107 }, 108 { 109 featureType: "transit.station", 110 elementType: "geometry", 111 stylers: [{ color: "#eeeeee" }], 112 }, 113 { 114 featureType: "water", 115 elementType: "geometry", 116 stylers: [{ color: "#c9c9c9" }], 117 }, 118 { 119 featureType: "water", 120 elementType: "labels.text.fill", 121 stylers: [{ color: "#B64733" }], 122 }, 123 ], 124 { name: 'Hotel Styled Map' }); 125 126 var mapConfig = { 127 zoom: 6.7, 128 center: { lat: 56.227, lng: 10.419 }, 129 mapTypeControl: false, 130 panControl: false, 131 zoomControl: true, 132 // gestureHandling: 'none', 133 streetViewControl: false, 134 mapId: "hotelMap" 135 }; 136 if (window.hotelMap) { 137 mapConfig.zoom = window.hotelMap.getZoom(); 138 mapConfig.center = window.hotelMap.getCenter(); 139 } 140 141 window.hotelMap = new google.maps.Map(document.getElementById('hotelmap'), mapConfig); 142 143 //Theses lines spawn a warning that the map cannot be styles this way when using mapId https://developers.google.com/maps/documentation/javascript/cloud-customization but we can't use the cluster
143s unless we use a mapid - and it actually does work, soeh... 144 window.hotelMap.mapTypes.set('styled_map', hotelStyledMap); 145 window.hotelMap.setMapTypeId('styled_map'); 146 147 const Icon = '/typo3conf/ext/idefa_spectraintegration/Resources/Public/Icons/bed-solid-full.svg'; 148 149 150 151 const $searchElement = $('.hotelsearch .search-input'); 152 window.hotelSearch = new GoogleAutocomplete($searchElement, window.hotelMap, '/typo3conf/ext/idefa_spectraintegration/Resources/Public/Icons/hotel.png'); 153 154 const infoWindow = new google.maps.InfoWindow({ 155 content: "", 156 disableAutoPan: true, 157 }); 158 159 // Add some markers to the map. 160 var allHotels = JSON.parse(document.querySelector('.hotelsearch .search-input').dataset['hotels']); 161 162 allHotels.sort(function (a,b){ 163 if(a.name < b.name) return -1; 164 if(a.name > b.name) return 1; 165 return 0; 166 }); 167 const markers = allHotels.map((hotel, i) => { 168 if(!hotel.coords){ return; } 169 const pinGlyph = new google.maps.marker.PinElement({ 170 glyphColor: "black", 171 glyphSrc: Icon 172 }); 173 174 if (!hotel.location) { 175 hotel.location = window.hotelSearch.stringToCoords(hotel.coords); 176 } 177 const marker = new google.maps.marker.AdvancedMarkerElement({ 178 position:hotel.location, 179 // map:window.hotelMap, 180 content: pinGlyph.element, 181 }); 182 // markers can only be keyboard focusable when they have click listeners 183 // open info window when marker is clicked 184 marker.addListener("click", () => { 185 const place=hotel;const $autoComplete=window.hotelSearch; 186 var mapElement = $autoComplete.map.getDiv(); 187 if (!$autoComplete.infowindow) { 188 $autoComplete.infowindow = new google.maps.InfoWindow(); 189 $autoComplete.infowindow.addListener('closeclick', function () { 190 $autoComplete.resetMapZoom(); 191 $(mapElement).trigger('hotelMap.showAll'); 192 }); 193 $autoComplete.map.addListener('click', function () { 194 195 $autoComplete.infowindow.close(); 196 $autoComplete.resetMapZoom(); 197 $(mapElement).trigger('hotelMap.showAll'); 198 }); 199 } 200 201 var placemanchet = ""; 202 if (place.manchet.length > 0) { 203 var placemanchet = '<div class="mapplacedescription">' + place.manchet + '</div>'; 204 } 205 206 $autoComplete.infowindow.setContent( 207 '<a href="//'+place.domain.domainName+'"><h3 class="mapplacetitle">' + place.name + '</h3></a>' + 208 209 placemanchet + 210 211 '<div class="mapplacephone">Tlf: <a href="tel:' + place.phone + '">' + place.phone + ' | <a href="mailto:' + place.email + '">' + place.email + '</a></a></div>' + 212 '<div class="mapplaceweb"><strong><a href="//' + place.domain.domainName + '">' + place.domain.domainName + '</a></strong></div>' + 213 '<div class="mapvisitweb"><a href="//' + place.domain.domainName + '" class="btn-primary">GÃ TIL WEBSITE</a></div>' 214 ); 215 216 $autoComplete.infowindow.open($autoComplete.map, marker); 217 $autoComplete.zoomMap(place); 218 $(mapElement).trigger('hotelMap.showHotel', [place]); 219 }); 220 hotel.marker=marker; 221 window.hotelSearch.addMarker(hotel); 222 return marker; 223 }).filter(marker => marker); 224 // Add a marker clusterer to manage the markers. 225 new MarkerClusterer({ markers:markers, map:window.hotelMap }); 226 227 google.maps.event.addListenerOnce(window.hotelMap, 'idle', function () { 228 var Input = window.hotelSearch.input, 229 tA = Input.data('typeahead'), 230 hotels = Input.data('hotels'); 231 if (Input.val()) { 232 var found = false; 233 for (i in hotels) { 234 if (hotels[i].name == Input.val()) { 235 found = hotels[i]; 236 break; 237 } 238 } 239 if (found) { 240 tA.setDefault(found); 241 } else { 242 window.hotelSearch.lookup(Input.val(), function (results) { 243 if (results.length) { 244 tA.setDefault(results[0]); 245 } else { 246 console.warn('unexpected', results); 247 } 248 }); 249 } 250 } else { 251 reArrangeByDistance(); 252 } 253 }); 254 255} 256 257 258function GoogleAutocomplete(input, map, hotelIcon) { 259 // TODO: rewrite @see https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteSuggestion 260 this.input = input; 261 this.lastValue = null; 262 this.lastData = null; 263 this.minLength = 3 264 this.map = map; 265 //TODO: Places API (new) 266 // this.autocompleteService = new google.maps.places.AutocompleteSuggestion(); 267 this.autocompleteService = new google.maps.places.AutocompleteService(); 268 this.placeService = new google.maps.places.PlacesService(this.map); 269 this.selected = false;
270 this.sessionToken = new google.maps.places.AutocompleteSessionToken(); 271 this.markers = []; 272 this.hotelIcon = hotelIcon; 273 this.__selected = false; 274 275 if (typeof (jQuery.fn.typeahead) != 'function') { 276 console.error('GoogleAutocomlete is missing bootstrap typeahead - not loading'); 277 } else { 278 var $autoComplete = this; 279 $(this.input).typeahead({ 280 minLength: this.minLength, 281 source: function (value, callbackFunction) { 282 283 if (!value && $autoComplete.input.val()) { 284 this.query = value = $autoComplete.input.val(); 285 } 286 if (value == $autoComplete.lastValue) { return callbackFunction($autoComplete.lastData); } 287 $autoComplete.lastValue = value; 288 //! .data returns by reference so we need to slice in order to avoid getting all results ever stored... 289 // var data = ($autoComplete.input).data('hotels').slice(); 290 var data = $autoComplete.markers.slice(); 291 data.sort(function (a,b){ 292 if(a.name < b.name) return -1; 293 if(a.name > b.name) return 1; 294 return 0; 295 }); 296 297 var postProcess = function (data) { 298 //Post process data 299 $autoComplete.lastData = data; 300 callbackFunction(data); 301 } 302 303 if (value && value.length >= $autoComplete.minLength) { 304 var result = $autoComplete.lookup(value, postProcess, data); 305 } else { 306 postProcess(data); 307 } 308 }, 309 afterSelect: function (selected) { 310 $autoComplete.selected = selected; 311 if (selected.isGoogle) { 312 $autoComplete.resolvePlaceId(selected.id); 313 } else { 314 $autoComplete.zoomMap(selected); 315 } 316 }, 317 sorter: function (items) { 318 var beginswith = []; 319 var caseSensitive = []; 320 var caseInsensitive = []; 321 var item; 322 var hotelList = $($autoComplete.input).data('hotels').slice(); 323 324 while ((item = items.shift())) { 325 var it = this.displayText(item); 326 if (item.isHotel) { 327 //Makes sure hotels always appear first 328 beginswith.unshift(item); 329 continue; 330 } 331 if (!it.toLowerCase().indexOf(this.query.toLowerCase())) { 332 beginswith.push(item); 333 } else if (~it.indexOf(this.query)) { 334 caseSensitive.push(item); 335 } else { 336 caseInsensitive.push(item); 337 } 338 } 339 340 return beginswith.concat(caseSensitive, caseInsensitive); 341 }, 342 matcher: function (item) { 343 var it = item.name; 344 return ~it.toLowerCase().indexOf(this.query.toLowerCase()); 345 }, 346 highlighter: function (item) { 347 //disable highlight 348 return item; 349 }, 350 displayText: function (element) { 351 if (element == this.__selected) { 352 this.__selected = false; 353 return element.name; 354 } 355 if ( element.isHotel ){ 356 return (element.icon ? '<img src="' + element.icon + '">' : '<img src="' + $autoComplete.hotelIcon + '">') + element.name; 357 } 358 return (element.icon ? '<img src="' + element.icon + '">' : '') + element.name; 359 }, 360 updater: function (element) { 361 this.__selected = element; 362 return element; 363 }, 364 showHintOnFocus: 'all', 365 selectOnBlur: false, 366 items: 'all', 367 fitToElement: true, 368 scrollHeight: 10 369 }); 370 371 } 372} 373 374 375GoogleAutocomplete.prototype.lookup = async function (text, callback, results) { 376 var results = results || []; 377 //TODO: Places API (new) has not been used in project 843862390463 before or it is disabled 378 // const { predictions } = await google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions( 379 // { 380 // input: text, 381 // sessionToken: this.sessionToken, 382 // // componentRestrictions: { country: 'dk' }, 383 // includedPrimaryTypes: ['regions'],//(cities) 384 // region: "dk" 385 // } 386 // ); 387 // console.debug(predictions); 388 // callback(results); 389 // return ; 390 // if (!predictions) { 391 // callback(results); 392 // return; 393 // } 394 // predictions.forEach(function (element) { 395 // results.push( 396 // { 397 // id: element["place_id"], 398 // name: element.description, 399 // isGoogle: true 400 // } 401 // ) 402 // }); 403 // callback(results); 404 this.autocompleteService.getPlacePredictions( 405 { 406 input: text, 407 sessionToken: this.sessionToken, 408 componentRestrictions: { country: 'dk' }, 409 types: ['(regions)']//(cities) 410 }, 411 function (predictions, status) { 412 if (status != google.maps.places.PlacesServiceStatus.OK) { 413 callback(results); 414 return; 415 } 416 predictions.forEach(function (element) { 417 results.push( 418 { 419 id: element["place_id"], 420 name: element.description, 421 isGoogle: true 422 } 423 ) 424 }); 425 callback(results); 426 } 427 ); 428} 429 430GoogleAutocomplete.prototype.resolvePlaceId = function (placeid) { 431 var $autoComplete = this; 432 this.placeService.getDetails( 433 { 434 placeId: placeid, 435 sessionToken: this.sessionToken, 436 fields: ['geometry'] 437 }, 438 function (place, status) { 439 if (status != google.maps.places.PlacesServiceStatus.OK) { 440 return; 441 } 442 $autoComplete.zoomMap(place); 443 } 444 ); 445 //Genereate new session token or be punished by google 446 this.sessionToken = new google.maps.places.AutocompleteSessionToken(); 447} 448 449GoogleAutocomplete.prototype.zoomMap = function (place) { 450 const zoomLevel=14; 451 if (place.autoclick == true) { place.autoclick = false; return; } 452 if (place.geometry && place.geometry.viewport) { 453 this.map.fitBounds(place.geometry.viewport); 454 this.map.setZoom(zoomLevel); 455 this.updateFormHotels(); 456 } else { 457 this.map.setZoom(zoomLevel); 458 this.map.setCenter(this.stringToCoords(place.coords)); 459 place.autoclick = true; 460 console.debug(place); 461 this.updateFormHotels(); 462 google.maps.event.trigger(place.marker, 'click'); 463 464 } 465 //this.updateFormHotels(); 466} 467 468GoogleAutocomplete.prototype.resetMapZoom = function () { 469 //Hard coded, cause time... 470 this.map.setZoom(6); 471 this.map.setCenter({ lat: 56.227, lng: 10.419 }); 472} 473 474GoogleAutocomplete.prototype.updateFormHotels = function () { 475 this.input.closest("form").find("input[name='hotels']").val(this.getVisibleHotelIds().join(",")); 476} 477 478GoogleAutocomplete.prototype.stringToCoords = function (str) { 479 var tmp = str.split(','); 480 return { lat: parseFloat(tmp[0]), lng: parseFloat(tmp[1]) }; 481} 482 483GoogleAutocomplete.prototype.addMarker = function (place) { 484 if (place.coords && !place.location) { 485 place.location = this.stringToCoords(place.coords); 486 } 487 this.markers.push(place); 488} 489 490GoogleAutocomplete.prototype.getVisibleHotelIds = function () { 491 492 var visible = [], bounds = this.map.getBounds();
493 this.markers.forEach(gaPush); 494 495 function gaPush(element, index){ 496 if (bounds.contains(element.location)) { 497 visible.push(element.id); 498 } 499 } 500 reArrangeByDistance(); 501 502 return visible; 503} 504 505function reArrangeByDistance() { 506 var $hotels = $('.container.hotel-items'), mapCenter = window.hotelMap.getCenter(), hotelPos = false, dist = false, distArr = []; 507 var searchValue = $('.search-input').val(); 508 509 $hotels.each(function (i, e) { 510 $hotel = $(this); 511 hotelPos = new google.maps.LatLng($hotel.data('coords')[0], $hotel.data('coords')[1]); 512 dist = (google.maps.geometry.spherical.computeDistanceBetween(mapCenter, hotelPos) / 1000).toFixed(1); 513 distArr.push({ 514 'hotel': $hotel, 515 'dist': dist 516 }); 517 }); 518 if (distArr.length) { 519 distArr.sort(function (a, b) { 520 return b.dist - a.dist; 521 }); 522 $tmp = $('<div>'); 523 $hotels.first().before($tmp); 524 for (i in distArr) { 525 $tmp.after(distArr[i].hotel.remove()); 526 // console.debug(distArr[i].hotel.find('h2').text(), distArr[i].dist); 527 if(searchValue.length > 0){ 528 distArr[i].hotel.find('.distinkm').html(distArr[i].dist + ' km'); 529 } 530 531 } 532 $tmp.remove(); 533 } 534} 535 536initMap();
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.