diff --git a/README.md b/README.md index abc30b8b..4fdf093f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,30 @@ # NASA Space Apps Challenge 2024 [Noida] -#### Team Name - -#### Problem Statement - -#### Team Leader Email - +#### Team Name - Supernovas +#### Problem Statement - Community Mapping +#### Team Leader Email - nishantpandey2004@gmail.com ## A Brief of the Prototype: - What is your solution? and how it works. + The prototype is an interactive web-based map application designed to provide real-time geographic data visualization and user-controlled features. It integrates multiple layers of geographic information, including OpenStreetMap (OSM) tiles, air quality index (AQI), vegetation, highways, terrain, and satellite imagery. + +Key features of the prototype include: + +1. **Map Layer Toggling**: Users can easily toggle different map layers such as vegetation, highways, satellite view, terrain, and pollution, allowing for customized map visualization. + +2. **Air Quality Information**: An AQI widget can be displayed for specific cities, showing real-time air quality data with a user-friendly interface. + +3. **Location-based Markers**: Users can mark specific cities on the map and calculate the distance between marked locations using a routing system. + +4. **Rainfall Data**: The prototype allows toggling a rainfall layer to display precipitation data over the map, adding an environmental monitoring feature. + +5. **Voice and Text Commands**: The application supports both text-based input and voice commands to control the map’s features (e.g., zooming in/out, going to a location, toggling layers). + +6. **History Logging**: Each user action, such as toggling a layer or marking a city, is logged and displayed in a history list for reference. + +The application is designed to be user-friendly, allowing seamless interaction between the user and the map interface for visualizing geographic and environmental data. ## Code Execution Instruction: - *[If your solution is **not** application based, you can ignore this para] + 1. [Run Prototype](https://nasa-space-apps-noida.vercel.app/) + 2. [Watch DEMO](https://www.youtube.com/watch?v=8CtPQ6vBYGo) - *The Repository must contain your **Execution Plan PDF**. + diff --git a/Supernovas/index.css b/Supernovas/index.css new file mode 100644 index 00000000..76c4bf2c --- /dev/null +++ b/Supernovas/index.css @@ -0,0 +1,225 @@ +/* General reset for styling */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Orbitron', sans-serif; + /* Futuristic font */ + background: url('server/space-background.jpg') no-repeat center center fixed; + /* Space background */ + background-size: cover; + color: white; + display: flex; + flex-direction: column; + align-items: center; +} + +header { + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + background: rgba(0, 0, 0, 0.7); + border-bottom: 2px solid #fff; + width: 100%; +} + +header img { + width: 80px; + margin-right: 20px; +} + +header h1 { + font-size: 2.5rem; + color: #00bfff; + /* NASA-like light blue */ + text-shadow: 0px 0px 15px #00bfff; +} + +.map-container { + width: 90%; + height: 65vh; + margin: 20px 0; + position: relative; + display: flex; + /* Flexbox for positioning */ +} + +#map { + flex: 1; + /* Map takes remaining space */ + height: 100%; + border: 3px solid #fff; + border-radius: 15px; + overflow: hidden; + box-shadow: 0 0 20px rgba(255, 255, 255, 0.3); + position: relative; +} + +/* History box on the left side */ +.history-box { + position: absolute; + top: 20px; + left: 20px; + width: 250px; + max-height: 200px; + background: rgba(0, 0, 0, 0.7); + padding: 15px; + border-radius: 10px; + color: #fff; + box-shadow: 0px 0px 10px rgba(0, 0, 255, 0.5); + overflow-y: auto; + z-index: 1000; + /* Ensure it stays on top */ +} + +.history-box h2 { + font-size: 1.5rem; + margin-bottom: 10px; + text-align: center; + color: #00bfff; +} + +.history-box ul { + list-style: none; + padding-left: 0; + margin: 0; + max-height: 160px; + /* Adjust height to fit box */ + overflow-y: auto; + /* Scroll when content exceeds */ +} + +.history-box ul li { + margin-bottom: 10px; + padding: 8px; + background: rgba(255, 255, 255, 0.1); + border-radius: 5px; + font-size: 1rem; + color: #00bfff; + box-shadow: 0px 0px 5px rgba(255, 255, 255, 0.3); +} + +/* Distance box on the right side */ +#distance { + position: absolute; + top: 20px; + right: 20px; + background: rgba(0, 0, 0, 0.7); + padding: 10px; + border-radius: 10px; + color: white; + font-size: 1.2rem; + box-shadow: 0px 0px 15px rgba(0, 191, 255, 0.7); + z-index: 1000; +} + +/* Coordinates display (below distance) */ +#coordinates { + position: absolute; + top: 80px; + right: 20px; + background: rgba(0, 0, 0, 0.7); + padding: 10px; + border-radius: 10px; + color: white; + font-size: 1.2rem; + box-shadow: 0px 0px 15px rgba(0, 191, 255, 0.7); + z-index: 1000; +} + + +.watermark { + position: absolute; + top: 20px; + right: 10px; + background-color: rgba(255, 255, 255, 0.7); + padding: 5px 10px; + border-radius: 5px; + font-size: 14px; + font-weight: bold; + color: red; + z-index: 1000; + opacity: 70%; +} + + +footer { + width: 100%; + text-align: center; + background: rgba(0, 0, 0, 0.7); + padding: 20px; + color: #fff; + border-top: 2px solid #00bfff; +} + +footer p { + font-size: 1.2rem; +} + +footer .commands { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +footer .command-box { + background: rgba(255, 255, 255, 0.2); + border-radius: 5px; + margin: 5px; + padding: 10px 15px; + font-size: 1rem; + color: #00bfff; + box-shadow: 0px 0px 10px rgba(0, 191, 255, 0.5); + cursor: pointer; + transition: all 0.3s; +} + +footer .command-box:hover { + background: #00bfff; + color: #fff; + box-shadow: 0px 0px 15px #fff; +} + +.search-bar { + display: flex; + justify-content: center; + align-items: center; + margin: 10px; + z-index: 1000; + + position: absolute; + top: 20px; + right: 30%; + + + + z-index: 1000; + /* Ensure it stays on top */ +} + +#search-input { + padding: 10px; + font-size: 16px; + width: 300px; + border: 1px solid #ccc; + border-radius: 5px; +} + +#mic-button { + background-color: #ffffff; + color: white; + font-size: 26px; + border-color: #ff0000; + border-radius: 50%; + + margin-left: 5px; + cursor: pointer; +} + +#mic-button:hover { + background-color: #ff0000; +} \ No newline at end of file diff --git a/Supernovas/index.html b/Supernovas/index.html new file mode 100644 index 00000000..db909ede --- /dev/null +++ b/Supernovas/index.html @@ -0,0 +1,95 @@ + + + + + + + VANI - Voice Assistance for Navigation and Interaction + + + + + + + + + + + + + + + + + + + + + + + +
+ NASA Logo +

VANI

+
+ + + +
+ +
+
+

History

+ +
+ + +
+
Supernovas
+
+
+ +
+ + + + + + + \ No newline at end of file diff --git a/Supernovas/script.js b/Supernovas/script.js new file mode 100644 index 00000000..94091dec --- /dev/null +++ b/Supernovas/script.js @@ -0,0 +1,336 @@ +var map = L.map("map").setView([21.82, 82.71], 5); +L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); + +// AQI (Air Quality Index) layer +var WAQI_URL = "https://tiles.waqi.info/tiles/usepa-aqi/{z}/{x}/{y}.png?token=f9a3aa393a0d9a30af06f258f0a2854e227670db"; // Replace with your actual token +var waqiLayer = L.tileLayer(WAQI_URL, { + attribution: 'Air Quality Tiles © waqi.info' +}) + +var vegetationLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'); +var highwaysLayer = L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png'); +let pollutionLayer = null; +let rainfallLayer = null; + +// Functions for handling different map layers and controls +const goToCoords = function (city) { + var inurl = "https://nominatim.openstreetmap.org/search?q=" + city + "&limit=1&format=json&addressdetails=1"; + $.when(ajax1()).done(function (data) { + if (data.length > 0) { + map.flyTo([data[0]["lat"], data[0]["lon"]], 12); + addToHistory('Go to ' + city); + } else { + alert('City not found!'); + } + }); + function ajax1() { + return $.ajax({ + url: inurl, + dataType: "json" + }); + } +}; + +const toggleVegetation = function () { + if (map.hasLayer(vegetationLayer)) { + map.removeLayer(vegetationLayer); + addToHistory('Hide vegetation'); + } else { + map.addLayer(vegetationLayer); + addToHistory('Show vegetation'); + } +}; + +const toggleHighways = function () { + if (map.hasLayer(highwaysLayer)) { + map.removeLayer(highwaysLayer); + addToHistory('Hide highways'); + } else { + map.addLayer(highwaysLayer); + addToHistory('Show highways'); + } +}; + +const toggleSatellite = function () { + var satelliteLayer = L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { + maxZoom: 20, + subdomains: ['mt0', 'mt1', 'mt2', 'mt3'] + }); + if (map.hasLayer(satelliteLayer)) { + map.removeLayer(satelliteLayer); + addToHistory('Hide satellite'); + } else { + map.addLayer(satelliteLayer); + addToHistory('Show satellite'); + } +}; + +const toggleTerrain = function () { + var terrainLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png'); + if (map.hasLayer(terrainLayer)) { + map.removeLayer(terrainLayer); + addToHistory('Hide terrain'); + } else { + map.addLayer(terrainLayer); + addToHistory('Show terrain'); + } +}; + +const showPollutionMap = function () { + // Check if pollutionLayer already exists + if (pollutionLayer) { + map.removeLayer(pollutionLayer); // Remove the previous layer if it exists + pollutionLayer = null; // Reset the layer variable + } + + // Create the pollution layer using the location + pollutionLayer = L.tileLayer(WAQI_URL, { + attribution: 'Air Quality Tiles © waqi.info', + opacity: 0.7 + }).addTo(map); + + // Log the action in history + addToHistory('Show pollution'); +}; + +const showRainfallMap = function () { + // Check if rainfallLayer already exists + if (rainfallLayer) { + map.removeLayer(rainfallLayer); // Remove the previous layer if it exists + rainfallLayer = null; // Reset the layer variable + } + + // Create the rainfall layer using the location + rainfallLayer = L.tileLayer(WAQI_URL, { + attribution: 'Air Quality Tiles © waqi.info', + opacity: 0.7 + }).addTo(map); + + // Log the action in history + addToHistory('Show rainfall'); +}; + +const zoomIn = function () { + map.zoomIn(); + addToHistory('Zoom in'); +}; + +const zoomOut = function () { + map.zoomOut(); + addToHistory('Zoom out'); +}; + +const stoplistening = function () { + annyang.abort(); + addToHistory('Stop listening'); +}; + +let markersArray = []; +let routingControl = null; + +const addMarker = function (city) { + var markerUrl = "https://nominatim.openstreetmap.org/search?q=" + city + "&format=json&limit=1"; + $.ajax({ + url: markerUrl, + dataType: "json", + success: function (data) { + if (data.length > 0) { + var lat = data[0].lat; + var lon = data[0].lon; + var marker = L.marker([lat, lon]).addTo(map).bindPopup(city); + markersArray.push(marker); + addToHistory('Mark ' + city); + } else { + alert('City not found!'); + } + } + }); +}; + +const removeMarker = function (city) { + markersArray = markersArray.filter(marker => { + if (marker.getPopup().getContent() === city) { + map.removeLayer(marker); + addToHistory('Unmark ' + city); + return false; + } + return true; + }); +}; + +const calculateDistance = function () { + if (markersArray.length < 2) { + alert("You need at least two markers to calculate distance."); + return; + } + + if (routingControl !== null) { + map.removeControl(routingControl); + } + + var waypoints = markersArray.map(marker => marker.getLatLng()); + routingControl = L.Routing.control({ + waypoints: waypoints, + lineOptions: { + styles: [{ color: 'red', opacity: 0.6, weight: 4 }] + }, + createMarker: function () { return null; }, + routeWhileDragging: true, + show: false, + addWaypoints: false + }).addTo(map); + + routingControl.on('routesfound', function (e) { + var routes = e.routes; + var summary = routes[0].summary; + var distance = (summary.totalDistance / 1000).toFixed(2); // Convert to kilometers + document.getElementById('distance').innerHTML = "Total distance: " + distance + " km"; + addToHistory('Calculate distance'); + }); +}; + +// AQI Widget Integration +const showAQIWidget = function(city) { + goToCoords(city); // Navigate to the city first + + // After the navigation is successful, load the AQI data + $.when(ajax1(city)).done(function() { + const container = document.getElementById('city-aqi-container'); + container.innerHTML = ''; // Clear previous widget + + // Set CSS styles for positioning + container.style.position = 'absolute'; + container.style.zIndex = '1000'; + container.style.position = 'fixed'; // Change to fixed for viewport centering + container.style.top = '50%'; // Move down 50% of the viewport height + container.style.left = '50%'; // Move right 50% of the viewport width + container.style.transform = 'translate(-50%, -50%)'; // Center it perfectly + container.style.color = 'black'; + + // Load the AQI data + _aqiFeed({ + container: "city-aqi-container", + city: city.toLowerCase(), + + display: "%cityname AQI is %aqi
on %date" + }); + + addToHistory('Show AQI widget of ' + city); + }).fail(function() { + alert('Unable to load AQI data for ' + city); + }); +}; + + +// Helper function to get city coordinates +function ajax1(city) { + var inurl = "https://nominatim.openstreetmap.org/search?q=" + city + "&limit=1&format=json&addressdetails=1"; + return $.ajax({ + url: inurl, + dataType: "json" + }); +} + +const closeAQIWidget = function() { + document.getElementById('city-aqi-container').innerHTML = ''; // Clear the widget + addToHistory('Close AQI widget'); +}; + +// History Management +const addToHistory = function (action) { + var historyList = document.getElementById("history-list"); + var listItem = document.createElement("li"); + listItem.textContent = action; + historyList.appendChild(listItem); +}; + + + +// Searchbar Event Listener for Text Input - Handling All Commands +document.getElementById('search-input').addEventListener('keydown', function (e) { + if (e.key === 'Enter') { + const input = this.value.trim().toLowerCase(); + const command = input.split(' '); + + // Identify which command was entered + if (input.startsWith('go to ')) { + const city = input.replace('go to ', ''); + goToCoords(city); + } else if (input === 'show vegetation') { + toggleVegetation(); + } else if (input === 'show highways') { + toggleHighways(); + } else if (input === 'show satellite') { + toggleSatellite(); + } else if (input === 'show terrain') { + toggleTerrain(); + } else if (input === 'zoom in') { + zoomIn(); + } else if (input === 'zoom out') { + zoomOut(); + } else if (input.startsWith('mark ')) { + const city = input.replace('mark ', ''); + addMarker(city); + } else if (input.startsWith('unmark ')) { + const city = input.replace('unmark ', ''); + removeMarker(city); + } else if (input === 'calculate distance') { + calculateDistance(); + } else if (input === 'show pollution') { + showPollutionMap(); + }else if (input.startsWith('show aqi of ')) { + const city = input.replace('show aqi of ', ''); + showAQIWidget(city); + } else if (input === 'close widget') { + closeAQIWidget();} + else if (input === 'show rainfall') { + showRainfallMap(); + } + else { + alert("Command not recognized."); + } + + + // Clear input field after command is executed + this.value = ''; + } +}); + +// Microphone Button to Activate Voice Command (One-time listening) +document.getElementById('mic-button').addEventListener('click', function () { + if (annyang) { + // Start listening, but listen for a single command only + annyang.start({ autoRestart: false, continuous: false }); + + // Automatically stop after the first recognition event + annyang.addCallback('result', function () { + annyang.abort(); + }); + alert("Listening for voice commands..."); + } +}); + +// Voice Command Setup (Annyang) +if (annyang) { + const commands = { + 'go to *city': goToCoords, + 'show vegetation': toggleVegetation, + 'show highways': toggleHighways, + 'show satellite': toggleSatellite, + 'show terrain': toggleTerrain, + 'zoom in': zoomIn, + 'zoom out': zoomOut, + 'mark *city': addMarker, + 'unmark *city': removeMarker, + 'calculate distance': calculateDistance, + 'stop listening': stoplistening, + 'show pollution': showPollutionMap, + 'show aqi of *city': showAQIWidget, + 'close aqi': closeAQIWidget, + }; + + annyang.addCommands(commands); + SpeechKITT.annyang(); + SpeechKITT.setStylesheet('//cdnjs.cloudflare.com/ajax/libs/SpeechKITT/0.3.0/themes/flat-clouds.css'); + SpeechKITT.vroom(); +} \ No newline at end of file diff --git a/Supernovas/server/app.py b/Supernovas/server/app.py new file mode 100644 index 00000000..072c5570 --- /dev/null +++ b/Supernovas/server/app.py @@ -0,0 +1,53 @@ +from flask import Flask, request, jsonify +import whisper +from flask_cors import CORS +import numpy as np +from pydub import AudioSegment +import os + +app = Flask(__name__) +CORS(app) + +model = whisper.load_model("base") + +@app.route("/transcribe", methods=["POST"]) +def transcribe(): + try: + # Check if an audio file is present in the request + if "audio" not in request.files: + return jsonify({"error": "No audio file found in the request"}), 400 + + # Get the audio file from the request + audio_file = request.files["audio"] + + # Save the audio file to a temporary location + audio_file_path = "./temp_audio.wav" + audio_file.save(audio_file_path) + + # Load the audio file with pydub + audio = AudioSegment.from_file(audio_file_path) + + # Convert to mono and resample to 16 kHz + audio = audio.set_channels(1).set_frame_rate(16000) + + # Export to a new temporary file + processed_audio_path = "./processed_temp_audio.wav" + audio.export(processed_audio_path, format="wav") + + # Load the processed audio with whisper + result = model.transcribe(processed_audio_path) + print(result) + + # Clean up the temporary audio files + os.remove(audio_file_path) + os.remove(processed_audio_path) + + return jsonify(result) + + except Exception as e: + print("An error occurred during transcription:") + print(str(e)) + return jsonify({"error": str(e)}), 500 + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/Supernovas/server/demo.html b/Supernovas/server/demo.html new file mode 100644 index 00000000..a28ce9ae --- /dev/null +++ b/Supernovas/server/demo.html @@ -0,0 +1,19 @@ + + + + + + Voice-enabled Geospatial Navigation + + + +

Voice-enabled Geospatial Navigation

+ +
+
+

Transcription

+
+
+
+ + diff --git a/Supernovas/server/demo.js b/Supernovas/server/demo.js new file mode 100644 index 00000000..280cefa8 --- /dev/null +++ b/Supernovas/server/demo.js @@ -0,0 +1,26 @@ +function sendAudioFile(file) { + const formData = new FormData(); + formData.append("audio", file); + + fetch("http://127.0.0.1:5000/transcribe", { + method: "POST", + body: formData + }) + .then(response => response.json()) + .then(data => { + const transcriptionTextElement = document.getElementById("transcriptionText"); + console.log(data) + transcriptionTextElement.textContent = data.text; + }) + .catch(error => { + console.error("Error during transcription:", error); + }); +} + + +document.getElementById("audioInput").addEventListener("change", function(event) { + const file = event.target.files[0]; + if (file) { + sendAudioFile(file); + } +}); diff --git a/Supernovas/server/index.html b/Supernovas/server/index.html new file mode 100644 index 00000000..eff5acd0 --- /dev/null +++ b/Supernovas/server/index.html @@ -0,0 +1,71 @@ + + + + + + Voice-enabled Geospatial Navigation + + + +

Voice-enabled Geospatial Navigation

+
+
+
+
+
+

Press the mic to start recording.

+ +
+ + + + diff --git a/Supernovas/server/isro.png b/Supernovas/server/isro.png new file mode 100644 index 00000000..88a308d0 Binary files /dev/null and b/Supernovas/server/isro.png differ diff --git a/Supernovas/server/nasa.gif b/Supernovas/server/nasa.gif new file mode 100644 index 00000000..5e804493 Binary files /dev/null and b/Supernovas/server/nasa.gif differ diff --git a/Supernovas/server/requirements.txt b/Supernovas/server/requirements.txt new file mode 100644 index 00000000..c303a1b8 --- /dev/null +++ b/Supernovas/server/requirements.txt @@ -0,0 +1,7 @@ +flask +flask-cors +torch +openai-whisper +pydub +pyaudio +numpy==1.24.1 diff --git a/Supernovas/server/script.js b/Supernovas/server/script.js new file mode 100644 index 00000000..2055a85b --- /dev/null +++ b/Supernovas/server/script.js @@ -0,0 +1,88 @@ +let mediaRecorder; +let audioChunks = []; +let isRecording = false; + +const micContainer = document.getElementById("micContainer"); +const micIcon = document.getElementById("micIcon"); +const wave = document.getElementById("wave"); +const status = document.getElementById("status"); +const textBox = document.getElementById("textBox"); + +micContainer.addEventListener("click", () => { + if (!isRecording) { + startRecording(); + } else { + stopRecording(); + } +}); + +async function startRecording() { + console.log("Recording started."); + status.textContent = "Recording..."; + isRecording = true; + + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + mediaRecorder = new MediaRecorder(stream); + audioChunks = []; + + mediaRecorder.ondataavailable = event => { + audioChunks.push(event.data); + }; + + mediaRecorder.onstop = () => { + const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); + const audioFile = new File([audioBlob], "recording.wav", { type: 'audio/wav' }); + sendAudioFile(audioFile); + status.textContent = "Recording stopped. Processing..."; + }; + + mediaRecorder.start(); + micIcon.classList.add("active"); + wave.style.opacity = 1; + } catch (error) { + console.error("Error accessing microphone:", error); + status.textContent = "Error accessing microphone. Please allow microphone access."; + } +} + +function stopRecording() { + console.log("Recording stopped."); + mediaRecorder.stop(); + isRecording = false; + micIcon.classList.remove("active"); + wave.style.opacity = 0; + status.textContent = "Recording stopped. Click the mic to start recording again."; +} + +function sendAudioFile(file) { + console.log("Sending audio file..."); + const formData = new FormData(); + formData.append("audio", file); + + // Replace with your server URL for transcription + fetch("http://127.0.0.1:5000/transcribe", { + method: "POST", + body: formData + }) + .then(response => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + console.log("Transcription response:", response); + return response.json(); + }) + .then(data => { + console.log("Transcription result:", data); + displayTranscription(data.text); + }) + .catch(error => { + console.error("Error during transcription:", error); + status.textContent = "Error during transcription."; + }); +} + +function displayTranscription(text) { + console.log("Transcription:", text); + textBox.textContent = text; // Display transcription result in text box +} \ No newline at end of file diff --git a/Supernovas/server/test_audio.wav b/Supernovas/server/test_audio.wav new file mode 100644 index 00000000..3d449fb1 Binary files /dev/null and b/Supernovas/server/test_audio.wav differ