Water Intelligence System for Agriculture using Earth Observation
AgriTech-WISE est une plateforme web avancée qui révolutionne le monitoring de l'humidité du sol en agriculture grâce à l'imagerie satellite et l'intelligence artificielle. Le système exploite les données Sentinel-1 (radar) et Sentinel-2 (optique) pour fournir des cartes d'humidité précises et actualisées.
- Double source satellite : Sentinel-1 (radar, tout temps) et Sentinel-2 (optique, haute résolution)
- IA adaptative : Sélection automatique du meilleur indice spectral selon les conditions
- Calibration terrain : Régression linéaire avec vos mesures réelles
- Visualisation interactive : Cartes dynamiques avec détection de changements temporels
- Cloud-based : Architecture Firebase pour scalabilité et fiabilité
- Irrigation de précision : Optimiser l'apport en eau selon les besoins réels
- Gestion parcellaire : Surveiller plusieurs zones simultanément
- Détection de stress hydrique : Alertes précoces sur les zones problématiques
- Analyse temporelle : Comparer l'évolution de l'humidité entre périodes
- Recherche agronomique : Calibration et validation d'indices spectraux
- Python 3.8+ : Langage principal
- Flask : Framework web
- Google Earth Engine API : Traitement d'images satellites
- scikit-learn : Régression et machine learning
- pandas/numpy : Traitement de données
- Firebase Realtime Database : Données utilisateurs et zones
- Firebase Storage : Fichiers CSV et exports
- Firebase Authentication : Gestion sécurisée des comptes
- Leaflet.js : Cartographie interactive
- Bootstrap : Interface responsive
- JavaScript ES6+ : Interactions dynamiques
| Indice | Formule | Usage |
|---|---|---|
| NDVI | (NIR - Red) / (NIR + Red) | Vigueur végétation |
| NDWI | (Green - NIR) / (Green + NIR) | Contenu en eau végétation |
| MNDWI | (Green - SWIR) / (Green + SWIR) | Eau de surface |
| MSI | SWIR / NIR | Stress hydrique |
| Indice | Formule | Usage |
|---|---|---|
| RVI | 4 × VH / (VV + VH) | Humidité du sol |
┌─────────────────┐
│ Images satellite│
│ Sentinel-1/2 │
└────────┬────────┘
│
▼
┌─────────────────┐
│Calcul d'indices │
│ spectraux │
└────────┬────────┘
│
▼
┌─────────────────┐ ┌──────────────┐
│ Mesures terrain │────▶│ Régression │
│ (CSV) │ │ linéaire │
└─────────────────┘ └──────┬───────┘
│
▼
┌──────────────────┐
│Sélection meilleur│
│indice (min MSE) │
└────────┬─────────┘
│
▼
┌─────────────────────┐
│Carte d'humidité │
│calibrée et validée │
└─────────────────────┘
- Python 3.8+
- Compte Google Earth Engine → Inscription
- Projet Firebase → Console Firebase
git clone https://github.com/votre-username/agritech-wise.git
cd agritech-wisepython -m venv venv
# Linux/Mac
source venv/bin/activate
# Windows
venv\Scripts\activatepip install -r requirements.txtearthengine authenticateSuivre le lien pour autoriser l'accès à votre compte GEE.
Initialiser avec votre projet :
earthengine set_project YOUR_EE_PROJECT_IDa) Créer le projet Firebase
- Aller sur Firebase Console
- Créer un nouveau projet
- Activer :
- Authentication (Email/Password)
- Realtime Database
- Storage
b) Télécharger les credentials
- Project Settings → Service Accounts
- Generate New Private Key
- Sauvegarder le JSON dans le dossier racine
c) Configuration des variables
Copier .env.example vers .env :
cp .env.example .envRemplir avec vos valeurs Firebase (depuis Project Settings → General) :
FIREBASE_API_KEY=AIza...
FIREBASE_AUTH_DOMAIN=votre-projet.firebaseapp.com
FIREBASE_DATABASE_URL=https://votre-projet.firebaseio.com
FIREBASE_PROJECT_ID=votre-projet
FIREBASE_STORAGE_BUCKET=votre-projet.appspot.com
FIREBASE_MESSAGING_SENDER_ID=123456789
FIREBASE_APP_ID=1:123456789:web:abc123
FIREBASE_MEASUREMENT_ID=G-ABC123
EE_PROJECT_ID=votre-projet-ee
FLASK_SECRET_KEY=generer-une-cle-secrete-forted) Adapter firebase_config.py
Copier le template :
cp firebase_config.template.py firebase_config.pyMettre à jour le chemin vers votre fichier credentials JSON.
python app.pyAccéder à http://localhost:5000
Vos mesures terrain doivent être au format CSV avec au minimum :
| Colonne | Type | Description | Exemple |
|---|---|---|---|
| 0 | float | Humidité pondérale (%) | 85.5 |
| 1 | float | Latitude (WGS84) | 41.6367 |
| 2 | float | Longitude (WGS84) | -0.9023 |
85.5,41.6367,-0.9023,2024-05-21,10:30
78.2,41.6377,-0.8990,2024-05-21,10:45
92.1,41.6356,-0.9016,2024-05-21,11:00
- Nombre de points : Minimum 10-15 pour une régression fiable
- Distribution spatiale : Couvrir toute la zone d'intérêt
- Synchronisation temporelle : Mesures ± 3 jours de l'image satellite
- Profondeur : Cohérente (ex: 0-10cm pour toutes les mesures)
- Cliquer sur "S'inscrire"
- Renseigner email et mot de passe
- Validation automatique
- Aller dans "Nouvelle Zone"
- Dessiner le polygone sur la carte (Leaflet Draw)
- Nommer et décrire la zone
- Sauvegarder
- Accéder à "Mes Zones"
- Cliquer sur la zone créée
- "Uploader fichier CSV"
- Sélectionner votre fichier de mesures
- Sélectionner les dates d'images (start/end)
- Cliquer sur "Lancer les calculs"
- Le système :
- Récupère les images Sentinel
- Calcule tous les indices
- Effectue les régressions
- Sélectionne le meilleur modèle (MSE minimum)
- Carte d'humidité générée automatiquement
- Légende : du rouge (sec) au vert (humide)
- Statistiques : coefficient, intercept, MSE
- "Détection des changements"
- Sélectionner 2 périodes à comparer
- Visualisation côte-à-côte ou différentielle
agritech-wise/
│
├── app.py # Application Flask principale
├── my_functions.py # Régression et traitement GEE
├── detection_functions.py # Détection et visualisation
├── firebase_config.py # Configuration Firebase (non versionné)
│
├── templates/ # Templates HTML
│ ├── home.html
│ ├── mes_zones.html
│ ├── nouvelle_zone.html
│ ├── centre_de_apprentissage.html
│ └── detection_des_changements.html
│
├── static/ # Assets statiques
│ ├── css/
│ ├── js/
│ └── images/
│
├── requirements.txt # Dépendances Python
├── .gitignore # Fichiers à ignorer
├── README.md # Ce fichier
# detection_functions.py
def get_clear_sentinel1_image(start_date, end_date, roi):
sentinel1 = ee.ImageCollection("COPERNICUS/S1_GRD") \
.filter(ee.Filter.eq('instrumentMode', 'IW')) # Mode Interferometric Wide
.filter(ee.Filter.eq('orbitProperties_pass', 'DESCENDING')) # Orbite descendante
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VV'))
.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH'))# my_functions.py
def get_clear_sentinel2_image(start_date, end_date, roi, max_cloud_percentage=10):
sentinel2 = ee.ImageCollection("COPERNICUS/S2") \
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', max_cloud_percentage))# detection_functions.py
viz_params = {
'min': 1.8, # Humidité minimale (g/g)
'max': 2.8, # Humidité maximale (g/g)
'palette': [
'#d73027', # Rouge - Très sec
'#f46d43',
'#fdae61',
'#fee08b',
'#ffffbf', # Jaune - Moyen
'#d9ef8b',
'#a6d96a',
'#66bd63',
'#1a9850' # Vert foncé - Très humide
]
}# Tester Earth Engine
python -c "import ee; ee.Initialize(); print('EE OK')"
# Tester Firebase
python -c "import firebase_admin; print('Firebase OK')"# Dans app.py
@app.route('/test_function', methods=['GET'])
def test_function():
# Utilise des données de test pour valider le pipeline- Code Python PEP 8 compliant
- Docstrings pour toutes les fonctions
- Tests unitaires si possible
- Documentation des nouvelles fonctionnalités
- Support Sentinel-3 (résolution thermique)
- Modèles ML avancés (Random Forest, XGBoost)
- API REST pour intégration externe
- Application mobile (Flutter)
- Alertes automatiques (email/SMS)
- Export des données (GeoTIFF, Shapefile)
- Résolution : 10m (Sentinel-1/2) - non adapté aux petites parcelles (<1ha)
- Revisit : 5-6 jours (combiné) - pas de suivi temps réel
- Météo : Sentinel-2 limité par la couverture nuageuse
- Profondeur : Surface seulement (0-5cm pour radar)
-
La régression linéaire suppose une relation linéaire indice-humidité
-
La calibration est spécifique à chaque zone (texture sol, type culture)
-
Validation recommandée avec mesures indépendantes
-
Google Earth Engine : Pour l'accès gratuit aux données satellites
-
ESA Copernicus : Programme Sentinel-1 et Sentinel-2
-
Firebase : Infrastructure cloud robuste
-
Communauté open-source : Flask, scikit-learn, Leaflet
Ce dépôt ne contient AUCUN credential sensible.
Pour exécuter l'application, vous devez :
- Créer vos propres comptes Firebase et Google Earth Engine
- Configurer vos propres credentials selon la documentation d'installation
- Ne JAMAIS committer de fichiers contenant des clés API ou secrets
Ce projet est fourni à titre éducatif et de démonstration.
agritech-wise-default-rtdb.firebaseio.com/
│
└── users/ # Collection racine des utilisateurs
│
├── {userId}/ # Nœud unique par utilisateur (Firebase UID)
│ │
│ ├── email: string # Email de l'utilisateur
│ ├── name: string # Nom d'affichage
│ ├── phone: string # Numéro de téléphone
│ │
│ └── zones/ # Collection des zones agricoles
│ │
│ ├── {zoneId}/ # Zone unique (UUID v4)
│ │ │
│ │ ├── name: string # Nom de la zone
│ │ ├── description: string # Description détaillée
│ │ ├── coordinates: string # Polygone GeoJSON stringifié
│ │ │
│ │ ├── mesures_sur_terrain: boolean # Statut upload fichier CSV
│ │ ├── file_url: string # URL Firebase Storage du CSV
│ │ ├── prette_a_detecter: boolean # Zone calibrée et prête
│ │ ├── programmer_rendez_vous: boolean # Rendez-vous terrain programmé
│ │ │
│ │ └── paramettre_de_regression/ # Résultats de l'apprentissage
│ │ │
│ │ ├── optimal_index: string # Indice sélectionné (RVI, NDVI, etc.)
│ │ ├── coefficient: float # Coefficient de régression (a)
│ │ ├── intercept: float # Ordonnée à l'origine (b)
│ │ ├── mse: float # Erreur quadratique moyenne
│ │ │
│ │ ├── INDEX_values: array<float> # Valeurs de l'indice spectral
│ │ │ ├── 0: 2.5122194205786568
│ │ │ ├── 1: 2.3456789012345678
│ │ │ └── ...
│ │ │
│ │ └── humidity_values: array<float> # Humidité mesurée (g/g)
│ │ ├── 0: 1.95
│ │ ├── 1: 2.12
│ │ └── ...
│ │
│ ├── {zoneId2}/ # Autre zone
│ └── ...
│
├── {userId2}/ # Autre utilisateur
└── ...
| Champ | Type | Description | Exemple |
|---|---|---|---|
userId |
String (UID) | Identifiant unique Firebase Auth | omNKKc8Q8WOecpAExmi271x9lCr2 |
email |
String | Adresse email de connexion | [email protected] |
name |
String | Nom d'affichage de l'utilisateur | test10Q |
phone |
String | Numéro de téléphone de contact | 123423 |
Créé lors de : Inscription utilisateur (/signup)
Modifié par : Route /update_contact_info
| Champ | Type | Description | Exemple |
|---|---|---|---|
zoneId |
String (UUID) | Identifiant unique de la zone | 23dc505c-71ca-4ae9-94aa-2d666325544a |
name |
String | Nom personnalisé de la zone | test1 |
description |
String | Informations additionnelles | Informations |
coordinates |
String (JSON) | Polygone délimitant la zone Format: Array de [lon, lat] |
[[-0.896, 35.582], [-0.902, 35.581], ...] |
Créé lors de : Définition de zone (/save-zone)
Modifié par : Route /edit-zone/{zoneId}
| Champ | Type | Description | États possibles |
|---|---|---|---|
mesures_sur_terrain |
Boolean | Fichier CSV uploadé | true / false / absent |
programmer_rendez_vous |
Boolean | Rendez-vous terrain planifié | true / false / absent |
prette_a_detecter |
Boolean | Calibration terminée, détection possible | true / false / absent |
file_url |
String | URL publique du CSV dans Firebase Storage | https://storage.googleapis.com/agritech-wise.appspot.com/terrain1.csv |
Workflow typique :
1. mesures_sur_terrain: false → Upload CSV
2. mesures_sur_terrain: true → Lancer régression
3. prette_a_detecter: true → Détection activée
| Champ | Type | Description | Exemple |
|---|---|---|---|
optimal_index |
String | Indice spectral avec meilleur R² | RVI, NDVI_S, MNDWI_S, MSI_S, NDWI_S |
coefficient |
Float | Pente de la droite de régression (a) | 0.7542 |
intercept |
Float | Ordonnée à l'origine (b) | 1.823 |
mse |
Float | Mean Squared Error (qualité du modèle) | 0.0234 |
Équation de prédiction :
Humidité_prédite = coefficient × Indice_spectral + intercept
| Champ | Type | Description |
|---|---|---|
INDEX_values |
Array<Float> | Valeurs de l'indice spectral extrait des pixels satellites |
humidity_values |
Array<Float> | Humidité mesurée sur terrain (après transformation) |
Transformation appliquée :
# my_functions.py ligne 18
humidity_transformed = (humidity_measured * 0.01) + 1.8
# Exemple: 85.5% → 2.655 g/gUtilisé pour :
- Validation du modèle (graphiques de régression)
- Analyse de la qualité de prédiction
- Amélioration itérative du modèle
graph LR
A[POST /signup] --> B[Créer user dans Firebase Auth]
B --> C[Créer nœud /users/userId]
C --> D[Stocker email, name, phone]
E[POST /update_contact_info] --> F[Mettre à jour /users/userId]
Code correspondant :
# app.py ligne 107
def signup():
user = auth.create_user_with_email_and_password(email, password)
create_user(user_id, email) # → firebase_config.pygraph LR
A[POST /save-zone] --> B[Générer UUID zoneId]
B --> C[Créer /users/userId/zones/zoneId]
C --> D[Stocker coordinates, name, description]
D --> E[Initialiser statuts = false]
Structure initiale :
{
"name": "Parcelle Nord",
"description": "Culture de blé",
"coordinates": "[[-0.896, 35.582], ...]",
"mesures_sur_terrain": false,
"prette_a_detecter": false
}graph TD
A[POST /upload_file/userId/zoneId] --> B[Upload CSV vers Firebase Storage]
B --> C[Générer URL publique]
C --> D[Mettre à jour zone:]
D --> E[mesures_sur_terrain = true]
D --> F[file_url = URL_public]
Code correspondant :
# app.py ligne 296
def upload_file(user_id, zone_id):
blob = bucket.blob(filename)
blob.upload_from_file(file)
file_url = blob.public_url
zone_ref.update({'mesures_sur_terrain': True, 'file_url': file_url})graph TD
A[GET /run_regression] --> B[Télécharger CSV depuis file_url]
B --> C[Récupérer images Sentinel-1/2]
C --> D[Calculer indices spectraux]
D --> E[Régression linéaire]
E --> F[Sélectionner meilleur indice MSE minimum]
F --> G[Créer /paramettre_de_regression]
G --> H[prette_a_detecter = true]
Données créées :
{
"paramettre_de_regression": {
"optimal_index": "RVI",
"coefficient": 0.7542,
"intercept": 1.823,
"mse": 0.0234,
"INDEX_values": [2.512, 2.345, 2.678, ...],
"humidity_values": [1.95, 2.12, 2.08, ...]
},
"prette_a_detecter": true
}Code correspondant :
# app.py ligne 409
def run_regression():
log_data, coefficient, intercept, mse, INDEX_values, humidity_values = \
get_para_and_apply_regression(polygone, file_path, start_date, end_date)
regression_data = {
'optimal_index': log_data,
'coefficient': coefficient,
'intercept': intercept,
'mse': mse,
'INDEX_values': INDEX_values_list,
'humidity_values': humidity_values_list
}
regression_ref.set(regression_data)graph TD
A[POST /detect] --> B[Vérifier prette_a_detecter = true]
B --> C[Récupérer paramettre_de_regression]
C --> D[Charger image satellite date demandée]
D --> E[Calculer indice optimal_index]
E --> F[Appliquer formule: H = coefficient × I + intercept]
F --> G[Générer carte d'humidité]
G --> H[Retourner tile_url pour Leaflet]
Pas de modification de la DB - Lecture seule des paramètres.
| Valeur | Signification | Actions possibles |
|---|---|---|
false / absent |
Pas de données terrain | Upload CSV requis |
true |
CSV uploadé et stocké | Lancer régression |
Utilisé dans :
- Interface pour afficher/masquer bouton "Lancer calculs"
- Validation avant régression
| Valeur | Signification | Actions possibles |
|---|---|---|
false / absent |
Pas de calibration | Compléter workflow |
true |
Régression effectuée, modèle validé | Détection activée |
Utilisé dans :
- Route
/check_prette_a_detecter/{zone_id}(ligne 205) - Activer/désactiver page "Détection des changements"
| Valeur | Signification | Workflow |
|---|---|---|
false / absent |
Pas de rendez-vous | Planification disponible |
true |
Rendez-vous programmé | En attente intervention terrain |
Utilisé dans :
- Gestion des demandes administrateur
- Suivi des zones en attente de mesures
{
"rules": {
"users": {
"$userId": {
".read": "$userId === auth.uid",
".write": "$userId === auth.uid",
"zones": {
"$zoneId": {
".validate": "newData.hasChildren(['name', 'coordinates'])"
}
}
}
}
}
}Principes :
- Chaque utilisateur accède uniquement à ses données
- Validation de la structure minimale des zones
- Admin peut avoir accès lecture seule (à configurer selon besoins)
{
"users": {
"omNKKc8Q8WOecpAExmi271x9lCr2": {
"email": "[email protected]",
"name": "test10Q",
"phone": "123423",
"zones": {
"23dc505c-71ca-4ae9-94aa-2d666325544a": {
"name": "Parcelle Expérimentale 1",
"description": "Culture de blé irrigué - Campagne 2024",
"coordinates": "[[-0.8960151672908979, 35.582239292725276], [-0.902280807549687, 35.581750659855274], [-0.8985042572567182, 35.57295475838894], [-0.8867454529354292, 35.57532835076638]]",
"mesures_sur_terrain": true,
"file_url": "https://storage.googleapis.com/agritech-wise.appspot.com/terrain1.csv",
"prette_a_detecter": true,
"programmer_rendez_vous": false,
"paramettre_de_regression": {
"optimal_index": "RVI",
"coefficient": 0.7542,
"intercept": 1.823,
"mse": 0.0234,
"INDEX_values": [
2.5122194205786568, 2.3456789012345678, 2.6789012345678901,
2.4567890123456789, 2.567890123456789
],
"humidity_values": [1.95, 2.12, 2.08, 2.15, 2.03]
}
}
}
}
}
}# app.py ligne 182
user_id = session['user']['localId']
zones = get_user_zones(user_id) # → firebase_config.py# firebase_config.py
def get_user_zones(user_id):
zones_ref = db.reference(f'users/{user_id}/zones')
return zones_ref.get()# app.py ligne 197
zone_ref = db.reference(f'users/{user_id}/zones/{zone_id}')
zone_data = zone_ref.get()
prette_a_detecter = zone_data.get('prette_a_detecter', False)# app.py ligne 428
regression_ref = user_ref.child(f'zones/{zone_id}/paramettre_de_regression')
regression_ref.set(regression_data)
zone_ref.update({'prette_a_detecter': True})| Composant | Taille approximative |
|---|---|
| Métadonnées de base | 500 bytes |
| Coordinates (polygone 10 points) | 300 bytes |
| INDEX_values (50 mesures) | 400 bytes |
| humidity_values (50 mesures) | 400 bytes |
| Total par zone | ~1.6 KB |
Capacité Firebase :
- Plan gratuit : 1 GB stockage
- → ~625,000 zones possibles (limite théorique)
-
namenon vide -
coordinatescontient au moins 3 points -
mesures_sur_terrain = true -
file_urlaccessible et CSV valide -
paramettre_de_regressionexiste -
optimal_indexdans la liste autorisée -
mse < 0.1(seuil de qualité) -
prette_a_detecter = true