/
varsl
/
HTML-CSS
Обзор
Документация
Войти
/
varsl
/
HTML-CSS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/js/map.js
712 строк
25 KB
varsl
Добавление JavaScript логики
21 дек 2025, 23:16
21 дек 2025, 23:16
9dd2130
Код
Авторство
О чём код?
class ZooMap { constructor() { this.map = null; this.markers = []; this.userMarker = null; this.zooCoordinates = [55.761173, 37.578433]; this.init(); } init() { this.initMap(); this.loadZooAttractions(); this.setupEventListeners(); this.getUserLocation(); } initMap() { this.map = L.map('map').setView(this.zooCoordinates, 15); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>', maxZoom: 19 }).addTo(this.map); this.addZooMarker(); this.addZooBoundary(); } addZooMarker() { const zooIcon = L.divIcon({ className: 'zoo-marker', html: '<i class="fas fa-paw"></i>', iconSize: [40, 40], iconAnchor: [20, 40] }); const marker = L.marker(this.zooCoordinates, { icon: zooIcon }).addTo(this.map); marker.bindPopup(` <div class="map-popup"> <h3>Зоопарк Дикий Мир</h3> <p><i class="fas fa-map-marker-alt"></i> г. Москва, ул. Зоологическая, 123</p> <p><i class="fas fa-clock"></i> Ежедневно 9:00 - 20:00</p> <p><i class="fas fa-phone"></i> +7 (495) 123-45-67</p> <div class="popup-buttons"> <button class="btn btn-small btn-primary" id="getDirections"> <i class="fas fa-directions"></i> Маршрут </button> <button class="btn btn-small btn-outline" id="viewDetails"> <i class="fas fa-info-circle"></i> Подробнее </button> </div> </div> `); this.markers.push(marker); marker.on('popupopen', () => { document.getElementById('getDirections').addEventListener('click', () => { this.showDirections(); }); document.getElementById('viewDetails').addEventListener('click', () => { window.location.href = 'about.html'; }); }); } addZooBoundary() { const boundary = [ [55.7605, 37.5770], [55.7605, 37.5800], [55.7620, 37.5800], [55.7620, 37.5770] ]; L.polygon(boundary, { color: '#2E7D32', weight: 2, fillColor: '#2E7D32', fillOpacity: 0.1 }).addTo(this.map).bindTooltip('Территория зоопарка'); } async loadZooAttractions() { try { const response = await fetch('assets/data/attractions.json'); const attractions = await response.json(); this.addAttractionMarkers(attractions); } catch (error) { console.error('Ошибка загрузки аттракционов:', error); this.loadDefaultAttractions(); } } addAttractionMarkers(attractions) { attractions.forEach(attraction => { const icon = this.getAttractionIcon(attraction.type); const marker = L.marker([attraction.lat, attraction.lng], { icon }).addTo(this.map); const popupContent = ` <div class="map-popup attraction-popup"> <h3>${attraction.name}</h3> <p class="attraction-type"><i class="fas fa-${this.getAttractionIconName(attraction.type)}"></i> ${this.getAttractionTypeName(attraction.type)}</p> <p>${attraction.description}</p> ${attraction.hours ? `<p><i class="fas fa-clock"></i> ${attraction.hours}</p>` : ''} ${attraction.feedingTime ? `<p><i class="fas fa-utensils"></i> Кормление: ${attraction.feedingTime}</p>` : ''} <div class="popup-buttons"> <button class="btn btn-small btn-primary" onclick="window.location.href='animals.html#${attraction.id}'"> <i class="fas fa-binoculars"></i> Смотреть животных </button> ${attraction.hasSound ? ` <button class="btn btn-small btn-outline play-attraction-sound" data-sound="${attraction.sound}"> <i class="fas fa-volume-up"></i> Послушать </button> ` : ''} </div> </div> `; marker.bindPopup(popupContent); marker.on('popupopen', () => { const soundButtons = document.querySelectorAll('.play-attraction-sound'); soundButtons.forEach(btn => { btn.addEventListener('click', (e) => { const sound = e.target.closest('button').getAttribute('data-sound'); this.playAttractionSound(sound); }); }); }); this.markers.push(marker); }); } getAttractionIcon(type) { const iconClasses = { lion: 'fa-lion', elephant: 'fa-elephant', panda: 'fa-cat', bird: 'fa-dove', aquarium: 'fa-fish', reptile: 'fa-dragon', primate: 'fa-monkey', farm: 'fa-horse', restaurant: 'fa-utensils', toilet: 'fa-restroom', firstaid: 'fa-first-aid', gift: 'fa-gift' }; const className = iconClasses[type] || 'fa-map-marker-alt'; return L.divIcon({ className: `attraction-marker attraction-${type}`, html: `<i class="fas ${className}"></i>`, iconSize: [30, 30], iconAnchor: [15, 30] }); } getAttractionIconName(type) { const icons = { lion: 'lion', elephant: 'elephant', panda: 'cat', bird: 'dove', aquarium: 'fish', reptile: 'dragon', primate: 'monkey', farm: 'horse' }; return icons[type] || 'map-marker-alt'; } getAttractionTypeName(type) { const names = { lion: 'Львиный вольер', elephant: 'Слоновник', panda: 'Панды', bird: 'Птичий павильон', aquarium: 'Аквариум', reptile: 'Террариум', primate: 'Обезьянник', farm: 'Детский зоопарк', restaurant: 'Кафе', toilet: 'Туалет', firstaid: 'Медпункт', gift: 'Магазин сувениров' }; return names[type] || 'Аттракцион'; } loadDefaultAttractions() { const defaultAttractions = [ { id: 'lion', name: 'Львиный вольер', type: 'lion', lat: 55.7608, lng: 37.5775, description: 'Дом льва Симбы и его семьи. Можно наблюдать за королевской семьей в естественных условиях.', hours: '10:00 - 18:00', feedingTime: '14:00', hasSound: true, sound: 'lion' }, { id: 'elephant', name: 'Слоновник', type: 'elephant', lat: 55.7610, lng: 37.5780, description: 'Крупнейший слоновник в России. Здесь живут африканские и азиатские слоны.', hours: '10:00 - 19:00', feedingTime: '12:00, 16:00', hasSound: true, sound: 'elephant' }, { id: 'panda', name: 'Павильон панд', type: 'panda', lat: 55.7615, lng: 37.5785, description: 'Единственные большие панды в России. Особенно активны утром.', hours: '9:30 - 17:30', feedingTime: '11:00, 15:00', hasSound: true, sound: 'panda' }, { id: 'birds', name: 'Птичий мир', type: 'bird', lat: 55.7612, lng: 37.5790, description: 'Более 100 видов птиц со всего мира. Ежедневные шоу полетов.', hours: '9:00 - 18:00', hasSound: true, sound: 'bird' }, { id: 'aquarium', name: 'Океанариум', type: 'aquarium', lat: 55.7605, lng: 37.5788, description: 'Подводный мир с акулами, скатами и тропическими рыбами.', hours: '10:00 - 20:00', hasSound: true, sound: 'dolphin' }, { id: 'restaurant', name: 'Кафе "У водопоя"', type: 'restaurant', lat: 55.7618, lng: 37.5778, description: 'Уютное кафе с видом на озеро. Детское меню доступно.', hours: '10:00 - 20:00' }, { id: 'gift', name: 'Сувенирный магазин', type: 'gift', lat: 55.7613, lng: 37.5772, description: 'Плюшевые игрушки, книги и сувениры на память о посещении.', hours: '9:00 - 20:00' } ]; this.addAttractionMarkers(defaultAttractions); } async getUserLocation() { if (!navigator.geolocation) { this.showLocationError('Геолокация не поддерживается вашим браузером'); return; } const locationBtn = document.getElementById('getLocation'); if (locationBtn) { locationBtn.addEventListener('click', () => { locationBtn.classList.add('btn-loading'); this.requestUserLocation(); }); } } requestUserLocation() { navigator.geolocation.getCurrentPosition( (position) => { this.showUserLocation(position.coords); const locationBtn = document.getElementById('getLocation'); if (locationBtn) locationBtn.classList.remove('btn-loading'); }, (error) => { this.handleLocationError(error); const locationBtn = document.getElementById('getLocation'); if (locationBtn) locationBtn.classList.remove('btn-loading'); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); } showUserLocation(coords) { if (this.userMarker) { this.map.removeLayer(this.userMarker); } const userIcon = L.divIcon({ className: 'user-marker', html: '<i class="fas fa-user"></i>', iconSize: [30, 30], iconAnchor: [15, 30] }); this.userMarker = L.marker([coords.latitude, coords.longitude], { icon: userIcon }).addTo(this.map); this.userMarker.bindPopup(` <div class="map-popup"> <h3>Ваше местоположение</h3> <p>Вы находитесь здесь</p> <p>Расстояние до зоопарка: ${this.calculateDistance(coords.latitude, coords.longitude)} км</p> </div> `).openPopup(); this.map.setView([coords.latitude, coords.longitude], 13); this.showRouteToZoo(coords.latitude, coords.longitude); } calculateDistance(lat1, lon1) { const R = 6371; const dLat = this.toRad(lat1 - this.zooCoordinates[0]); const dLon = this.toRad(lon1 - this.zooCoordinates[1]); const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(this.toRad(this.zooCoordinates[0])) * Math.cos(this.toRad(lat1)) * Math.sin(dLon/2) * Math.sin(dLon/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); const distance = R * c; return distance.toFixed(1); } toRad(degrees) { return degrees * Math.PI / 180; } showRouteToZoo(userLat, userLon) { const routeLayer = L.layerGroup().addTo(this.map); const routeCoordinates = [ [userLat, userLon], this.zooCoordinates ]; const routeLine = L.polyline(routeCoordinates, { color: '#2196F3', weight: 4, opacity: 0.7, dashArray: '10, 10' }).addTo(routeLayer); routeLayer.addTo(this.map); this.routeLayer = routeLayer; const routeControl = document.getElementById('routeControl'); if (routeControl) { routeControl.innerHTML = ` <button class="btn btn-small btn-outline" id="clearRoute"> <i class="fas fa-times"></i> Убрать маршрут </button> `; document.getElementById('clearRoute').addEventListener('click', () => { this.map.removeLayer(routeLayer); routeControl.innerHTML = ''; }); } } showDirections() { const directionsModal = document.getElementById('directionsModal'); if (directionsModal) { directionsModal.classList.add('show'); const transportSelect = document.getElementById('transportMode'); if (transportSelect) { transportSelect.addEventListener('change', (e) => { this.calculateTravelTime(e.target.value); }); } document.getElementById('getRoute').addEventListener('click', () => { const mode = transportSelect.value; this.openExternalMaps(mode); }); } } calculateTravelTime(mode) { const times = { car: '15-25 минут', walking: '40-60 минут', transit: '30-40 минут', bicycle: '20-35 минут' }; const timeElement = document.getElementById('travelTime'); if (timeElement) { timeElement.textContent = times[mode] || 'Неизвестно'; } } openExternalMaps(mode) { const maps = { car: `https://www.google.com/maps/dir/?api=1&destination=${this.zooCoordinates[0]},${this.zooCoordinates[1]}&travelmode=driving`, walking: `https://www.google.com/maps/dir/?api=1&destination=${this.zooCoordinates[0]},${this.zooCoordinates[1]}&travelmode=walking`, transit: `https://www.google.com/maps/dir/?api=1&destination=${this.zooCoordinates[0]},${this.zooCoordinates[1]}&travelmode=transit`, bicycle: `https://www.google.com/maps/dir/?api=1&destination=${this.zooCoordinates[0]},${this.zooCoordinates[1]}&travelmode=bicycling` }; window.open(maps[mode] || maps.car, '_blank'); } handleLocationError(error) { let message = ''; switch(error.code) { case error.PERMISSION_DENIED: message = 'Доступ к геолокации запрещен. Разрешите доступ в настройках браузера.'; break; case error.POSITION_UNAVAILABLE: message = 'Информация о местоположении недоступна.'; break; case error.TIMEOUT: message = 'Время ожидания определения местоположения истекло.'; break; default: message = 'Произошла неизвестная ошибка.'; break; } this.showLocationError(message); } showLocationError(message) { const errorElement = document.createElement('div'); errorElement.className = 'map-error alert alert-error'; errorElement.innerHTML = ` <i class="fas fa-exclamation-triangle"></i> <span>${message}</span> <button class="alert-close"> <i class="fas fa-times"></i> </button> `; const mapContainer = document.getElementById('map'); if (mapContainer) { mapContainer.parentElement.insertBefore(errorElement, mapContainer.nextSibling); setTimeout(() => { errorElement.classList.add('show'); }, 10); const closeBtn = errorElement.querySelector('.alert-close'); closeBtn.addEventListener('click', () => { errorElement.classList.remove('show'); setTimeout(() => errorElement.remove(), 300); }); } } playAttractionSound(soundName) { const audio = new Audio(`assets/sounds/${soundName}.mp3`); audio.volume = 0.5; audio.play().catch(e => { console.log('Ошибка воспроизведения звука:', e); }); } setupEventListeners() { const filterButtons = document.querySelectorAll('.attraction-filter'); filterButtons.forEach(button => { button.addEventListener('click', (e) => { const type = e.target.getAttribute('data-type'); this.filterAttractions(type); filterButtons.forEach(btn => btn.classList.remove('active')); e.target.classList.add('active'); }); }); const searchInput = document.getElementById('mapSearch'); if (searchInput) { searchInput.addEventListener('input', (e) => { this.searchAttractions(e.target.value); }); } const resetViewBtn = document.getElementById('resetView'); if (resetViewBtn) { resetViewBtn.addEventListener('click', () => { this.map.setView(this.zooCoordinates, 15); if (this.routeLayer) { this.map.removeLayer(this.routeLayer); const routeControl = document.getElementById('routeControl'); if (routeControl) routeControl.innerHTML = ''; } this.showAllAttractions(); const filterButtons = document.querySelectorAll('.attraction-filter'); filterButtons.forEach(btn => btn.classList.remove('active')); document.querySelector('[data-type="all"]').classList.add('active'); if (searchInput) searchInput.value = ''; }); } const zoomInBtn = document.getElementById('zoomIn'); const zoomOutBtn = document.getElementById('zoomOut'); if (zoomInBtn) { zoomInBtn.addEventListener('click', () => { this.map.zoomIn(); }); } if (zoomOutBtn) { zoomOutBtn.addEventListener('click', () => { this.map.zoomOut(); }); } this.map.on('zoomend', () => { this.updateZoomControls(); }); } filterAttractions(type) { this.markers.forEach(marker => { if (marker !== this.userMarker) { if (type === 'all') { marker.addTo(this.map); } else { const markerType = marker.options.icon.options.className.includes(`attraction-${type}`); if (markerType) { marker.addTo(this.map); } else { this.map.removeLayer(marker); } } } }); } searchAttractions(query) { const searchTerm = query.toLowerCase().trim(); if (!searchTerm) { this.showAllAttractions(); return; } this.markers.forEach(marker => { if (marker !== this.userMarker) { const popup = marker.getPopup(); if (popup) { const content = popup.getContent(); const text = content.toLowerCase(); if (text.includes(searchTerm)) { marker.addTo(this.map); if (!marker.isPopupOpen()) { marker.openPopup(); } } else { this.map.removeLayer(marker); marker.closePopup(); } } } }); } showAllAttractions() { this.markers.forEach(marker => { marker.addTo(this.map); marker.closePopup(); }); } updateZoomControls() { const currentZoom = this.map.getZoom(); const zoomInBtn = document.getElementById('zoomIn'); const zoomOutBtn = document.getElementById('zoomOut'); if (zoomInBtn) { zoomInBtn.disabled = currentZoom >= this.map.getMaxZoom(); } if (zoomOutBtn) { zoomOutBtn.disabled = currentZoom <= this.map.getMinZoom(); } } addCustomMarker(lat, lng, title, description) { const icon = L.divIcon({ className: 'custom-marker', html: '<i class="fas fa-map-pin"></i>', iconSize: [30, 30], iconAnchor: [15, 30] }); const marker = L.marker([lat, lng], { icon }).addTo(this.map); marker.bindPopup(` <div class="map-popup"> <h3>${title}</h3> <p>${description}</p> </div> `); this.markers.push(marker); return marker; } clearCustomMarkers() { this.markers.forEach((marker, index) => { if (marker.options.icon.options.className === 'custom-marker') { this.map.removeLayer(marker); this.markers.splice(index, 1); } }); } } document.addEventListener('DOMContentLoaded', function() { const zooMap = new ZooMap(); const modalClose = document.querySelectorAll('.modal-close'); modalClose.forEach(btn => { btn.addEventListener('click', () => { const modal = btn.closest('.modal'); if (modal) { modal.classList.remove('show'); } }); }); window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { e.target.classList.remove('show'); } }); const printMapBtn = document.getElementById('printMap'); if (printMapBtn) { printMapBtn.addEventListener('click', () => { window.print(); }); } const fullscreenBtn = document.getElementById('fullscreen'); if (fullscreenBtn) { fullscreenBtn.addEventListener('click', () => { const mapContainer = document.getElementById('map'); if (!document.fullscreenElement) { mapContainer.requestFullscreen().catch(err => { console.log(`Ошибка полноэкранного режима: ${err.message}`); }); } else { document.exitFullscreen(); } }); document.addEventListener('fullscreenchange', () => { if (document.fullscreenElement) { fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>'; } else { fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>'; } }); } const map = document.getElementById('map'); if (map) { map.addEventListener('click', (e) => { if (e.target.closest('.leaflet-popup')) return; const popups = document.querySelectorAll('.leaflet-popup'); popups.forEach(popup => { popup.style.display = 'none'; }); }); } });