let currentMarker = null;
let currentInfoWindow = null;
let userLocation = null;
function initMap() {
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 14,
center: { lat: 44.0521, lng: -123.0867 }, // Eugene, OR
mapTypeId: "terrain",
});
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
userLocation = new google.maps.LatLng(
position.coords.latitude,
position.coords.longitude
);
if (isWithinOregon(userLocation)) {
setupEventListeners(map, userLocation);
showModal();
} else {
alert(
"You are not within Oregon. This service is only available in Oregon."
);
}
},
() => {
alert("Geolocation service failed. Please allow location access.");
}
);
} else {
alert("Your browser doesn't support geolocation.");
}
}
function isWithinOregon(location) {
const lat = location.lat();
const lng = location.lng();
return (
lat >= 41.991794 &&
lat <= 46.292035 &&
lng >= -124.566244 &&
lng <= -116.463262
);
}
function setupEventListeners(map, location) {
document.getElementById("findHikingTrails").addEventListener("click", () => {
closeModal();
findPlaces(map, location, "hiking trail");
});
document.getElementById("findCampgrounds").addEventListener("click", () => {
closeModal();
findPlaces(map, location, "campground");
});
document.getElementById("infoButton").addEventListener("click", showModal);
document.getElementById("closeModal").addEventListener("click", closeModal);
window.addEventListener("click", (event) => {
if (event.target === document.getElementById("infoModal")) {
closeModal();
}
});
}
function showModal() {
document.getElementById("infoModal").style.display = "block";
}
function closeModal() {
document.getElementById("infoModal").style.display = "none";
}
function findPlaces(map, location, placeType) {
const request = {
location: location,
radius: "144841", // 90 miles in meters
type: placeType,
keyword: placeType,
};
const service = new google.maps.places.PlacesService(map);
service.nearbySearch(request, (results, status) => {
if (
status === google.maps.places.PlacesServiceStatus.OK &&
results.length > 0
) {
const index = Math.floor(Math.random() * results.length);
createMarkerForPlace(map, results[index]);
} else {
console.error(
`No ${placeType}s found or Places API returned an error: ${status}`
);
}
});
}
function createMarkerForPlace(map, place) {
const service = new google.maps.places.PlacesService(map);
service.getDetails(
{
placeId: place.place_id,
fields: [
"name",
"geometry",
"rating",
"formatted_address",
"photos",
"reviews",
],
},
(placeDetails, status) => {
if (status === google.maps.places.PlacesServiceStatus.OK) {
if (currentMarker) {
currentMarker.setMap(null);
}
if (currentInfoWindow) {
currentInfoWindow.close();
}
currentMarker = new google.maps.Marker({
map: map,
position: placeDetails.geometry.location,
title: placeDetails.name,
});
currentInfoWindow = new google.maps.InfoWindow({
content: buildInfoWindowContent(placeDetails),
});
currentMarker.addListener("click", () => {
adjustMapCenter(map, placeDetails.geometry.location);
currentInfoWindow.open(map, currentMarker);
cycleReviews(placeDetails.reviews);
});
adjustMapCenter(map, placeDetails.geometry.location);
currentInfoWindow.open(map, currentMarker);
cycleReviews(placeDetails.reviews);
} else {
console.error("Places API returned an error: " + status);
}
}
);
}
let touchStartX = 0;
let touchEndX = 0;
function handleTouchStart(event) {
touchStartX = event.changedTouches[0].screenX;
}
function handleTouchMove(event) {
touchEndX = event.changedTouches[0].screenX;
}
function handleTouchEnd() {
if (touchEndX < touchStartX) {
changeImage(1);
} else if (touchEndX > touchStartX) {
changeImage(-1);
}
}
function changeImage(direction) {
const hikingImageDiv = document.getElementById("hikingImage");
if (hikingImageDiv) {
const photoUrls = JSON.parse(hikingImageDiv.getAttribute("data-photos"));
let currentImageIndex = photoUrls.findIndex((url) =>
hikingImageDiv.style.backgroundImage.includes(url)
);
currentImageIndex =
(currentImageIndex + direction + photoUrls.length) % photoUrls.length;
hikingImageDiv.style.backgroundImage = `url('${photoUrls[currentImageIndex]}')`;
}
}
function adjustMapCenter(map, location) {
const projection = map.getProjection();
const worldPoint = projection.fromLatLngToPoint(location);
const screenWidth = window.innerWidth;
// Conditional value based on screen width
const offsetValue = screenWidth <= 480 ? -4 : -150;
const adjustedPoint = new google.maps.Point(
worldPoint.x,
worldPoint.y + offsetValue / Math.pow(2, map.getZoom())
);
map.setCenter(projection.fromPointToLatLng(adjustedPoint));
}
function buildInfoWindowContent(place) {
let content = `<div id="infoWindowContent" style="text-align: left; padding: 0px 8px px 8px; overflow-wrap: break-word; max-width: 300px">`;
content += `<h3>${place.name}</h3>`;
if (place.rating) {
const ratingStars = '<span style="color: gold;">\u2605</span>'.repeat(
Math.floor(place.rating)
);
content += `<p>${place.rating.toFixed(1)} ${ratingStars}</p>`;
}
if (place.photos && place.photos.length > 0) {
const photoUrls = place.photos.map((photo) =>
photo.getUrl({ maxWidth: 250, maxHeight: 250 })
);
content += `<div id="hikingImageContainer" style="position: relative; height: 150px;">
<div id="hikingImage" style="width: 100%; height: 100%; background-image: url('${
photoUrls[0]
}'); background-size: cover; background-position: center; border-radius: 4px;" data-photos='${JSON.stringify(
photoUrls
)}'></div>
<button onclick="changeImage(-1)" class="image-change-button" style="left: 0;">❮</button>
<button onclick="changeImage(1)" class="image-change-button" style="right: 0;">❯</button>
</div>`;
}
content += `<div id="reviewContainer">`;
if (place.reviews && place.reviews.length > 0) {
content += `"${place.reviews[0].text}" — ${place.reviews[0].author_name}`;
}
content += `</div>`;
content += `<p>${place.formatted_address}</p>`;
const mapsUrl = getMobileMapsUrl(
place.geometry.location.lat(),
place.geometry.location.lng(),
place.name
);
content += `<p><a href="${mapsUrl}" target="_blank" id="viewDetailsLink">View on Google Maps</a></p>`;
content += `</div>`;
setTimeout(() => {
const hikingImageDiv = document.getElementById("hikingImage");
if (hikingImageDiv) {
hikingImageDiv.addEventListener("touchstart", handleTouchStart, false);
hikingImageDiv.addEventListener("touchmove", handleTouchMove, false);
hikingImageDiv.addEventListener("touchend", handleTouchEnd, false);
}
}, 100);
return content;
}
let reviewInterval;
function cycleReviews(reviews) {
if (!reviews || reviews.length === 0) return;
clearInterval(reviewInterval);
let index = 0;
const reviewContainer = document.getElementById("reviewContainer");
reviewContainer.textContent = `"${reviews[index].text}" — ${reviews[index].author_name}`;
reviewInterval = setInterval(() => {
index = (index + 1) % reviews.length;
reviewContainer.textContent = `"${reviews[index].text}" — ${reviews[index].author_name}`;
}, 15000);
}
function getMobileMapsUrl(lat, lng, placeName) {
const encodedPlaceName = encodeURIComponent(placeName);
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
return `comgooglemaps://?q=${encodedPlaceName}¢er=${lat},${lng}&zoom=14`;
} else if (
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
) {
return `geo:${lat},${lng}?q=${encodedPlaceName}`;
} else {
return `https://www.google.com/maps/search/?api=1&query=${encodedPlaceName}¢er=${lat},${lng}`;
}
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.