Spaces:
Sleeping
Sleeping
| /** | |
| * Map initialization and GeoJSON visualization | |
| * This file handles the map creation and displaying GeoJSON data on it | |
| */ | |
| // Store the map object globally | |
| let map = null; | |
| let currentFeatureType = 'buildings'; | |
| // Initialize the map with default settings | |
| function initMap(initialCoords) { | |
| // If map already exists, remove it and create a new one | |
| if (map !== null) { | |
| map.remove(); | |
| } | |
| // Default center coordinates (will be overridden by GeoJSON data) | |
| let center = [0, 0]; | |
| let zoom = 2; | |
| // If coordinates are provided, use them | |
| if (initialCoords && initialCoords.lat !== undefined && initialCoords.lng !== undefined) { | |
| center = [initialCoords.lat, initialCoords.lng]; | |
| zoom = initialCoords.zoom || 13; | |
| } | |
| // Initialize the map with the center coordinates | |
| map = L.map('map').setView(center, zoom); | |
| // Define tile layers | |
| const osmLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', | |
| maxZoom: 19 | |
| }); | |
| const satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { | |
| attribution: 'Imagery © Esri', | |
| maxZoom: 19 | |
| }); | |
| // Add OpenStreetMap layer by default | |
| osmLayer.addTo(map); | |
| // Add layer control | |
| const baseLayers = { | |
| "OpenStreetMap": osmLayer, | |
| "Satellite": satelliteLayer | |
| }; | |
| L.control.layers(baseLayers, null, {position: 'topright'}).addTo(map); | |
| // Add a scale control | |
| L.control.scale().addTo(map); | |
| return map; | |
| } | |
| // Display GeoJSON data on the map | |
| function displayGeoJSON(geojsonData) { | |
| // Log the GeoJSON data for debugging | |
| console.log('GeoJSON data received:', geojsonData); | |
| if (geojsonData && geojsonData.features && geojsonData.features.length > 0) { | |
| console.log('First feature:', geojsonData.features[0]); | |
| if (geojsonData.features[0].geometry && geojsonData.features[0].geometry.coordinates) { | |
| console.log('First feature coordinates:', | |
| geojsonData.features[0].geometry.type === 'Polygon' ? | |
| geojsonData.features[0].geometry.coordinates[0][0] : | |
| geojsonData.features[0].geometry.coordinates[0][0][0]); | |
| } | |
| } | |
| // Calculate center coordinates from GeoJSON data | |
| let initialCoords = calculateCenterFromGeoJSON(geojsonData); | |
| console.log('Calculated center coordinates:', initialCoords); | |
| if (!map) { | |
| initMap(initialCoords); | |
| } | |
| // Switch to satellite view for better context when viewing features | |
| if (geojsonData && geojsonData.features && geojsonData.features.length > 0) { | |
| // Switch to satellite view for better visualization | |
| try { | |
| document.querySelectorAll('.leaflet-control-layers-base input')[1].click(); | |
| } catch (e) { | |
| console.warn('Could not switch to satellite view:', e); | |
| } | |
| } | |
| // Update feature type if available in the data | |
| if (geojsonData && geojsonData.feature_type) { | |
| currentFeatureType = geojsonData.feature_type; | |
| } | |
| // Clear any existing GeoJSON layers | |
| map.eachLayer(function(layer) { | |
| if (layer instanceof L.GeoJSON) { | |
| map.removeLayer(layer); | |
| } | |
| }); | |
| // Add the GeoJSON data to the map with styling based on feature type | |
| const geojsonLayer = L.geoJSON(geojsonData, { | |
| style: function(feature) { | |
| // Different styling based on feature type | |
| switch(currentFeatureType) { | |
| case 'buildings': | |
| return { | |
| fillColor: '#e63946', | |
| weight: 1.5, | |
| opacity: 1, | |
| color: '#999', | |
| fillOpacity: 0.7 | |
| }; | |
| case 'trees': | |
| return { | |
| fillColor: '#2a9d8f', | |
| weight: 1, | |
| opacity: 0.9, | |
| color: '#006d4f', | |
| fillOpacity: 0.7 | |
| }; | |
| case 'water': | |
| return { | |
| fillColor: '#0077b6', | |
| weight: 1, | |
| opacity: 0.8, | |
| color: '#023e8a', | |
| fillOpacity: 0.6 | |
| }; | |
| case 'roads': | |
| return { | |
| fillColor: '#a8dadc', | |
| weight: 3, | |
| opacity: 1, | |
| color: '#457b9d', | |
| fillOpacity: 0.8 | |
| }; | |
| default: | |
| return { | |
| fillColor: getRandomColor(), | |
| weight: 2, | |
| opacity: 1, | |
| color: '#666', | |
| fillOpacity: 0.7 | |
| }; | |
| } | |
| }, | |
| pointToLayer: function(feature, latlng) { | |
| // Style points based on feature type | |
| let pointStyle = { | |
| radius: 8, | |
| color: "#000", | |
| weight: 1, | |
| opacity: 1, | |
| fillOpacity: 0.8 | |
| }; | |
| // Set color based on feature type | |
| switch(currentFeatureType) { | |
| case 'buildings': | |
| pointStyle.fillColor = '#e63946'; | |
| break; | |
| case 'trees': | |
| pointStyle.fillColor = '#2a9d8f'; | |
| break; | |
| case 'water': | |
| pointStyle.fillColor = '#0077b6'; | |
| break; | |
| case 'roads': | |
| pointStyle.fillColor = '#a8dadc'; | |
| break; | |
| default: | |
| pointStyle.fillColor = getRandomColor(); | |
| } | |
| return L.circleMarker(latlng, pointStyle); | |
| }, | |
| onEachFeature: function(feature, layer) { | |
| // Add popups to show feature properties | |
| if (feature.properties) { | |
| let popupContent = '<div class="feature-popup">'; | |
| // Set title based on feature type | |
| let title = 'Feature'; | |
| switch(currentFeatureType) { | |
| case 'buildings': | |
| title = 'Building'; | |
| break; | |
| case 'trees': | |
| title = 'Tree/Vegetation'; | |
| break; | |
| case 'water': | |
| title = 'Water Body'; | |
| break; | |
| case 'roads': | |
| title = 'Road'; | |
| break; | |
| } | |
| popupContent += `<h5>${title} Properties</h5>`; | |
| for (const [key, value] of Object.entries(feature.properties)) { | |
| popupContent += `<strong>${key}:</strong> ${value}<br>`; | |
| } | |
| popupContent += '</div>'; | |
| layer.bindPopup(popupContent); | |
| } | |
| } | |
| }).addTo(map); | |
| // Zoom to fit the GeoJSON data bounds | |
| if (geojsonLayer.getBounds().isValid()) { | |
| const bounds = geojsonLayer.getBounds(); | |
| console.log('GeoJSON bounds:', bounds); | |
| map.fitBounds(bounds); | |
| } else { | |
| console.warn('GeoJSON bounds not valid'); | |
| } | |
| } | |
| // Generate a random color for styling different features | |
| function getRandomColor() { | |
| const colors = [ | |
| '#3388ff', '#33a02c', '#1f78b4', '#ff7f00', '#6a3d9a', | |
| '#a6cee3', '#b2df8a', '#fb9a99', '#fdbf6f', '#cab2d6' | |
| ]; | |
| return colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| // Function to format GeoJSON for display | |
| function formatGeoJSON(geojson) { | |
| return JSON.stringify(geojson, null, 2); | |
| } | |
| // Calculate center coordinates from GeoJSON data | |
| function calculateCenterFromGeoJSON(geojsonData) { | |
| if (!geojsonData || !geojsonData.features || geojsonData.features.length === 0) { | |
| return { lat: 0, lng: 0, zoom: 2 }; // Default to world view | |
| } | |
| try { | |
| // Create a temporary GeoJSON layer to calculate bounds | |
| const tempLayer = L.geoJSON(geojsonData); | |
| const bounds = tempLayer.getBounds(); | |
| if (bounds.isValid()) { | |
| const center = bounds.getCenter(); | |
| // Calculate appropriate zoom level based on bounds size | |
| const zoom = getBoundsZoomLevel(bounds); | |
| return { lat: center.lat, lng: center.lng, zoom: zoom }; | |
| } | |
| } catch (e) { | |
| console.warn('Error calculating center from GeoJSON:', e); | |
| } | |
| // If we can't calculate from features, try to get center from the first feature | |
| try { | |
| const firstFeature = geojsonData.features[0]; | |
| if (firstFeature.geometry && firstFeature.geometry.coordinates) { | |
| let coords; | |
| // Handle different geometry types | |
| if (firstFeature.geometry.type === 'Point') { | |
| coords = firstFeature.geometry.coordinates; | |
| return { lat: coords[1], lng: coords[0], zoom: 15 }; | |
| } else if (firstFeature.geometry.type === 'Polygon') { | |
| coords = firstFeature.geometry.coordinates[0][0]; | |
| return { lat: coords[1], lng: coords[0], zoom: 13 }; | |
| } else if (firstFeature.geometry.type === 'MultiPolygon') { | |
| coords = firstFeature.geometry.coordinates[0][0][0]; | |
| return { lat: coords[1], lng: coords[0], zoom: 13 }; | |
| } | |
| } | |
| } catch (e) { | |
| console.warn('Error getting coordinates from first feature:', e); | |
| } | |
| // Default fallback | |
| return { lat: 0, lng: 0, zoom: 2 }; | |
| } | |
| // Calculate appropriate zoom level based on bounds size | |
| function getBoundsZoomLevel(bounds) { | |
| const WORLD_DIM = { height: 256, width: 256 }; | |
| const ZOOM_MAX = 18; | |
| const ne = bounds.getNorthEast(); | |
| const sw = bounds.getSouthWest(); | |
| const latFraction = (ne.lat - sw.lat) / 180; | |
| const lngFraction = (ne.lng - sw.lng) / 360; | |
| const latZoom = Math.floor(Math.log(1 / latFraction) / Math.LN2); | |
| const lngZoom = Math.floor(Math.log(1 / lngFraction) / Math.LN2); | |
| const zoom = Math.min(latZoom, lngZoom, ZOOM_MAX); | |
| return zoom > 0 ? zoom - 1 : 0; // Zoom out slightly for better context | |
| } | |
| // Initialize map when the DOM is loaded | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // The map will be initialized when results are available | |
| }); | |