Spaces:
Running
Running
Commit ·
454ef0a
1
Parent(s): 9d3bc81
✨ 新增距離計算功能,優化搜尋結果排序及顯示距離資訊
Browse files- .DS_Store +0 -0
- index.html +65 -9
.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
index.html
CHANGED
|
@@ -239,6 +239,35 @@
|
|
| 239 |
let activeInfoWindow = null;
|
| 240 |
let userLocation = null;
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
function initMap() {
|
| 243 |
const defaultLoc = { lat: 25.0330, lng: 121.5654 }; // 預設台北市中心
|
| 244 |
map = new google.maps.Map(document.getElementById("map"), {
|
|
@@ -260,6 +289,10 @@
|
|
| 260 |
new google.maps.Marker({ map, position: userLocation, icon: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png', title: '您的位置' });
|
| 261 |
}, () => {
|
| 262 |
console.log("使用者拒絕提供位置資訊。");
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
});
|
| 264 |
}
|
| 265 |
|
|
@@ -295,25 +328,36 @@
|
|
| 295 |
const request = {
|
| 296 |
query: query,
|
| 297 |
location: userLocation || new google.maps.LatLng(25.0330, 121.5654),
|
| 298 |
-
radius:
|
| 299 |
fields: ['name', 'geometry', 'place_id', 'rating', 'user_ratings_total', 'vicinity', 'url']
|
| 300 |
};
|
| 301 |
|
| 302 |
placesService.textSearch(request, (results, status) => {
|
| 303 |
loader.style.display = 'none';
|
| 304 |
|
| 305 |
-
if (status === google.maps.places.PlacesServiceStatus.OK && results) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
// 限制最多 10 個結果
|
| 307 |
-
const limitedResults =
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
|
|
|
|
|
|
| 311 |
map.setCenter(limitedResults[0].geometry.location);
|
| 312 |
map.setZoom(13);
|
| 313 |
}
|
| 314 |
|
| 315 |
// 為每個結果獲取詳細信息(包含評論)
|
| 316 |
-
limitedResults.forEach(
|
| 317 |
const detailsRequest = {
|
| 318 |
placeId: place.place_id,
|
| 319 |
fields: ['name', 'rating', 'user_ratings_total', 'vicinity', 'geometry', 'place_id', 'reviews', 'url']
|
|
@@ -321,6 +365,9 @@
|
|
| 321 |
|
| 322 |
placesService.getDetails(detailsRequest, (placeDetails, detailsStatus) => {
|
| 323 |
if (detailsStatus === google.maps.places.PlacesServiceStatus.OK) {
|
|
|
|
|
|
|
|
|
|
| 324 |
createMarker(placeDetails);
|
| 325 |
createCard(placeDetails);
|
| 326 |
}
|
|
@@ -355,10 +402,15 @@
|
|
| 355 |
reviewsHtml += '</ul></div>';
|
| 356 |
}
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
card.innerHTML = `
|
| 359 |
<h4>${place.name}</h4>
|
| 360 |
<p>⭐ ${place.rating || '無評分'} (${place.user_ratings_total || 0} 則評論)</p>
|
| 361 |
<p>📍 <a href="${place.url}" target="_blank">${place.vicinity}</a></p>
|
|
|
|
| 362 |
${reviewsHtml}
|
| 363 |
`;
|
| 364 |
|
|
@@ -380,12 +432,16 @@
|
|
| 380 |
title: place.name
|
| 381 |
});
|
| 382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
const infoWindowContent = `
|
| 384 |
<div class="custom-infowindow">
|
| 385 |
<span class="close-btn" onclick="this.parentElement.parentElement.style.display='none';">×</span>
|
| 386 |
<strong>${place.name}</strong><br>
|
| 387 |
⭐ ${place.rating || 'N/A'} | 評論: ${place.user_ratings_total || 0}<br>
|
| 388 |
-
<a href="${place.url}" target="_blank">在 Google 地圖上查看</a>
|
| 389 |
</div>
|
| 390 |
`;
|
| 391 |
|
|
@@ -478,4 +534,4 @@
|
|
| 478 |
</script>
|
| 479 |
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB9zIkabAHoudMDAwbKom6URgjmUuejEpo&callback=initMap&libraries=places,marker&v=weekly"></script>
|
| 480 |
</body>
|
| 481 |
-
</html>
|
|
|
|
| 239 |
let activeInfoWindow = null;
|
| 240 |
let userLocation = null;
|
| 241 |
|
| 242 |
+
const EARTH_RADIUS_METERS = 6371000;
|
| 243 |
+
|
| 244 |
+
function toRadians(degrees) {
|
| 245 |
+
return degrees * (Math.PI / 180);
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
function calculateDistanceMeters(origin, destination) {
|
| 249 |
+
if (!origin || !destination) {
|
| 250 |
+
return Number.POSITIVE_INFINITY;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
const lat1 = origin.lat;
|
| 254 |
+
const lng1 = origin.lng;
|
| 255 |
+
const lat2 = typeof destination.lat === 'function' ? destination.lat() : destination.lat;
|
| 256 |
+
const lng2 = typeof destination.lng === 'function' ? destination.lng() : destination.lng;
|
| 257 |
+
|
| 258 |
+
if ([lat1, lng1, lat2, lng2].some(value => typeof value !== 'number')) {
|
| 259 |
+
return Number.POSITIVE_INFINITY;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
const dLat = toRadians(lat2 - lat1);
|
| 263 |
+
const dLng = toRadians(lng2 - lng1);
|
| 264 |
+
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
| 265 |
+
Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
|
| 266 |
+
Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
| 267 |
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
| 268 |
+
return EARTH_RADIUS_METERS * c;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
function initMap() {
|
| 272 |
const defaultLoc = { lat: 25.0330, lng: 121.5654 }; // 預設台北市中心
|
| 273 |
map = new google.maps.Map(document.getElementById("map"), {
|
|
|
|
| 289 |
new google.maps.Marker({ map, position: userLocation, icon: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png', title: '您的位置' });
|
| 290 |
}, () => {
|
| 291 |
console.log("使用者拒絕提供位置資訊。");
|
| 292 |
+
}, {
|
| 293 |
+
enableHighAccuracy: true,
|
| 294 |
+
timeout: 10000,
|
| 295 |
+
maximumAge: 300000
|
| 296 |
});
|
| 297 |
}
|
| 298 |
|
|
|
|
| 328 |
const request = {
|
| 329 |
query: query,
|
| 330 |
location: userLocation || new google.maps.LatLng(25.0330, 121.5654),
|
| 331 |
+
radius: 40000,
|
| 332 |
fields: ['name', 'geometry', 'place_id', 'rating', 'user_ratings_total', 'vicinity', 'url']
|
| 333 |
};
|
| 334 |
|
| 335 |
placesService.textSearch(request, (results, status) => {
|
| 336 |
loader.style.display = 'none';
|
| 337 |
|
| 338 |
+
if (status === google.maps.places.PlacesServiceStatus.OK && results && results.length) {
|
| 339 |
+
let processedResults = results.slice();
|
| 340 |
+
|
| 341 |
+
if (userLocation) {
|
| 342 |
+
processedResults.forEach(place => {
|
| 343 |
+
place.distanceFromUser = calculateDistanceMeters(userLocation, place.geometry.location);
|
| 344 |
+
});
|
| 345 |
+
processedResults.sort((a, b) => (a.distanceFromUser || Infinity) - (b.distanceFromUser || Infinity));
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
// 限制最多 10 個結果
|
| 349 |
+
const limitedResults = processedResults.slice(0, 10);
|
| 350 |
+
|
| 351 |
+
if (userLocation) {
|
| 352 |
+
map.setCenter(userLocation);
|
| 353 |
+
map.setZoom(14);
|
| 354 |
+
} else if (limitedResults.length > 0) {
|
| 355 |
map.setCenter(limitedResults[0].geometry.location);
|
| 356 |
map.setZoom(13);
|
| 357 |
}
|
| 358 |
|
| 359 |
// 為每個結果獲取詳細信息(包含評論)
|
| 360 |
+
limitedResults.forEach(place => {
|
| 361 |
const detailsRequest = {
|
| 362 |
placeId: place.place_id,
|
| 363 |
fields: ['name', 'rating', 'user_ratings_total', 'vicinity', 'geometry', 'place_id', 'reviews', 'url']
|
|
|
|
| 365 |
|
| 366 |
placesService.getDetails(detailsRequest, (placeDetails, detailsStatus) => {
|
| 367 |
if (detailsStatus === google.maps.places.PlacesServiceStatus.OK) {
|
| 368 |
+
if (typeof place.distanceFromUser === 'number') {
|
| 369 |
+
placeDetails.distanceFromUser = place.distanceFromUser;
|
| 370 |
+
}
|
| 371 |
createMarker(placeDetails);
|
| 372 |
createCard(placeDetails);
|
| 373 |
}
|
|
|
|
| 402 |
reviewsHtml += '</ul></div>';
|
| 403 |
}
|
| 404 |
|
| 405 |
+
const distanceText = typeof place.distanceFromUser === 'number'
|
| 406 |
+
? `<p>📏 距離:約 ${(place.distanceFromUser / 1000).toFixed(1)} 公里</p>`
|
| 407 |
+
: '';
|
| 408 |
+
|
| 409 |
card.innerHTML = `
|
| 410 |
<h4>${place.name}</h4>
|
| 411 |
<p>⭐ ${place.rating || '無評分'} (${place.user_ratings_total || 0} 則評論)</p>
|
| 412 |
<p>📍 <a href="${place.url}" target="_blank">${place.vicinity}</a></p>
|
| 413 |
+
${distanceText}
|
| 414 |
${reviewsHtml}
|
| 415 |
`;
|
| 416 |
|
|
|
|
| 432 |
title: place.name
|
| 433 |
});
|
| 434 |
|
| 435 |
+
const distanceInfo = typeof place.distanceFromUser === 'number'
|
| 436 |
+
? `<br>距離:約 ${(place.distanceFromUser / 1000).toFixed(1)} 公里`
|
| 437 |
+
: '';
|
| 438 |
+
|
| 439 |
const infoWindowContent = `
|
| 440 |
<div class="custom-infowindow">
|
| 441 |
<span class="close-btn" onclick="this.parentElement.parentElement.style.display='none';">×</span>
|
| 442 |
<strong>${place.name}</strong><br>
|
| 443 |
⭐ ${place.rating || 'N/A'} | 評論: ${place.user_ratings_total || 0}<br>
|
| 444 |
+
<a href="${place.url}" target="_blank">在 Google 地圖上查看</a>${distanceInfo}
|
| 445 |
</div>
|
| 446 |
`;
|
| 447 |
|
|
|
|
| 534 |
</script>
|
| 535 |
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB9zIkabAHoudMDAwbKom6URgjmUuejEpo&callback=initMap&libraries=places,marker&v=weekly"></script>
|
| 536 |
</body>
|
| 537 |
+
</html>
|