"use strict";
// Variables for global usage
let map;
const initialZoom = 15;
const targetZoom = 17.8;
let salesCenterLat = 33.658600;
let salesCenterLng = -78.983632;
const zLevelHide = initialZoom;
const zoomLevelToRotate = 19;
const mapRotateDegrees = 90;
let disableClicks = false;
let lotAndFloorplanData = {};
const baseUrl = 'https://www.sayebrook.com';
const theme = 'sayebrook';
const markerIconURL = `/themes/sayebrook/modules/siteplan/images/sb-marker.svg`;
const markerHideZoom = initialZoom;
let markerArray = [];
let infoWindows = [];
const animationDelay = 1000;
const cookieName = "mapZoomPlayed";
let zoomLevel = initialZoom;
let sitePlanOverlay;
function initMap() {
const centerLat = 33.658600;
const centerLng = -78.983632;
const mapCenter = new google.maps.LatLng(centerLat, centerLng);
const siteSVG = document.querySelector('#siteplanSVG');
const siteMapBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(33.646681, -79.003506),
new google.maps.LatLng(33.674156, -78.962055)
);
const mapOptions = {
zoom: initialZoom,
center: mapCenter,
scrollwheel: true,
mapTypeControl: false,
fullscreenControl: true,
fullscreenControlOptions: {
position: google.maps.ControlPosition.RIGHT_BOTTOM,
},
streetViewControl: false,
controlSize: 30,
mapId: '58bb8e82c39f0270'
};
///'ae63ecc734886ff6'
map = new google.maps.Map(document.getElementById('map_canvas_full'), mapOptions);
class sitePlanSVGOverlay extends google.maps.OverlayView {
constructor(bounds, svg, map) {
super();
this.bounds = bounds;
this.svg = svg;
this.map = map;
this.setMap(map);
}
onAdd() {
const panes = this.getPanes();
panes.overlayLayer.appendChild(this.svg);
this.svg.style.display = 'block';
this.svg.style.position = 'absolute';
this.svg.style.zIndex = 1;
this.attachEvents();
}
attachEvents() {
const lotElements = this.svg.querySelectorAll('[id*="Lot"]');
lotElements.forEach(el => {
el.addEventListener('click', (e) => {
handleLotClick(e, el);
});
el.addEventListener('mouseover', (e) => {
handleLotMouseOver(e, el);
});
el.addEventListener('mouseout', (e) => {
handleLotMouseOut(e, el);
});
// Optional: Update tooltip position on mouse move
el.addEventListener('mousemove', (e) => {
updateTooltipPosition(e);
});
});
}
draw() {
const projection = this.getProjection();
const sw = projection.fromLatLngToDivPixel(this.bounds.getSouthWest());
const ne = projection.fromLatLngToDivPixel(this.bounds.getNorthEast());
Object.assign(this.svg.style, {
left: `${sw.x}px`,
top: `${ne.y}px`,
width: `${ne.x - sw.x}px`,
height: `${sw.y - ne.y}px`,
});
}
show() {
this.svg.style.display = 'block';
}
hide() {
this.svg.style.display = 'none';
}
}
sitePlanOverlay = new sitePlanSVGOverlay(siteMapBounds, siteSVG, map);
handleMapEvents(map, sitePlanOverlay);
//addLabelsOverlay(siteMapBounds);
//addMapKeyOverlay();
getMarkerData('site-plan');
getMarkerData('commercial/commercial-map');
map.addListener("zoom_changed", function () {
updateMarkerVisibility();
});
//// get user's location and add an icon to the map
//addUserLocationToMap(map);
} //init()
function updateMarkerVisibility() {
let currentZoom = map.getZoom();
markerArray.forEach(marker => {
let shouldBeVisible = currentZoom >= marker.markerZoomLevel; // Show only when zoom reaches marker's level
if (marker.getVisible() !== shouldBeVisible) {
marker.setVisible(shouldBeVisible);
}
});
// Close all open info windows when zooming
infoWindows.forEach(infoWindow => infoWindow.close());
}
function getMarkerData(filename='site-plan') {
fetch(`https://www.sayebrook.com/admin/sections/webpages/pages.cfc?method=getChildPagesJSON&filename=${filename}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
data.forEach(markerData => addCustomMarker(map, markerData));
})
.catch(error => console.error("Error loading marker data:", error));
}
function addCustomMarker(map, markerData) {
console.log('markerData:', markerData);
if (!markerData.LATITUDE || !markerData.LONGITUDE) return; // Skip if missing coordinates
let markerIcon = {
url: markerIconURL,
scaledSize: new google.maps.Size(26, 36),
anchor: new google.maps.Point(13, 36),
};
let marker = new google.maps.Marker({
position: { lat: parseFloat(markerData.LATITUDE), lng: parseFloat(markerData.LONGITUDE) },
map: map,
icon: markerIcon,
title: markerData.HEADLINE,
visible: map.getZoom() >= markerData.MAP_ZOOM_LEVEL
});
marker.markerZoomLevel = markerData.MAP_ZOOM_LEVEL; // Store marker-specific zoom level
// Info Window for Marker
let infoWindowContent = `
${markerData.HEADLINE}
`;
let infoWindowContentPlain = `${markerData.HEADLINE}
View Details`;
let infoWindow = new google.maps.InfoWindow({
//content: `${markerData.HEADLINE}
View Details`,
content: infoWindowContent,
pixelOffset: new google.maps.Size(0, 34)
});
marker.addListener("click", function () {
infoWindows.forEach(infoWindow => infoWindow.close());
infoWindow.open(map, marker);
});
map.addListener("click", function () {
infoWindow.close();
});
markerArray.push(marker);
infoWindows.push(infoWindow);
} //addCustomMarker
function animateZoom(currentZoom, finalZoom) {
let zoom = currentZoom;
// Create an interval to increase the zoom level gradually
const interval = setInterval(() => {
if (zoom >= finalZoom) {
clearInterval(interval);
} else {
zoom += .1; // Increment the zoom value (Adjust for smoother/slower zoom)
map.setZoom(Math.round(zoom));
}
}, 100); // Adjust interval time for the desired animation speed
}
function stepZoom() {
if (zoomLevel >= targetZoom) {
map.setZoom(targetZoom);
} else {
zoomLevel += 0.05; // Increment the zoom value for smoother zoom
map.setZoom(Math.round(zoomLevel));
requestAnimationFrame(stepZoom);
}
}
function loadGoogleMapsAPI() {
const apiKey = 'AIzaSyDZFYCp5Jo1nClFTm6EAFnuGS31HMsFJvw'; // Replace with your actual Google Maps API key
const script = document.createElement('script');
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=initMap&loading=async`;
script.async = true;
script.defer = true;
document.head.appendChild(script);
}
if (typeof google === 'undefined' || typeof google.maps === 'undefined') {
loadGoogleMapsAPI();
} else {
initMap();
}
function handleMapEvents(map, overlay) {
google.maps.event.addListener(map, 'zoom_changed', () => {
const zoomLevel = map.getZoom();
infoWindows.forEach(infoWindow => infoWindow.close());
/*if (zoomLevel >= zLevelHide) {
overlay.show();
} else {
overlay.hide();
}*/
});
}
////////////////////// add user location ///////////////////////////
function addUserLocationToMap(map) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
const userLocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
// Custom SVG as Data URL
const userIcon = {
url: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(`
`),
scaledSize: new google.maps.Size(20, 32), // Resize if needed
anchor: new google.maps.Point(10, 32) // Adjust anchor point if necessary
};
// Add user marker
new google.maps.Marker({
position: userLocation,
map: map,
title: "Your Location",
icon: userIcon
});
// Center the map on the user's location
map.setCenter(userLocation);
map.setZoom(15); // Adjust zoom level
}, function (error) {
console.error("Error getting user location:", error);
});
} else {
console.error("Geolocation is not supported by this browser.");
}
}
async function getLotsAndFloorplans(phase = 0) {
try {
const url = new URL(`${baseUrl}/themes/${theme}/modules/siteplan/siteplan.cfc?method=getLotsAndFloorplans`);
url.searchParams.append('phase', phase);
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const lot_data = await response.json();
return lot_data;
} catch (error) {
console.error('Failed to fetch lots and floorplans:', error);
return null;
}
}
/*
getLotsAndFloorplans().then(data => {
lotAndFloorplanData = JSON.parse(data);
setLotStatus();
}).catch(error => {
console.error('Error fetching data:', error);
});
*/
function setLotStatus() {
console.log('lotAndFloorplanData',lotAndFloorplanData);
lotAndFloorplanData.lots.forEach(lot => {
$('#Lot' + lot.lotNumber).addClass(lot.lotSalesStatus);
$('#Lot' + lot.lotNumber).attr('data-status', lot.lotSalesStatus);
});
// Attach events after lot status is set
/*if (sitePlanOverlay) {
sitePlanOverlay.attachEvents();
}*/
}
function handleLotClick(e, element) {
e.stopPropagation();
if (!disableClicks) {
createAndShowLotModal(element.id);
}
}
function createAndShowLotModal(lotNumber) {
const lotInfo = getLotByNumber(lotAndFloorplanData, lotNumber.replace('Lot', ''));
if (lotInfo) {
console.log('lotInfo',lotInfo);
const lotSize = Number(lotInfo.lotSize).toLocaleString();
const salesStatus = '';
$('.modal').remove();
let modalContent = `
Floor Plans
Available for Home Site ${lotInfo.lotNumber}
${generateFloorPlanCards(lotInfo)}
`;
const modalHtml = `
`;
$('body').append(modalHtml);
$('#lotModal').modal('show');
}
}
function generateFloorPlanCards(lotInfo) {
let floorPlanRow = ``;
lotInfo.lotFloorPlans.forEach(plan => {
if (plan != null) {
const heatedSqFt = Number(plan.floorHeatedSqFt).toLocaleString();
const totalSqFt = plan.floorTotalSqFt ? Number(plan.floorTotalSqFt).toLocaleString() : 0;
let cardHtml = `
${plan.floorPlanName}
${heatedSqFt} Heated SqFt ${totalSqFt != '' ? `| ${totalSqFt} Total SqFt` : ``}
- ${plan.floorPlanBeds} Beds
- ${plan.floorPlanBaths} Baths
- ${plan.floorPlanHalfBaths} Half Bath
- ${plan.floorPlanStories} Stories
`;
if (lotInfo.showLotHomePrice) {
cardHtml += `
From ${ currency(plan.lotAndHomePrice, {precision: 0}).format() }
`;
}
cardHtml += `
`;
floorPlanRow += cardHtml;
}
});
return floorPlanRow;
}
// Add event listener once for floor plan buttons
$(document).on('click', '.btn-floor-plan', function (e) {
const floorPlanId = $(this).data('floor-plan-id');
createAndShowFloorPlanModal(floorPlanId);
});
function createAndShowFloorPlanModal(floorPlanId) {
const getFloorPlanDetails = getFloorPlanById(lotAndFloorplanData, floorPlanId);
if (getFloorPlanDetails) {
$('.floorplan-modal').remove();
const heatedSqFt = Number(getFloorPlanDetails.floorHeatedSqFt).toLocaleString();
let floorPlanDetailsNoPriceHTML = `
`;
if (getFloorPlanDetails.lotAndHomePrice > 0) {
floorPlanDetailsNoPriceHTML += `
From ${ currency(getFloorPlanDetails.lotAndHomePrice, {precision: 0}).format() }
`;
} else {
//floorPlanDetailsNoPriceHTML += `
Call For Price
`;
}
floorPlanDetailsNoPriceHTML += `
${getFloorPlanDetails.floorPlanName}
${getFloorPlanDetails.floorPlanBeds ? `- ${getFloorPlanDetails.floorPlanBeds} Beds
` : ''}
${getFloorPlanDetails.floorPlanBaths ? `- ${getFloorPlanDetails.floorPlanBaths} Baths
` : ''}
${getFloorPlanDetails.floorPlanHalfBaths ? `- ${getFloorPlanDetails.floorPlanHalfBaths} Half Baths
` : ''}
${getFloorPlanDetails.floorPlanStories ? `- ${getFloorPlanDetails.floorPlanStories} Stories
` : ''}
${heatedSqFt ? `- ${heatedSqFt} Heated SqFt
` : ''}
${getFloorPlanDetails.floorPlanDetails}
`;
const floorPlanDetailsHTML = `
${getFloorPlanDetails.floorBasePrice > 0 ? `
Starting at $${getFloorPlanDetails.floorBasePrice}
` : `
Call For Price
`}
${getFloorPlanDetails.floorPlanName}
${getFloorPlanDetails.floorPlanBeds ? `- ${getFloorPlanDetails.floorPlanBeds} Beds
` : ''}
${getFloorPlanDetails.floorPlanBaths ? `- ${getFloorPlanDetails.floorPlanBaths} Baths
` : ''}
${getFloorPlanDetails.floorPlanHalfBaths ? `- ${getFloorPlanDetails.floorPlanHalfBaths} Half Baths
` : ''}
${getFloorPlanDetails.floorPlanStories ? `- ${getFloorPlanDetails.floorPlanStories} Stories
` : ''}
${heatedSqFt ? `- ${heatedSqFt} Heated SqFt
` : ''}
${getFloorPlanDetails.floorPlanDetails}
`;
$('body').append(floorPlanDetailsNoPriceHTML);
$('#floorPlanModal').modal('show');
$('#floorPlanModal').on('click', '.close', function () {
$('#floorPlanModal').modal('hide');
});
}
}
function getLotByNumber(data, lotNumber) {
const lots = data.lots;
for (let lot of lots) {
if (lot.lotNumber === lotNumber) {
return lot;
}
}
return null;
}
function getFloorPlanById(data, floorPlanId) {
for (let lot of data.lots) {
for (let floorPlan of lot.lotFloorPlans) {
if (floorPlan.floorPlanID === floorPlanId) {
return floorPlan;
}
}
}
return null;
}
function addLabelsOverlay(siteMapBounds) {
var srcImage = `/themes/${theme}/modules/siteplan/site-plan-labels.svg`;
let labelOverlay = new google.maps.GroundOverlay(srcImage,siteMapBounds);
console.log('labelOverlay',labelOverlay);
labelOverlay.setMap(map);
google.maps.event.addListenerOnce(map, 'idle', function(){
labelOverlay.setOptions({ zIndex: 10000 });
});
}//// end addLabelsOverlay
function addMapKeyOverlay() {
let srcImage = `/themes/${theme}/modules/siteplan/images/map-key.svg`;
const mapKeyBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(31.038359, -81.413328), // Southwest corner
new google.maps.LatLng(31.039054, -81.412435) // Northeast corner
);
let mapKeyOverlay = new google.maps.GroundOverlay(srcImage,mapKeyBounds);
mapKeyOverlay.setMap(map);
google.maps.event.addListenerOnce(map, 'idle', function(){
mapKeyOverlay.setOptions({ zIndex: 1000 });
});
}//// end addMapKeyOverlay
function displayMapInfo(info) {
const div = document.getElementById('map-info');
console.log(div);
if (div != null){
div.innerHTML += info + '
';
}
}
///// helpers
function formatPrice(num) {
if (num >= 1000000) {
// Number is at least 1 million
let millions = num / 1000000;
let rounded = millions.toFixed(1);
return `$${rounded} M`;
} else if (num >= 100000) {
// Number is between 100,000 and 999,999
let thousands = Math.floor(num / 1000);
let rounded = Math.floor(thousands / 10) * 10;
return `$${rounded} K`;
} else {
// For numbers less than 100,000, output the exact number with a dollar sign
return `$${num}`;
}
}
function formatStatus(status) {
return status.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
///// cookies
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
}
function getCookie(name) {
const cname = name + "=";
const decodedCookie = decodeURIComponent(document.cookie);
const ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1);
}
if (c.indexOf(cname) === 0) {
return c.substring(cname.length, c.length);
}
}
return "";
}
//////// tooltip for sales status ////////
// Create tooltip div
const tooltipDiv = document.createElement('div');
tooltipDiv.id = 'tooltip';
Object.assign(tooltipDiv.style, {
position: 'absolute',
pointerEvents: 'none',
background: 'rgba(0, 0, 0, 0.7)',
color: 'white',
padding: '3px 6px',
borderRadius: '4px',
display: 'none',
zIndex: '10000',
fontSize: '10px'
});
document.body.appendChild(tooltipDiv);
function handleLotMouseOverORIG(e, element) {
e.stopPropagation();
const status = element.getAttribute('data-status') || 'Call Us';
const formattedStatus = formatStatus(status);
showTooltip(e, formattedStatus);
}
function handleLotMouseOver(e, element) {
e.stopPropagation();
let status = element.getAttribute('data-status');
if (!status) {
// If data-status is not yet set, default to a placeholder or fetch the status dynamically if possible
status = 'Status Loading...';
} else {
status = formatStatus(status);
}
const formattedStatus = formatStatus(status);
showTooltip(e, formattedStatus);
}
function handleLotMouseOut(e, element) {
e.stopPropagation();
hideTooltip();
}
function showTooltip(e, status) {
tooltipDiv.innerHTML = status;
const mouseX = e.pageX;
const mouseY = e.pageY;
tooltipDiv.style.left = (mouseX + 10) + 'px';
tooltipDiv.style.top = (mouseY + 10) + 'px';
tooltipDiv.style.display = 'block';
}
function hideTooltip() {
tooltipDiv.style.display = 'none';
}
// Optional: Update tooltip position on mouse move
function updateTooltipPosition(e) {
const mouseX = e.pageX;
const mouseY = e.pageY;
tooltipDiv.style.left = (mouseX + 10) + 'px';
tooltipDiv.style.top = (mouseY + 10) + 'px';
}