Map in Progress * { -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; -webkit-user-select: none; -ms-user-select: none; user-select: none; } html, body { margin: 0; padding: 0; height: 100%; width: 100%; overflow: hidden; font-family: sans-serif; } #map-box { width: 100%; height: 100vh; max-height: 100vh; border: none; background-color: #011c6b; overflow: hidden; display: flex; touch-action: none; cursor: pointer; position: relative; } select { max-width: 50vw; position: absolute; top: 10px; left: 10px; z-index: 20; padding: 6px 6px; font-size: 14px; border-radius: 6px; border: 2px solid #9b9b9b; background: rgb(255, 255, 255); box-shadow: 0 2px 6px rgba(0,0,0,0.2); } #plant-select{ max-width: 50vw; padding: 6px; font-size: 14px; border-radius: 6px; border: 2px solid #9b9b9b; background: rgb(255, 255, 255); box-shadow: 0 2px 6px rgba(0,0,0,0.2); margin: 0; } #back-button{ background: rgb(255, 255, 255); height:30px; border: 2px solid #9b9b9b; border-radius: 6px; box-shadow: 0 2px 6px rgba(0,0,0,0.2); color: #333; font-size: 14px; font-family: sans-serif; padding: 0 10px; display: flex; align-items: center; text-decoration: none; white-space: nowrap; } #manual-coords input { padding: 6px; font-size: 14px; border-radius: 4px; border: 1px solid #9b9b9b; } #manual-coords button { padding: 6px 10px; font-size: 14px; border-radius: 4px; border: 1px solid #9b9b9b; background: #f0f0f0; cursor: pointer; } #manual-coords button:hover { background: #e0e0e0; } .flash { fill: yellow !important; transition: fill 0.2s ease-in-out; } svg { width: 100%; height: 100%; display: block; } .zone { fill: rgb(75, 232, 91); opacity: 0.6; stroke: rgb(12, 28, 122); stroke-width: 2; transition: all 0.2s ease-in-out; display: none; /* Hide zones by default */ } .zone:not(.hidden) { display: block; /* Show zones when not hidden */ } .zone:hover, .zone:focus { fill: rgb(237, 233, 3); opacity: 0.5; stroke: rgb(12, 28, 122); stroke-width: 3; cursor: pointer; } .zone:active { opacity: 1; stroke: red; stroke-width: 4; } #zone-popup { height: 25vh; /* Fixed size; adjust as needed */ overflow-y: auto; /* Allow scrolling if content overflows */ position: fixed; bottom: 0; width: 100%; background: white; border-top: 1px solid #ccc; display: none; /* Hidden by default */ transition: transform 0.3s ease-in-out; /* Smooth slide animation */ transform: translateY(0); /* Default position */ } #zone-popup.minimized { transform: translateY(calc(100% - 60px)); /* Show only the button area */ height: 60px; /* Height of minimized state */ overflow: hidden; } #zone-popup .popup-minimize { position: absolute; top: 5px; right: 5px; font-size: 16px; padding: 5px; width: 30px; height: 30px; text-align: center; border: none; background: #f0f0f0; cursor: pointer; border-radius: 4px; } #zone-popup .popup-minimize:hover { background: #e0e0e0; } #zone-popup .popup-content { padding: 30px; } #zone-popup h4 { margin: 0 0 10px; font-size: 18px; font-weight: bold; } #zone-popup ul { margin: 0; padding: 0 0 0 20px; list-style-type: disc; } #zone-popup li { margin: 5px 0; } #zone-popup a { color: #0066cc; text-decoration: none; } #zone-popup a:hover { text-decoration: underline; } a { color: #007BFF; text-decoration: none; } a:hover { text-decoration: underline; } #user-marker, #accuracy-circle { pointer-events: none; /* Prevents interfering with zone clicks or panning */ } #status { position: absolute; bottom: 10px; left: 10px; z-index: 20; color: white; font-size: 14px; background: rgba(0, 0, 0, 0.7); padding: 5px 10px; border-radius: 4px; } .help-button { position: absolute; top: 10px; right: 10px; z-index: 20; width: 30px; height: 30px; border-radius: 4px; background: #f0f0f0; border: 2px solid #9b9b9b; box-shadow: 0 2px 6px rgba(0,0,0,0.2); color: #333; font-size: 18px; font-weight: bold; display: flex; align-items: center; justify-content: center; cursor: pointer; } .help-popup { display: none; position: fixed; transform: translate(-50%, -50%); background: white; border: 1px solid #ccc; padding: 20px; width: 400px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); z-index: 1000; max-width: 90vw; max-height: 90vh; overflow-y: auto; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); } .help-popup-content { margin-bottom: 20px; } .help-popup-content h2 { margin: 0 0 10px; font-size: 20px; font-weight: bold; } .help-popup-content ul { margin: 0; padding: 0 0 0 20px; list-style-type: disc; } .help-popup-content li { margin: 5px 0; } .close-help-button { background: #0066cc; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; display: block; margin: 0 auto; width: 100%; } .close-help-button:hover { background: #0055a5; } .overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 999; } window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-VVMT4J6LN6');
What are you looking for?
<!---->
Requesting location...

How to Use the Scottsdale Farms Map

  • Navigate the Map: Drag the map around with your finger or mouse. Pinch or scroll to zoom in and out.
  • Search for Items: Use the selection box to search for something. Sections containing the desired item will flash yellow and be highlighted green.
  • GPS Location (Optional): Enable GPS to navigate the property. Your location is shown as a red dot with a blue accuracy circle on the map. Note: GPS accuracy may vary when indoors.
  • Zone Details: Click on a highlighted zone to open a popup window displaying the items located within the section.
const svg = document.getElementById('svg-map'); let isPanning = false; let startX = 0; let startY = 0; let velocityX = 0; let velocityY = 0; let momentumActive = false; let lastTouchMove = 0; let viewBox = { x: 1400, y: 600, width: 1700, height: 1368 }; const initialViewBoxWidth = 1700; // Initial viewBox width for scaling reference const imageWidth = 5000; const imageHeight = 4037; const minZoom = 200; const maxZoom = 3000; const baseMarkerRadius = 30; // Base radius of the user marker const baseStrokeWidth = 5; // Base stroke width of the user marker let isFirstHelpClose = true; // Flag to track first help popup close let plantLocations = []; // Will be populated from Google Sheet async function loadPlantLocations() { const APPS_SCRIPT_URL = 'https://script.google.com/macros/s/AKfycbxjTkjnuc7V6PVlYXZsjuEJyrSSPAYY-kBGR0MZZj8oC_YhxTQIyN0oZv2aSJZEoRDffw/exec'; // { const plant = String(row.plant ?? row.Plant ?? '').trim(); const information = String(row.Information ?? row.information ?? '').trim(); const locations = String(row.location ?? row.Location ?? '').trim(); if (!plant || !locations) return []; return locations.split(',').map(loc => ({ plant, Information: information, location: loc.trim() })); }); console.log(`Loaded ${plantLocations.length} plant locations`); } catch (err) { console.error('Failed to load plant locations:', err); document.getElementById('status').textContent = 'Error loading map data. Please refresh.'; } } const popup = document.getElementById('zone-popup'); function applyZoom(scale, originX, originY) { const newWidth = viewBox.width * scale; const newHeight = viewBox.height * scale; if (newWidth >= minZoom && newWidth { if (Math.abs(velocityX) < 0.10 && Math.abs(velocityY) imageWidth ? (viewBox.width - imageWidth) / 2 : 0; const overflowY = viewBox.height > imageHeight ? (viewBox.height - imageHeight) / 2 : 0; const minX = viewBox.width > imageWidth ? -overflowX : 0; const maxX = viewBox.width > imageWidth ? imageWidth + overflowX - viewBox.width : imageWidth - viewBox.width; const minY = viewBox.height > imageHeight ? -overflowY : 0; const maxY = viewBox.height > imageHeight ? imageHeight + overflowY - viewBox.height : imageHeight - viewBox.height; viewBox.x = Math.min(Math.max(viewBox.x, minX), maxX); viewBox.y = Math.min(Math.max(viewBox.y, minY), maxY); } function updateMarkerSize() { const marker = document.getElementById('user-marker'); if (marker) { const zoomFactor = viewBox.width / initialViewBoxWidth; const scaledRadius = baseMarkerRadius * zoomFactor; const scaledStrokeWidth = baseStrokeWidth * zoomFactor; marker.setAttribute('r', scaledRadius); marker.setAttribute('stroke-width', scaledStrokeWidth); } } function highlightAndZoomToPlant(plantName) { const zones = document.querySelectorAll('.zone'); const cleanPlantName = plantName.replace(/]+>/g, ''); const matchingPlants = plantLocations.filter(item => item.plant.replace(/]+>/g, '').toLowerCase() === cleanPlantName.toLowerCase() ); console.log(matchingPlants); const matchingLocations = [...new Set(matchingPlants.map(item => item.location))]; const targets = matchingLocations .map(location => document.getElementById(location)) .filter(target => target); zones.forEach(zone => zone.classList.add('hidden')); if (!matchingLocations.length || !targets.length) { popup.innerHTML = ` `; popup.style.display = 'block'; popup.style.maxHeight = '30vh'; popup.classList.remove('minimized'); return; } targets.forEach(target => target.classList.remove('hidden')); let flashes = 0; const flashInterval = setInterval(() => { targets.forEach(target => target.classList.toggle('flash')); flashes++; if (flashes === 10) { clearInterval(flashInterval); targets.forEach(target => target.classList.remove('flash')); } }, 350); const bboxes = targets.map(target => target.getBBox()); const combinedBbox = { x: Math.min(...bboxes.map(bbox => bbox.x)), y: Math.min(...bboxes.map(bbox => bbox.y)), xMax: Math.max(...bboxes.map(bbox => bbox.x + bbox.width)), yMax: Math.max(...bboxes.map(bbox => bbox.y + bbox.height)) }; combinedBbox.width = combinedBbox.xMax - combinedBbox.x; combinedBbox.height = combinedBbox.yMax - combinedBbox.y; const zoomFactor = 10; const targetWidth = combinedBbox.width * zoomFactor; const targetHeight = combinedBbox.height * zoomFactor; const targetX = combinedBbox.x + (combinedBbox.width / 2); const targetY = combinedBbox.y + (combinedBbox.height / 2); const clampedWidth = Math.min(Math.max(targetWidth, minZoom), maxZoom); const aspectRatio = imageHeight / imageWidth; const clampedHeight = clampedWidth * aspectRatio; const start = { ...viewBox }; const end = { x: targetX - clampedWidth / 2, y: targetY - clampedHeight / 4, width: clampedWidth, height: clampedHeight }; const duration = 300; const startTime = performance.now(); function animate(time) { const t = Math.min((time - startTime) / duration, 1); const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; viewBox.x = start.x + (end.x - start.x) * ease; viewBox.y = start.y + (end.y - start.y) * ease; viewBox.width = start.width + (end.width - start.width) * ease; viewBox.height = start.height + (end.height - start.height) * ease; svg.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`); updateMarkerSize(); // Update marker size during zoom animation if (t item.location === firstLocation) .map(item => ({ plant: item.plant, Information: item.Information })); const plantListItems = matchingZonePlants.length > 0 ? matchingZonePlants.map(item => `
  • ${item.plant}${item.Information ? `
    ${item.Information}` : ''}
  • `).join('') : '
  • No plants found
  • '; popup.innerHTML = ` `; popup.style.display = 'block'; popup.style.maxHeight = '30vh'; popup.classList.remove('minimized'); } function resetMap() { const zones = document.querySelectorAll('.zone'); zones.forEach(zone => zone.classList.add('hidden')); popup.style.display = 'none'; document.getElementById('plant-select').value = ''; } function openHelpPopup() { document.getElementById('help-popup').style.display = 'block'; document.getElementById('help-overlay').style.display = 'block'; } function closeHelpPopup() { document.getElementById('help-popup').style.display = 'none'; document.getElementById('help-overlay').style.display = 'none'; if (isFirstHelpClose && lastSvgX !== 0 && lastSvgY !== 0) { isFirstHelpClose = false; const targetWidth = 1000; // Slightly zoomed in const aspectRatio = imageHeight / imageWidth; const targetHeight = targetWidth * aspectRatio; const start = { ...viewBox }; const end = { x: lastSvgX - targetWidth / 2, y: lastSvgY - targetHeight / 2, width: targetWidth, height: targetHeight }; const duration = 300; const startTime = performance.now(); function animate(time) { const t = Math.min((time - startTime) / duration, 1); const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; viewBox.x = start.x + (end.x - start.x) * ease; viewBox.y = start.y + (end.y - start.y) * ease; viewBox.width = start.width + (end.width - start.width) * ease; viewBox.height = start.height + (end.height - start.height) * ease; svg.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`); updateMarkerSize(); if (t { isPanning = true; startX = e.clientX; startY = e.clientY; velocityX = 0; velocityY = 0; }); svg.addEventListener('mouseup', () => { isPanning = false; startMomentum(); }); svg.addEventListener('mouseleave', () => { isPanning = false; }); svg.addEventListener('mousemove', (e) => { if (!isPanning) return; const scaleX = viewBox.width / svg.clientWidth; const scaleY = viewBox.height / svg.clientHeight; const scale = Math.max(scaleX, scaleY); const dx = (e.clientX - startX) * scale; const dy = (e.clientY - startY) * scale; viewBox.x -= dx; viewBox.y -= dy; clampViewBox(); svg.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`); velocityX = dx * 0.4; velocityY = dy * 0.4; startX = e.clientX; startY = e.clientY; }); svg.addEventListener('wheel', (e) => { e.preventDefault(); const scale = e.deltaY > 0 ? 1.1 : 0.9; const pt = svg.createSVGPoint(); pt.x = e.clientX; pt.y = e.clientY; const cursor = pt.matrixTransform(svg.getScreenCTM().inverse()); applyZoom(scale, cursor.x, cursor.y); }, { passive: false }); let touchStart = []; svg.addEventListener('touchstart', (e) => { if (e.touches.length === 1) { isPanning = true; startX = e.touches[0].clientX; startY = e.touches[0].clientY; lastTouchMove = Date.now(); } else if (e.touches.length === 2) { touchStart = [...e.touches].map(t => ({ x: t.clientX, y: t.clientY })); } }); svg.addEventListener('touchmove', (e) => { e.preventDefault(); if (isPanning && e.touches.length === 1) { const now = Date.now(); const scaleX = viewBox.width / svg.clientWidth; const scaleY = viewBox.height / svg.clientHeight; const scale = Math.max(scaleX, scaleY); const dx = (e.touches[0].clientX - startX) * scale; const dy = (e.touches[0].clientY - startY) * scale; viewBox.x -= dx; viewBox.y -= dy; clampViewBox(); svg.setAttribute('viewBox', `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`); velocityX = dx * 0.4; velocityY = dy * 0.4; startX = e.touches[0].clientX; startY = e.touches[0].clientY; lastTouchMove = now; } else if (e.touches.length === 2 && touchStart.length === 2) { const distStart = Math.hypot( touchStart[0].x - touchStart[1].x, touchStart[0].y - touchStart[1].y ); const distNow = Math.hypot( e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY ); const scale = distStart / distNow; const currMidX = (e.touches[0].clientX + e.touches[1].clientX) / 2; const currMidY = (e.touches[0].clientY + e.touches[1].clientY) / 2; const pt = svg.createSVGPoint(); pt.x = currMidX; pt.y = currMidY; const svgPoint = pt.matrixTransform(svg.getScreenCTM().inverse()); applyZoom(scale, svgPoint.x, svgPoint.y); touchStart = [...e.touches].map(t => ({ x: t.clientX, y: t.clientY })); } }, { passive: false }); svg.addEventListener('touchend', () => { isPanning = false; touchStart = []; startMomentum(); }); svg.addEventListener('click', function (e) { if (e.target.classList.contains('zone')) { const zone = e.target; const zoneID = zone.getAttribute('id'); const href = zone.getAttribute('data-href'); const label = zone.getAttribute('aria-label') || 'No Zone Found'; const matchingPlants = plantLocations .filter(item => item.location === zoneID) .map(item => ({ plant: item.plant, Information: item.Information })); const plantListItems = matchingPlants.length > 0 ? matchingPlants.map(item => `
  • ${item.plant}${item.Information ? `
    ${item.Information}` : ''}
  • `).join('') : '
  • No plants found
  • '; popup.innerHTML = ` `; popup.style.display = 'block'; popup.style.maxHeight = '30vh'; popup.classList.remove('minimized'); } }); function populatePlantSelect() { const select = document.getElementById('plant-select'); select.innerHTML = 'What are you looking for?'; const plantMap = new Map(); plantLocations .filter(item => item.plant !== '') .forEach(item => { const cleanName = item.plant.replace(/]+>/g, ''); plantMap.set(cleanName, item.plant); }); const uniquePlants = [...plantMap.entries()]; uniquePlants.forEach(([cleanName, originalPlant]) => { const option = document.createElement('option'); option.value = originalPlant; option.textContent = cleanName; select.appendChild(option); }); } document.addEventListener('DOMContentLoaded', async () => { try { const zones = document.querySelectorAll('.zone'); if (!svg) { console.error('SVG map element not found'); return; } if (!popup) { console.error('Zone popup element not found'); return; } // Show help popup on page load await loadPlantLocations(); // Show help popup on page load document.getElementById('help-popup').style.display = 'block'; document.getElementById('help-overlay').style.display = 'block'; resetMap(); populatePlantSelect(); document.getElementById('plant-select').addEventListener('change', function () { const selectedId = this.value; console.log(selectedId); if (!selectedId) { resetMap(); return; } highlightAndZoomToPlant(selectedId); }); popup.addEventListener('click', (e) => { if (e.target.classList.contains('popup-minimize')) { const isMinimized = popup.classList.contains('minimized'); popup.classList.toggle('minimized'); e.target.textContent = isMinimized ? '-' : '+'; } }); document.getElementById('plot-manual-coords')?.addEventListener('click', () => { const latInput = document.getElementById('manual-lat')?.value; const lngInput = document.getElementById('manual-lng')?.value; const lat = parseFloat(latInput); const lng = parseFloat(lngInput); if (!isNaN(lat) && !isNaN(lng)) { const position = { coords: { latitude: lat, longitude: lng, accuracy: 0 } }; plotPosition(position, true); } else { const status = document.getElementById('status'); status.textContent = 'Invalid coordinates entered.'; } }); } catch (error) { console.error('Initialization error:', error); document.getElementById('status').textContent = 'Error loading map. Please try again.'; } }); const MAP_BOUNDS = { minLat: 34.159362, maxLat: 34.163412, minLng: -84.333950, maxLng: -84.328934 }; const IMAGE_WIDTH = 5000; const IMAGE_HEIGHT = 4037; const metersPerDegLat = 111132; const avgLat = (MAP_BOUNDS.minLat + MAP_BOUNDS.maxLat) / 2; const metersPerDegLng = metersPerDegLat * Math.cos(avgLat * Math.PI / 180); const deltaLat = MAP_BOUNDS.maxLat - MAP_BOUNDS.minLat; const deltaLng = MAP_BOUNDS.maxLng - MAP_BOUNDS.minLng; const scaleX = IMAGE_WIDTH / deltaLng; const scaleY = IMAGE_HEIGHT / deltaLat; const pxPerMeterX = scaleX / metersPerDegLng; const pxPerMeterY = scaleY / metersPerDegLat; const averagePxPerMeter = (pxPerMeterX + pxPerMeterY) / 2; let watchId = null; let lastPosition = null; let lastSvgX = 0; let lastSvgY = 0; function startWatchingLocation() { const status = document.getElementById('status'); if (navigator.geolocation) { watchId = navigator.geolocation.watchPosition( (position) => plotPosition(position, false), showError, { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 } ); status.textContent = "Watching location..."; } else { status.textContent = "Geolocation is not supported by this browser."; } } function stopWatchingLocation() { if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; const status = document.getElementById('status'); status.textContent = "Stopped watching location."; } } function plotPosition(position, isManual = false) { const lat = position.coords.latitude; const lng = position.coords.longitude; const accuracy = position.coords.accuracy || 0; const status = document.getElementById('status'); status.textContent = isManual ? `Manual Location: Lat ${lat.toFixed(6)}, Lng ${lng.toFixed(6)}` : `Location: Lat ${lat.toFixed(6)}, Lng ${lng.toFixed(6)} (Accuracy: ${accuracy.toFixed(0)}m)`; if (!isManual && lastPosition && calculateDistance(lastPosition, position) = 0 && normX = 0 && normY <= 1) { const svgX = normX * IMAGE_WIDTH; const svgY = normY * IMAGE_HEIGHT; lastSvgX = svgX; lastSvgY = svgY; let accuracyMarker = document.getElementById('accuracy-circle'); if (!accuracyMarker) { accuracyMarker = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); accuracyMarker.setAttribute('id', 'accuracy-circle'); accuracyMarker.setAttribute('fill', 'blue'); accuracyMarker.setAttribute('fill-opacity', '0.2'); accuracyMarker.setAttribute('stroke', 'blue'); accuracyMarker.setAttribute('stroke-width', '2'); svg.appendChild(accuracyMarker); } const r = accuracy * averagePxPerMeter; accuracyMarker.setAttribute('cx', svgX); accuracyMarker.setAttribute('cy', svgY); accuracyMarker.setAttribute('r', r); accuracyMarker.style.transition = 'cx 0.5s ease, cy 0.5s ease, r 0.5s ease'; let marker = document.getElementById('user-marker'); if (!marker) { marker = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); marker.setAttribute('id', 'user-marker'); marker.setAttribute('fill', 'red'); marker.setAttribute('stroke', 'white'); marker.setAttribute('stroke-width', baseStrokeWidth); svg.appendChild(marker); } marker.setAttribute('cx', svgX); marker.setAttribute('cy', svgY); updateMarkerSize(); marker.style.transition = 'cx 0.5s ease, cy 0.5s ease'; } else { status.textContent += ' (Location is outside map bounds)'; } } function calculateDistance(pos1, pos2) { const R = 6371000; const dLat = (pos2.coords.latitude - pos1.coords.latitude) * Math.PI / 180; const dLng = (pos2.coords.longitude - pos1.coords.longitude) * Math.PI / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(pos1.coords.latitude * Math.PI / 180) * Math.cos(pos2.coords.latitude * Math.PI / 180) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } function showError(error) { const status = document.getElementById('status'); switch (error.code) { case error.PERMISSION_DENIED: status.textContent = "User denied the request for Geolocation."; break; case error.POSITION_UNAVAILABLE: status.textContent = "Location information is unavailable."; break; case error.TIMEOUT: status.textContent = "The request to get user location timed out."; break; case error.UNKNOWN_ERROR: status.textContent = "An unknown error occurred."; break; } } startWatchingLocation(); window.addEventListener('beforeunload', stopWatchingLocation);