Spaces:
Sleeping
Sleeping
| <html> | |
| <head> | |
| <title>Matchmaking for habitater og arter</title> | |
| <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" /> | |
| <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/> | |
| <style> | |
| html, body, #map { | |
| height: 100%; | |
| width: 100%; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| #downloadButton { | |
| position: absolute; | |
| top: 140px; /* Positioned below the infoBox */ | |
| right: 10px; | |
| z-index: 1000; | |
| padding: 10px; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| border-radius: 5px; | |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); | |
| font-family: Arial, sans-serif; | |
| cursor: pointer; | |
| display: none; | |
| } | |
| #infoBox { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| padding: 10px; | |
| border-radius: 5px; | |
| z-index: 1000; | |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); | |
| font-family: Arial, sans-serif; | |
| text-align: center; | |
| max-width: 300px; | |
| } | |
| /* Loading spinner styles */ | |
| .spinner { | |
| position: absolute; | |
| width: 40px; | |
| height: 40px; | |
| margin: 0; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| border-radius: 50%; | |
| border: 3px solid transparent; | |
| border-top-color: #3498db; | |
| border-bottom-color: #3498db; | |
| animation: spin 2s linear infinite; | |
| z-index: 1000; | |
| } | |
| /* Add this if not already present */ | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .spinner-container { | |
| background: none ; | |
| } | |
| /* Make sure there's no Leaflet default icon background */ | |
| .leaflet-div-icon { | |
| background: transparent; | |
| border: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="map"></div> | |
| <button id="downloadButton">Download GeoJSON</button> | |
| <div id="infoBox"> | |
| <h3 style="margin: 0 0 5px 0;">Matchmaking for habitater og arter</h3> | |
| <p style="margin: 0;">Tegn et område på kortet og få en liste med de arter som bedst matcher habitatet.</p> | |
| <p style="margin: 0;">Udviklet af: <a href="mailto:[email protected]" style="color: #3498db; text-decoration: none;">Kenneth Thorø Martinsen</a></p> | |
| </div> | |
| <script> | |
| var map = L.map('map').setView([56.2, 10.3], 7); | |
| L.tileLayer('https://services.datafordeler.dk/GeoDanmarkOrto/orto_foraar_webm/1.0.0/WMTS/orto_foraar_webm/default/DFD_GoogleMapsCompatible/{z}/{y}/{x}.jpg?username=BJSIGPGRVW&password=Panseryrtat*56klinge', { | |
| attribution: 'CC BY 4.0, GeoDanmark, Forårsbilleder Ortofoto, dataforsyningen.dk', | |
| maxZoom: 19 | |
| }).addTo(map); | |
| var drawnItems = new L.FeatureGroup(); | |
| map.addLayer(drawnItems); | |
| var drawControl = new L.Control.Draw({ | |
| draw: { | |
| polygon: true, | |
| polyline: false, | |
| circle: false, | |
| rectangle: false, | |
| marker: false, | |
| circlemarker: false | |
| }, | |
| edit: { | |
| featureGroup: drawnItems | |
| } | |
| }); | |
| map.addControl(drawControl); | |
| map.on('draw:created', function (e) { | |
| var layer = e.layer; | |
| drawnItems.addLayer(layer); | |
| predictAndShow(layer); | |
| }); | |
| map.on('draw:edited', function(e){ | |
| var layers = e.layers; | |
| layers.eachLayer(function(layer) { | |
| predictAndShow(layer); | |
| }); | |
| }); | |
| map.on('draw:deleted', function(e){ | |
| updateDownloadButton(); | |
| }); | |
| function predictAndShow(layer) { | |
| var geojson = layer.toGeoJSON(); | |
| // Get the center of the polygon for placing the spinner | |
| var bounds = layer.getBounds(); | |
| var center = bounds.getCenter(); | |
| // Create a more visible spinner with custom HTML | |
| var spinnerHtml = '<div class="spinner" style="width: 25px; height: 25px; ' + | |
| 'border: 5px solid #f3f3f3; border-top: 5px solid #3498db; ' + | |
| 'border-radius: 50%; animation: spin 2s linear infinite;"></div>'; | |
| var spinner = L.divIcon({ | |
| html: spinnerHtml, | |
| className: 'spinner-container', | |
| iconSize: [50, 50], | |
| iconAnchor: [25, 25] // Center the spinner on the point | |
| }); | |
| // Add the spinner to the map | |
| var loadingMarker = L.marker(center, { | |
| icon: spinner, | |
| interactive: false, | |
| zIndexOffset: 1000 // Ensure spinner appears above other elements | |
| }).addTo(map); | |
| // Change the polygon style to indicate loading | |
| var originalStyle = { | |
| color: layer.options.color || '#3388ff', | |
| fillOpacity: layer.options.fillOpacity || 0.2 | |
| }; | |
| layer.setStyle({ | |
| fillOpacity: 0.1, | |
| color: '#aaa' | |
| }); | |
| fetch('/predict', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ geojson: geojson }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| // Remove the spinner and restore original style | |
| map.removeLayer(loadingMarker); | |
| layer.setStyle(originalStyle); | |
| var predictions = data.predictions; | |
| var popupContent = "<b>Arter:</b><br>" + data.predictions_formatted; | |
| // Store the popup content in the layer for later use | |
| layer.popupContent = popupContent; | |
| // Only add click handler, no mouseover/hover effects | |
| layer.on('click', function(e) { | |
| if (!layer._popup || !map.hasLayer(layer._popup)) { | |
| var popup = L.popup({ | |
| closeButton: true, | |
| autoClose: false, | |
| closeOnEscapeKey: false, | |
| closeOnClick: false | |
| }) | |
| .setLatLng(e.latlng) | |
| .setContent(layer.popupContent); | |
| layer.bindPopup(popup).openPopup(); | |
| } else { | |
| layer.closePopup(); | |
| } | |
| }); | |
| // Ensure the feature object exists before assigning to it | |
| if (!layer.feature) { | |
| layer.feature = {}; | |
| } | |
| if (!layer.feature.properties) { | |
| layer.feature.properties = {}; | |
| } | |
| // Store both the raw predictions and formatted text | |
| layer.feature.properties.arter = predictions; | |
| updateDownloadButton(); | |
| }) | |
| .catch(error => { | |
| // Remove the spinner and restore original style on error | |
| map.removeLayer(loadingMarker); | |
| layer.setStyle(originalStyle); | |
| console.error('Error:', error); | |
| alert('Prediction failed.'); | |
| }); | |
| } | |
| document.getElementById('downloadButton').addEventListener('click', function() { | |
| var features = []; | |
| // Collect all drawn layers with their prediction data | |
| drawnItems.eachLayer(function(layer){ | |
| // Get the GeoJSON representation of the layer | |
| var featureGeoJSON = layer.toGeoJSON(); | |
| // Ensure type is explicitly set to "Feature" | |
| featureGeoJSON.type = "Feature"; | |
| // Make sure we have properties object | |
| if (!featureGeoJSON.properties) { | |
| featureGeoJSON.properties = {}; | |
| } | |
| // Ensure prediction data is included in properties | |
| if (layer.feature && layer.feature.properties && layer.feature.properties.arter) { | |
| featureGeoJSON.properties.arter = layer.feature.properties.arter; | |
| } | |
| features.push(featureGeoJSON); | |
| }); | |
| // Create a proper GeoJSON FeatureCollection | |
| var featureCollection = { | |
| "type": "FeatureCollection", | |
| "features": features | |
| }; | |
| // Convert to a JSON string with pretty formatting | |
| var geojsonString = JSON.stringify(featureCollection, null, 2); | |
| // Create a Blob from the GeoJSON | |
| var blob = new Blob([geojsonString], {type: 'application/geo+json'}); | |
| // Create download link | |
| var url = window.URL.createObjectURL(blob); | |
| var a = document.createElement('a'); | |
| a.style.display = 'none'; | |
| a.href = url; | |
| a.download = 'polygoner.geojson'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| // Clean up | |
| setTimeout(function() { | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| }, 100); | |
| }); | |
| function updateDownloadButton(){ | |
| var geojsonData = []; | |
| drawnItems.eachLayer(function(layer){ | |
| geojsonData.push(layer.toGeoJSON()); | |
| }); | |
| if(geojsonData.length > 0){ | |
| document.getElementById('downloadButton').style.display = "block"; | |
| } else { | |
| document.getElementById('downloadButton').style.display = "none"; | |
| } | |
| } | |
| updateDownloadButton(); | |
| </script> | |
| </body> | |
| </html> |