diff --git a/Execution Plan for Satellite Overpass Notification and Data Retrieval Platform.pdf b/Execution Plan for Satellite Overpass Notification and Data Retrieval Platform.pdf new file mode 100644 index 00000000..242b045a Binary files /dev/null and b/Execution Plan for Satellite Overpass Notification and Data Retrieval Platform.pdf differ diff --git a/README.md b/README.md index abc30b8b..5e33c2fe 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,159 @@ # NASA Space Apps Challenge 2024 [Noida] -#### Team Name - -#### Problem Statement - -#### Team Leader Email - +#### Team Name - Tech Conqueror +#### Problem Statement - Landsat Reflectance Data: On the Fly and at Your +Fingertips +#### Team Leader Email - eeshuyadav123@gmail.com + + +#### Solution Overview +Our solution is a satellite overpass notification and data retrieval platform focused on Landsat imagery. Designed for users needing timely and precise satellite-based surface reflectance data, this prototype aims to simplify satellite imagery access and enhance environmental monitoring, research, and planning applications. The solution allows users to specify a target location, check upcoming Landsat satellite overpass times, and view a 3x3 grid of Landsat pixels centered on that location. Additionally, users can set notifications to alert them before an overpass, and the platform provides options to receive alerts via email or SMS. This system brings the power of satellite observation directly to users in real-time, aiding anyone from environmental scientists to policymakers in making data-driven decisions based on timely satellite imagery. + +The platform consists of three core components: + +Target Location and Overpass Notification Setup +Real-time Landsat Pixel Data Retrieval +Automated Notifications +### How it Works +#### 1. Target Location and Overpass Notification Setup +Users begin by selecting or inputting a target location. This input can be a latitude/longitude coordinate or a direct selection on an interactive map. Once the location is set, our platform determines when the next Landsat satellite will pass over the area by querying services such as the CelesTrak API. CelesTrak provides orbital parameters that can be used to predict satellite positions, enabling us to determine overpass times based on user-defined coordinates. With the overpass time calculated, users can set lead times (e.g., 1 day, 1 hour) before the satellite's expected arrival and choose their preferred notification method, either email or SMS. + +#### 2. Real-time Landsat Pixel Data Retrieval +The platform uses Google Earth Engine (GEE) or the USGS Earth Explorer API to fetch satellite imagery data from Landsat satellites. A 3x3 grid centered around the user-defined location is displayed, where each grid cell represents a Landsat pixel. This feature allows users to inspect nearby pixels and understand the local variations in surface reflectance. The grid data includes spectral reflectance values across multiple bands, and if available, surface temperature information derived from thermal infrared bands. This high-resolution, real-time grid visualization assists users in gaining insight into the spatial distribution of reflectance data in their area of interest. + +The retrieval of Landsat data considers user-set parameters, such as cloud cover threshold and acquisition time range, ensuring that the data is relevant and high-quality. Users can opt for recent acquisitions or specify a time range for historical data, allowing for a flexible analysis suited to their needs. + +#### 3. Automated Notifications +Upon setting a notification, Celery, a background task manager, schedules the notifications based on the user-defined lead time. Celery works in conjunction with Redis, a message broker, to ensure task reliability and timely notifications. Users are notified by their chosen method, either via email (using SendGrid) or SMS (using Twilio). Email notifications include the exact overpass time, while SMS notifications are sent in advance of the overpass as per the lead time preference. This ensures users are prepared to capture or analyze data as the satellite passes. + +By providing an intuitive interface for tracking satellite imagery overpasses and automating data notifications, this solution bridges the gap between remote sensing data accessibility and real-time field application. This capability is particularly valuable for environmental researchers, agriculturalists, and climate scientists who rely on up-to-date, reliable, and precise data to make informed decisions. Our platform enables easier satellite data access while supporting accurate and timely analysis, making it a robust tool for a range of geospatial and environmental monitoring applications. + + + + + +Execution Plan for Satellite Overpass Notification and Data Retrieval Platform +1. Repository Setup +Clone the Repository + +bash +Copy code +git clone +cd +Check Directory Structure +Ensure the repository has the following structure for smooth setup: + +arduino +Copy code +├── project_folder/ +│ ├── settings.py +│ ├── urls.py +│ ├── views.py +│ ├── tasks.py +│ ├── models.py +├── templates/ +├── static/ +├── Execution_Plan.pdf +├── requirements.txt +├── README.md +├── ... +2. Python Environment Setup +Set up a Python Virtual Environment + +bash +Copy code +python3 -m venv myenv +source myenv/bin/activate # For Windows: myenv\Scripts\activate +Install Requirements +Install all dependencies: + +bash +Copy code +pip install -r requirements.txt +3. Configure Celery and Redis for Background Tasks +Install Redis (if not already installed) +Follow instructions on Redis’s official website to install Redis on your system. + +Start Redis Server + +bash +Copy code +redis-server +Configure Celery in Django +Ensure that celery.py is configured correctly with Redis as the broker: + +python +Copy code +# In celery.py +app.conf.broker_url = 'redis://localhost:6379/0' +app.conf.result_backend = 'redis://localhost:6379/0' +Run Celery Worker +Open a new terminal and start the Celery worker: + +bash +Copy code +celery -A project_folder worker -l info +4. Configure Email and SMS Services +Email Setup (SendGrid or SMTP) +Configure email settings in settings.py: + +python +Copy code +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = 'smtp.sendgrid.net' # or your SMTP provider +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' +SMS Setup (Twilio) +Add your Twilio account credentials to settings.py: + +python +Copy code +TWILIO_ACCOUNT_SID = '' +TWILIO_AUTH_TOKEN = '' +TWILIO_PHONE_NUMBER = '' +5. Database Setup +Set up PostgreSQL Database +Create a PostgreSQL database and user, then add the database configurations in settings.py: + +python +Copy code +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'your_db_name', + 'USER': 'your_db_user', + 'PASSWORD': 'your_db_password', + 'HOST': 'localhost', + 'PORT': '5432', + } +} +Run Migrations + +bash +Copy code +python manage.py makemigrations +python manage.py migrate +6. Run Django Server +Start Development Server + +bash +Copy code +python manage.py runserver +Access the Application +Open your browser and go to http://localhost:8000 to view the application. + +7. Testing the Application +Set Up Test Data +Add initial data, including sample locations, by using the Django admin interface or through fixtures. + +Test Notification Scheduling + +Access the overpass time scheduling view. +Verify that notifications are scheduled and sent correctly according to lead times. +Verify Landsat Data Retrieval +Test the data retrieval views and ensure the 3x3 Landsat grid is fetched and displayed correctly. + -## A Brief of the Prototype: - What is your solution? and how it works. -## Code Execution Instruction: - *[If your solution is **not** application based, you can ignore this para] - - *The Repository must contain your **Execution Plan PDF**. diff --git a/SR_backend/__init__.py b/SR_backend/__init__.py new file mode 100644 index 00000000..b7b5dfeb --- /dev/null +++ b/SR_backend/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/SR_backend/__pycache__/__init__.cpython-312.pyc b/SR_backend/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..42bceda2 Binary files /dev/null and b/SR_backend/__pycache__/__init__.cpython-312.pyc differ diff --git a/SR_backend/__pycache__/celery.cpython-312.pyc b/SR_backend/__pycache__/celery.cpython-312.pyc new file mode 100644 index 00000000..d2ecd3bd Binary files /dev/null and b/SR_backend/__pycache__/celery.cpython-312.pyc differ diff --git a/SR_backend/__pycache__/settings.cpython-312.pyc b/SR_backend/__pycache__/settings.cpython-312.pyc new file mode 100644 index 00000000..e7288f5a Binary files /dev/null and b/SR_backend/__pycache__/settings.cpython-312.pyc differ diff --git a/SR_backend/__pycache__/urls.cpython-312.pyc b/SR_backend/__pycache__/urls.cpython-312.pyc new file mode 100644 index 00000000..fa991005 Binary files /dev/null and b/SR_backend/__pycache__/urls.cpython-312.pyc differ diff --git a/SR_backend/__pycache__/wsgi.cpython-312.pyc b/SR_backend/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 00000000..6e06762a Binary files /dev/null and b/SR_backend/__pycache__/wsgi.cpython-312.pyc differ diff --git a/SR_backend/asgi.py b/SR_backend/asgi.py new file mode 100644 index 00000000..c8c391c5 --- /dev/null +++ b/SR_backend/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for SR_backend project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "SR_backend.settings") + +application = get_asgi_application() diff --git a/SR_backend/celery.py b/SR_backend/celery.py new file mode 100644 index 00000000..4622e930 --- /dev/null +++ b/SR_backend/celery.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import, unicode_literals +import os +from celery import Celery + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings') + +app = Celery('your_project') +app.config_from_object('django.conf:settings', namespace='CELERY') +app.autodiscover_tasks() diff --git a/SR_backend/settings.py b/SR_backend/settings.py new file mode 100644 index 00000000..135a3a0f --- /dev/null +++ b/SR_backend/settings.py @@ -0,0 +1,138 @@ +from pathlib import Path +# import ee +# import os +# from dotenv import load_dotenv + +# Load environment variables from the .env file +# load_dotenv() + +# # Get service account email and private key path from environment variables +# SERVICE_ACCOUNT = os.getenv("GEE_SERVICE_ACCOUNT") +# PRIVATE_KEY_PATH = os.getenv("GEE_PRIVATE_KEY_PATH") + +# # Initialize Earth Engine with the Service Account credentials using the key file path +# credentials = ee.ServiceAccountCredentials(SERVICE_ACCOUNT, PRIVATE_KEY_PATH) +# ee.Initialize(credentials) + +# Django settings +BASE_DIR = Path(__file__).resolve().parent.parent + + +SECRET_KEY = "django-insecure-%%44dv42)-9!ro5mo8n6#pfo42vq(+da-=ukyx1&lumq8=57^i" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + 'rest_framework', + 'api_location', +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "SR_backend.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "SR_backend.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.1/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +CORS_ALLOWED_ORIGINS = [ + "http://localhost:8000", + "http://127.0.0.1:8000", +] + + +# your_project/settings.py +CELERY_BROKER_URL = 'redis://localhost:6379/0' +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' + +# Email configurations for SendGrid +EMAIL_BACKEND = "sendgrid_backend.SendgridBackend" +SENDGRID_API_KEY = "your_sendgrid_api_key" + diff --git a/SR_backend/urls.py b/SR_backend/urls.py new file mode 100644 index 00000000..1a953c30 --- /dev/null +++ b/SR_backend/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/', include('api_location.urls')), # Include your app URLs here +] diff --git a/SR_backend/wsgi.py b/SR_backend/wsgi.py new file mode 100644 index 00000000..d4cd207c --- /dev/null +++ b/SR_backend/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for SR_backend project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "SR_backend.settings") + +application = get_wsgi_application() diff --git a/api_location/__init__.py b/api_location/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api_location/__pycache__/__init__.cpython-312.pyc b/api_location/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..9866d966 Binary files /dev/null and b/api_location/__pycache__/__init__.cpython-312.pyc differ diff --git a/api_location/__pycache__/admin.cpython-312.pyc b/api_location/__pycache__/admin.cpython-312.pyc new file mode 100644 index 00000000..06131943 Binary files /dev/null and b/api_location/__pycache__/admin.cpython-312.pyc differ diff --git a/api_location/__pycache__/apps.cpython-312.pyc b/api_location/__pycache__/apps.cpython-312.pyc new file mode 100644 index 00000000..9c584186 Binary files /dev/null and b/api_location/__pycache__/apps.cpython-312.pyc differ diff --git a/api_location/__pycache__/models.cpython-312.pyc b/api_location/__pycache__/models.cpython-312.pyc new file mode 100644 index 00000000..cf6732e0 Binary files /dev/null and b/api_location/__pycache__/models.cpython-312.pyc differ diff --git a/api_location/__pycache__/serializers.cpython-312.pyc b/api_location/__pycache__/serializers.cpython-312.pyc new file mode 100644 index 00000000..f52d5fdc Binary files /dev/null and b/api_location/__pycache__/serializers.cpython-312.pyc differ diff --git a/api_location/__pycache__/tasks.cpython-312.pyc b/api_location/__pycache__/tasks.cpython-312.pyc new file mode 100644 index 00000000..3bbb5204 Binary files /dev/null and b/api_location/__pycache__/tasks.cpython-312.pyc differ diff --git a/api_location/__pycache__/urls.cpython-312.pyc b/api_location/__pycache__/urls.cpython-312.pyc new file mode 100644 index 00000000..047f0a54 Binary files /dev/null and b/api_location/__pycache__/urls.cpython-312.pyc differ diff --git a/api_location/__pycache__/views.cpython-312.pyc b/api_location/__pycache__/views.cpython-312.pyc new file mode 100644 index 00000000..4ddfe6f4 Binary files /dev/null and b/api_location/__pycache__/views.cpython-312.pyc differ diff --git a/api_location/admin.py b/api_location/admin.py new file mode 100644 index 00000000..c16ba1dd --- /dev/null +++ b/api_location/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from .models import Location + +@admin.register(Location) +class LocationAdmin(admin.ModelAdmin): + list_display = ('place_name', 'latitude', 'longitude') # Fields to display in the list view + search_fields = ('place_name',) # Add search capability on place_name + list_filter = ('latitude', 'longitude') # Optional: filter options for latitude and longitude + + # Optionally customize the form layout if desired + fieldsets = ( + (None, { + 'fields': ('place_name', 'latitude', 'longitude') + }), + ) diff --git a/api_location/apps.py b/api_location/apps.py new file mode 100644 index 00000000..215dd20b --- /dev/null +++ b/api_location/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiLocationConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "api_location" diff --git a/api_location/migrations/0001_initial.py b/api_location/migrations/0001_initial.py new file mode 100644 index 00000000..24594d66 --- /dev/null +++ b/api_location/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 5.1.1 on 2024-10-05 15:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Location", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("place_name", models.CharField(blank=True, max_length=100, null=True)), + ("latitude", models.FloatField()), + ("longitude", models.FloatField()), + ], + ), + ] diff --git a/api_location/migrations/0002_alter_location_latitude_alter_location_longitude.py b/api_location/migrations/0002_alter_location_latitude_alter_location_longitude.py new file mode 100644 index 00000000..289e6bb8 --- /dev/null +++ b/api_location/migrations/0002_alter_location_latitude_alter_location_longitude.py @@ -0,0 +1,22 @@ +# Generated by Django 5.1.1 on 2024-10-05 16:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("api_location", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="location", + name="latitude", + field=models.FloatField(blank=True, null=True), + ), + migrations.AlterField( + model_name="location", + name="longitude", + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/api_location/migrations/__init__.py b/api_location/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api_location/migrations/__pycache__/0001_initial.cpython-312.pyc b/api_location/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 00000000..9734661f Binary files /dev/null and b/api_location/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/api_location/migrations/__pycache__/0002_alter_location_latitude_alter_location_longitude.cpython-312.pyc b/api_location/migrations/__pycache__/0002_alter_location_latitude_alter_location_longitude.cpython-312.pyc new file mode 100644 index 00000000..394de2d0 Binary files /dev/null and b/api_location/migrations/__pycache__/0002_alter_location_latitude_alter_location_longitude.cpython-312.pyc differ diff --git a/api_location/migrations/__pycache__/__init__.cpython-312.pyc b/api_location/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..5d1d9daf Binary files /dev/null and b/api_location/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/api_location/models.py b/api_location/models.py new file mode 100644 index 00000000..3034f64a --- /dev/null +++ b/api_location/models.py @@ -0,0 +1,35 @@ +# from django.db import models + +# class Location(models.Model): +# place_name = models.CharField(max_length=100, null=True, blank=True) +# latitude = models.FloatField() +# longitude = models.FloatField() + +# def __str__(self): +# return f"{self.place_name or f'Lat: {self.latitude}, Lon: {self.longitude}'}" + + + +# from django.db import models + +# class Location(models.Model): +# place_name = models.CharField(max_length=100, null=True, blank=True) +# latitude = models.FloatField(null=True, blank=True) +# longitude = models.FloatField(null=True, blank=True) + +# def __str__(self): +# return f"{self.place_name or f'{self.latitude}, {self.longitude}'}" + + + +from django.db import models + +class Location(models.Model): + place_name = models.CharField(max_length=100, null=True, blank=True) + latitude = models.FloatField(null=True, blank=True) + longitude = models.FloatField(null=True, blank=True) + + def __str__(self): + return f"{self.place_name or f'{self.latitude}, {self.longitude}'}" + + diff --git a/api_location/serializers.py b/api_location/serializers.py new file mode 100644 index 00000000..0e80b014 --- /dev/null +++ b/api_location/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import Location + +class LocationSerializer(serializers.ModelSerializer): + class Meta: + model = Location + fields = ['id', 'place_name', 'latitude', 'longitude'] diff --git a/api_location/tasks.py b/api_location/tasks.py new file mode 100644 index 00000000..8cf1c6d8 --- /dev/null +++ b/api_location/tasks.py @@ -0,0 +1,23 @@ +from celery import shared_task +from django.core.mail import send_mail +from twilio.rest import Client +from django.conf import settings + +@shared_task +def send_email_notification(email, overpass_time): + send_mail( + 'Landsat Overpass Notification', + f'Landsat satellite will pass over your target location at {overpass_time}.', + 'no-reply@landsatapp.com', + [email], + fail_silently=False, + ) + +@shared_task +def send_sms_notification(phone_number, overpass_time): + client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN) + client.messages.create( + body=f'Landsat satellite will pass over your target location at {overpass_time}.', + from_=settings.TWILIO_PHONE_NUMBER, + to=phone_number + ) diff --git a/api_location/tests.py b/api_location/tests.py new file mode 100644 index 00000000..de8bdc00 --- /dev/null +++ b/api_location/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/api_location/urls.py b/api_location/urls.py new file mode 100644 index 00000000..6b33b220 --- /dev/null +++ b/api_location/urls.py @@ -0,0 +1,14 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import LocationViewSet,OverpassViewSet + +router = DefaultRouter() +router.register('locations', LocationViewSet) +router.register('overpass', OverpassViewSet, basename='overpass') + +urlpatterns = [ + path('', include(router.urls)), + # path('overpasses/', OverpassViewSet.as_view({'get': 'list'}), name='overpasses'), +] + + diff --git a/api_location/views.py b/api_location/views.py new file mode 100644 index 00000000..6ac2143b --- /dev/null +++ b/api_location/views.py @@ -0,0 +1,251 @@ +from rest_framework import viewsets +from rest_framework.response import Response +from .models import Location +from .serializers import LocationSerializer +import requests +import ee +from django.contrib import admin +import requests +from skyfield.api import load, Topos +from datetime import datetime, timedelta, timezone +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from django.shortcuts import get_object_or_404 +from .tasks import send_email_notification, send_sms_notification +from datetime import datetime, timedelta, timezone + + + +class LocationViewSet(viewsets.ModelViewSet): + queryset = Location.objects.all() + serializer_class = LocationSerializer + + def create(self, request, *args, **kwargs): + data = request.data + + # Handle place name + if 'place_name' in data: + if (data.get('latitude') is None or data.get('longitude') is None): + geocode_url = f"https://nominatim.openstreetmap.org/search?q={data['place_name']}&format=json" + response = requests.get(geocode_url) + if response.status_code == 200 and len(response.json()) > 0: + geo_data = response.json()[0] + data['latitude'] = geo_data['lat'] + data['longitude'] = geo_data['lon'] + else: + return Response({"error": "Invalid place name"}, status=400) + + # Handle latitude and longitude directly + elif (data.get('latitude') is not None and data.get('longitude') is not None): + # If latitude and longitude are present, no further action is needed + pass + + # Check for valid input + elif 'place_name' not in data and (data.get('latitude') is None or data.get('longitude') is None): + return Response({"error": "Please provide either place_name or both latitude and longitude"}, status=400) + + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + return Response(serializer.data) + +class LocationAdmin(admin.ModelAdmin): + list_display = ('place_name', 'latitude', 'longitude') + search_fields = ('place_name',) + list_filter = ('latitude', 'longitude') + ordering = ('place_name',) + readonly_fields = ('latitude', 'longitude') + +class LandsatDataViewSet(viewsets.ViewSet): + + def list(self, request): + location_id = request.query_params.get('location') + location = Location.objects.get(id=location_id) + + # Get the latitude and longitude of the user-defined location + lat = location.latitude + lon = location.longitude + + # Define the area of interest (3x3 pixel grid around the central point) + pixel_radius = 0.0003 # Approximate size for 3x3 grid at Landsat resolution + region = ee.Geometry.Rectangle( + [lon - pixel_radius, lat - pixel_radius, lon + pixel_radius, lat + pixel_radius] + ) + + # Fetch Landsat 8 Surface Reflectance data for the location + landsat_collection = ee.ImageCollection("LANDSAT/LC08/C01/T1_SR") \ + .filterBounds(region) \ + .filterDate('2023-01-01', '2023-12-31') \ + .sort('CLOUD_COVER') \ + .first() + + # Select the relevant bands (e.g., B4: Red, B5: Near Infrared) + bands = landsat_collection.select(['B4', 'B5']) + + # Sample the data at the defined grid + pixel_data = bands.sample(region, scale=30).getInfo() + + # Extract the data from the GEE response + landsat_grid = [] + for feature in pixel_data['features']: + lat_lon = feature['geometry']['coordinates'] + sr_value = feature['properties']['B4'] # Surface Reflectance value for band 4 (example) + landsat_grid.append({ + "lat": lat_lon[1], + "lon": lat_lon[0], + "sr_value": sr_value + }) + + return Response({"grid": landsat_grid}) + + + +from skyfield.api import load, Topos +from datetime import datetime, timedelta, timezone +import requests +from rest_framework import status +from rest_framework.response import Response +from rest_framework import viewsets +from .models import Location + +# class OverpassViewSet(viewsets.ViewSet): +# def list(self, request): +# location_id = request.query_params.get('location') +# try: +# location = Location.objects.get(id=location_id) +# except Location.DoesNotExist: +# return Response({"error": "Location not found"}, status=status.HTTP_404_NOT_FOUND) + +# lat = location.latitude +# lon = location.longitude + +# # Step 1: Fetch TLE data from CelesTrak +# tle_url = f"https://celestrak.com/NORAD/elements/gp.php?CATNR=39084" +# response = requests.get(tle_url) + +# if response.status_code != 200: +# return Response({"error": "Unable to retrieve TLE data"}, status_code=status.HTTP_400_BAD_REQUEST) + +# tle_lines = response.text.splitlines() +# if len(tle_lines) < 2: +# return Response({"error": "Invalid TLE data received"}, status_code=status.HTTP_400_BAD_REQUEST) + +# # Step 2: Use Skyfield to calculate the next overpass time +# tle_data = "\n".join(tle_lines) # Joining TLE lines into a single string + +# # Use load.tle instead of load.tle_file +# satellite = load.tle(tle_data) # Loading TLE data correctly +# ts = load.timescale() +# observer_location = Topos(latitude_degrees=lat, longitude_degrees=lon) + +# # Step 3: Calculate the next overpass within a 24-hour window +# start_time = datetime.now(timezone.utc) +# end_time = start_time + timedelta(days=1) +# t0 = ts.utc(start_time.year, start_time.month, start_time.day, start_time.hour, start_time.minute) +# t1 = ts.utc(end_time.year, end_time.month, end_time.day, end_time.hour, end_time.minute) + +# # Step 4: Determine satellite passes +# time, events = satellite.find_events(observer_location, t0, t1, altitude_degrees=30.0) + +# # Get the next overpass time +# for ti, event in zip(time, events): +# if event == 0: # 0 indicates rise event (when satellite becomes visible) +# overpass_time = ti.utc_iso() +# return Response({"overpass_time": overpass_time}) + +# return Response({"error": "No overpass found in the next 24 hours"}, status=status.HTTP_404_NOT_FOUND) + + +from rest_framework import status, viewsets +from rest_framework.response import Response +from .models import Location +from skyfield.api import load, Topos +import requests +from datetime import datetime, timedelta, timezone + +class OverpassViewSet(viewsets.ViewSet): + def list(self, request): + location_id = request.query_params.get('location') + try: + location = Location.objects.get(id=location_id) + except Location.DoesNotExist: + return Response({"error": "Location not found"}, status=status.HTTP_404_NOT_FOUND) + + lat = location.latitude + lon = location.longitude + + # Step 1: Fetch TLE data from CelesTrak + tle_url = "https://celestrak.com/NORAD/elements/gp.php?CATNR=39084" # Landsat 8 NORAD ID + response = requests.get(tle_url) + + if response.status_code != 200: + return Response({"error": "Unable to retrieve TLE data"}, status=status.HTTP_400_BAD_REQUEST) + + tle_lines = response.text.strip().splitlines() + if len(tle_lines) < 3: # Ensure at least 3 lines for a valid TLE + return Response({"error": "Invalid TLE data received"}, status=status.HTTP_400_BAD_REQUEST) + + # Clean up TLE lines and remove leading/trailing whitespace + tle_lines = [line.strip() for line in tle_lines] + + # Debugging output to check TLE lines + print("TLE Lines:") + for line in tle_lines: + print(f"'{line}'") # Print each line with quotes to identify whitespace + + # Step 2: Use Skyfield to calculate the next overpass time + try: + # Use load.tle_file with properly formatted TLE lines + satellites = load.tle_file('\n'.join(tle_lines)) + satellite = satellites[0] # Get the first satellite from the loaded TLE lines + except Exception as e: + return Response({"error": f"Error loading satellite: {str(e)}"}, status=status.HTTP_400_BAD_REQUEST) + + ts = load.timescale() + observer_location = Topos(latitude_degrees=lat, longitude_degrees=lon) + + # Step 3: Calculate the next overpass within a 24-hour window + start_time = datetime.now(timezone.utc) + end_time = start_time + timedelta(days=1) + t0 = ts.utc(start_time.year, start_time.month, start_time.day, start_time.hour, start_time.minute) + t1 = ts.utc(end_time.year, end_time.month, end_time.day, end_time.hour, end_time.minute) + + # Step 4: Determine satellite passes + time, events = satellite.find_events(observer_location, t0, t1, altitude_degrees=30.0) + + # Get the next overpass time + for ti, event in zip(time, events): + if event == 0: # 0 indicates rise event (when satellite becomes visible) + overpass_time = ti.utc_iso() + return Response({"overpass_time": overpass_time}) + + return Response({"error": "No overpass found in the next 24 hours"}, status=status.HTTP_404_NOT_FOUND) + + + +class NotificationViewSet(viewsets.ViewSet): + def create(self, request): + location_id = request.data.get('location') + email = request.data.get('email') + phone_number = request.data.get('phone_number') + lead_time = int(request.data.get('lead_time', 60)) # lead time in minutes + + location = get_object_or_404(Location, id=location_id) + + # Mock overpass time (in a real scenario, calculate or fetch overpass time) + overpass_time_str = "2024-10-12T14:55:00Z" + overpass_time = datetime.strptime(overpass_time_str, "%Y-%m-%dT%H:%M:%SZ") + + # Schedule time = Overpass time - Lead time + notification_time = overpass_time - timedelta(minutes=lead_time) + countdown = (notification_time - datetime.now(timezone.utc)).total_seconds() + + if countdown > 0: + if email: + send_email_notification.apply_async((email, overpass_time_str), countdown=countdown) + if phone_number: + send_sms_notification.apply_async((phone_number, overpass_time_str), countdown=countdown) + return Response({"message": "Notification scheduled."}) + else: + return Response({"error": "Lead time is too short; overpass has already occurred or is too soon."}, status=400) diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 00000000..4154ceea Binary files /dev/null and b/db.sqlite3 differ diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..7fe338b7 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1 @@ +Rishi \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 00000000..1a932731 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "SR_backend.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main()