# Satellite AWD FastAPI — Full Codebase

## Tech Stack

| Purpose           | Library      |
| ----------------- | ------------ |
| API               | FastAPI      |
| Raster processing | Rasterio     |
| NDVI              | EarthPy      |
| GIS               | GeoPandas    |
| ML                | scikit-learn |
| Export            | simplekml    |

---

# Final Project Structure

```text
Sattelite-Image/
│
├── app/
│   ├── __init__.py
│   ├── main.py
│   │
│   ├── routes/
│   │   ├── __init__.py
│   │   ├── upload.py
│   │   ├── analysis.py
│   │   ├── export.py
│   │   └── ml.py
│   │
│   ├── services/
│   │   ├── __init__.py
│   │   ├── raster_service.py
│   │   ├── ndvi_service.py
│   │   ├── biomass_service.py
│   │   ├── geojson_service.py
│   │   ├── kml_service.py
│   │   └── classification_service.py
│   │
│   ├── ml/
│   │   ├── __init__.py
│   │   ├── train.py
│   │   ├── predict.py
│   │   └── model.pkl
│   │
│   └── utils/
│       └── file_utils.py
│
├── data/
│   ├── raw/
│   ├── processed/
│   ├── training/
│   └── eurosat/
│
├── outputs/
│   ├── geojson/
│   ├── kml/
│   └── reports/
│
├── requirements.txt
└── README.md
```

---

# requirements.txt

```txt
fastapi
uvicorn
numpy
opencv-python
rasterio
earthpy
geopandas
shapely
matplotlib
simplekml
python-multipart
scikit-learn
joblib
pandas
Pillow
```

---

# Setup

## Create Virtual Environment

```bash
python3 -m venv venv
```

## Activate

```bash
source venv/bin/activate
```

## Install Packages

```bash
pip install -r requirements.txt
```

---

# Create Required Folders

```bash
mkdir -p app/routes
mkdir -p app/services
mkdir -p app/ml
mkdir -p app/utils
mkdir -p data/raw
mkdir -p data/processed
mkdir -p data/training
mkdir -p data/eurosat
mkdir -p outputs/geojson
mkdir -p outputs/kml
mkdir -p outputs/reports
```

---

# Create **init**.py Files

```bash
touch app/__init__.py
touch app/routes/__init__.py
touch app/services/__init__.py
touch app/ml/__init__.py
```

---

# app/main.py

```python
from fastapi import FastAPI

from app.routes import upload
from app.routes import analysis
from app.routes import export
from app.routes import ml

app = FastAPI(
    title="Satellite AWD API",
    version="1.0.0"
)

app.include_router(upload.router)
app.include_router(analysis.router)
app.include_router(export.router)
app.include_router(ml.router)


@app.get("/")
def home():
    return {
        "message": "Satellite AWD API Running"
    }
```

---

# app/routes/upload.py

```python
from fastapi import APIRouter, UploadFile, File
import shutil
import os

router = APIRouter(tags=["Upload"])


@router.post("/upload")
async def upload_image(file: UploadFile = File(...)):

    os.makedirs("data/raw", exist_ok=True)

    file_path = f"data/raw/{file.filename}"

    with open(file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)

    return {
        "message": "File uploaded successfully",
        "path": file_path
    }
```

---

# app/routes/analysis.py

```python
from fastapi import APIRouter

from app.services.raster_service import load_bands
from app.services.ndvi_service import calculate_ndvi
from app.services.biomass_service import (
    estimate_agb,
    estimate_bgb
)

router = APIRouter(tags=["Analysis"])


@router.post("/analyze")
async def analyze(image_path: str):

    bands = load_bands(image_path)

    ndvi = calculate_ndvi(
        bands["red"],
        bands["nir"]
    )

    agb = estimate_agb(ndvi)

    bgb = estimate_bgb(agb)

    return {
        "mean_ndvi": float(ndvi.mean()),
        "max_ndvi": float(ndvi.max()),
        "min_ndvi": float(ndvi.min()),
        "mean_agb": float(agb.mean()),
        "mean_bgb": float(bgb.mean())
    }
```

---

# app/routes/export.py

```python
from fastapi import APIRouter

from app.services.geojson_service import create_geojson
from app.services.kml_service import create_kml

router = APIRouter(tags=["Export"])


@router.get("/export/geojson")
async def export_geojson():

    path = create_geojson()

    return {
        "geojson": path
    }


@router.get("/export/kml")
async def export_kml():

    path = create_kml()

    return {
        "kml": path
    }
```

---

# app/routes/ml.py

```python
from fastapi import APIRouter
import cv2

from app.services.classification_service import classify_region

router = APIRouter(tags=["ML"])


@router.post("/classify")
async def classify(image_path: str):

    image = cv2.imread(image_path)

    if image is None:
        return {
            "error": "Image not found"
        }

    prediction = classify_region(image)

    return {
        "prediction": prediction
    }
```

---

# app/services/raster_service.py

```python
import rasterio


def load_bands(image_path):

    with rasterio.open(image_path) as src:

        blue = src.read(1).astype(float)
        green = src.read(2).astype(float)
        red = src.read(3).astype(float)
        nir = src.read(4).astype(float)

    return {
        "blue": blue,
        "green": green,
        "red": red,
        "nir": nir
    }
```

---

# app/services/ndvi_service.py

```python
import earthpy.spatial as es


def calculate_ndvi(red, nir):

    ndvi = es.normalized_diff(nir, red)

    return ndvi
```

---

# app/services/biomass_service.py

```python
import numpy as np


def estimate_agb(ndvi):

    agb = (ndvi + 1) * 50

    agb[agb < 0] = 0

    return agb



def estimate_bgb(agb):

    bgb = agb * 0.26

    return bgb
```

---

# app/services/geojson_service.py

```python
import geopandas as gpd
from shapely.geometry import Polygon


def create_geojson():

    polygon = Polygon([
        (88.1, 22.5),
        (88.2, 22.5),
        (88.2, 22.6),
        (88.1, 22.6)
    ])

    gdf = gpd.GeoDataFrame(
        {
            "class": ["vegetation"]
        },
        geometry=[polygon],
        crs="EPSG:4326"
    )

    output_path = "outputs/geojson/output.geojson"

    gdf.to_file(output_path, driver="GeoJSON")

    return output_path
```

---

# app/services/kml_service.py

```python
import simplekml


def create_kml():

    kml = simplekml.Kml()

    kml.newpolygon(
        name="Vegetation Zone",
        outerboundaryis=[
            (88.1, 22.5),
            (88.2, 22.5),
            (88.2, 22.6),
            (88.1, 22.6)
        ]
    )

    output_path = "outputs/kml/output.kml"

    kml.save(output_path)

    return output_path
```

---

# app/services/classification_service.py

```python
import numpy as np

from app.ml.predict import predict_land_type



def classify_region(image):

    blue_mean = np.mean(image[:, :, 0])
    green_mean = np.mean(image[:, :, 1])
    red_mean = np.mean(image[:, :, 2])

    ndvi = (green_mean - red_mean) / (
        green_mean + red_mean + 1e-10
    )

    features = [
        red_mean,
        green_mean,
        blue_mean,
        ndvi
    ]

    return predict_land_type(features)
```

---

# app/ml/train.py

```python
import os
import cv2
import numpy as np
import joblib

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

DATASET_PATH = "data/eurosat"

features = []
labels = []

CLASS_MAPPING = {
    "River": 0,
    "SeaLake": 0,

    "Forest": 1,
    "Pasture": 1,
    "HerbaceousVegetation": 1,

    "Residential": 2,
    "Industrial": 2,
    "Highway": 2,

    "AnnualCrop": 3
}

for class_name, label in CLASS_MAPPING.items():

    class_path = os.path.join(DATASET_PATH, class_name)

    if not os.path.exists(class_path):
        continue

    for image_name in os.listdir(class_path):

        image_path = os.path.join(class_path, image_name)

        image = cv2.imread(image_path)

        if image is None:
            continue

        image = cv2.resize(image, (64, 64))

        blue_mean = np.mean(image[:, :, 0])
        green_mean = np.mean(image[:, :, 1])
        red_mean = np.mean(image[:, :, 2])

        ndvi = (green_mean - red_mean) / (
            green_mean + red_mean + 1e-10
        )

        features.append([
            red_mean,
            green_mean,
            blue_mean,
            ndvi
        ])

        labels.append(label)

X = np.array(features)
y = np.array(labels)

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42
)

model = RandomForestClassifier(
    n_estimators=100,
    random_state=42
)

model.fit(X_train, y_train)

predictions = model.predict(X_test)

print(classification_report(y_test, predictions))

joblib.dump(model, "app/ml/model.pkl")

print("Model saved successfully")
```

---

# app/ml/predict.py

```python
import joblib
import os

MODEL_PATH = "app/ml/model.pkl"

LABELS = {
    0: "water",
    1: "vegetation",
    2: "urban",
    3: "barren"
}

model = None

if os.path.exists(MODEL_PATH):
    model = joblib.load(MODEL_PATH)



def predict_land_type(features):

    if model is None:
        return "Model not trained"

    prediction = model.predict([features])[0]

    return LABELS[prediction]
```

---

# app/utils/file_utils.py

```python
import os


def ensure_directory(path):

    os.makedirs(path, exist_ok=True)
```

---

# Run ML Training

```bash
python app/ml/train.py
```

This generates:

```text
app/ml/model.pkl
```

---

# Run FastAPI

```bash
uvicorn app.main:app --host 0.0.0.0 --port 12000
```

---

# Production Run

```bash
nohup venv/bin/uvicorn app.main:app \
--host 0.0.0.0 \
--port 12000 \
> app.log 2>&1 &
```

---

# Swagger API

```text
http://YOUR_SERVER_IP:12000/docs
```

---

# EuroSAT Dataset

Download:

[https://github.com/phelber/eurosat](https://github.com/phelber/eurosat)

Extract into:

```text
data/eurosat/
```

---

# API Endpoints

## Upload Satellite Image

```http
POST /upload
```

---

## Analyze NDVI + Biomass

```http
POST /analyze
```

Example body:

```json
{
  "image_path": "data/raw/sample.tif"
}
```

---

## Classify Land Type

```http
POST /classify
```

---

## Export GeoJSON

```http
GET /export/geojson
```

---

## Export KML

```http
GET /export/kml
```

---

# Recommended Next Upgrades

## Version 2

* real polygon extraction
* raster masking
* water segmentation
* urban heatmaps
* Google Maps integration

## Version 3

* Sentinel-2 GeoTIFF support
* true NIR band NDVI
* change detection
* CNN models
* UNet segmentation
* PostGIS

---

# Recommended Image Sources

## Sentinel Playground

[https://apps.sentinel-hub.com/sentinel-playground/](https://apps.sentinel-hub.com/sentinel-playground/)

## EarthExplorer

[https://earthexplorer.usgs.gov/](https://earthexplorer.usgs.gov/)

## Copernicus Data Space

[https://dataspace.copernicus.eu/](https://dataspace.copernicus.eu/)



python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt

To run api for ssh session
uvicorn api_filter:app --reload --port 8000

To run continuously
nohup uvicorn main:app --host 0.0.0.0 --port 12000 > app.log 2>&1 &

To kill
sudo lsof -i :12000
sudo kill -9 858138
