Commit 03ea8e75 authored by Commandre Benjamin's avatar Commandre Benjamin
Browse files

Optimisation + correction du calcul des coordonnees

parent 1d32fff1
......@@ -6,20 +6,24 @@ import math, subprocess
import urllib.request
import zipfile
import requests
import datetime
import configparser
from urllib.parse import urlencode
from osgeo import gdal, ogr, osr
import numpy as np
from uuid import uuid4
from collections import defaultdict, UserDict
import app.Satellites as Satellites
import app.Outils as Outils
import app.Constantes as Constantes
id_tuile = re.compile("T[0-9]+[A-Z]+")
date_tuile = re.compile("[SENTINEL.+?_]([0-9]*?)-")
def str2bool(v):
return v.lower() in (["false"])
class Archive():
"""
Classe pour lister, télécharger les archives via site Theia selon un shapefile représentant la zone d'étude.
......@@ -52,82 +56,70 @@ class Archive():
:type seuil: Entier
"""
def __init__(self, capteur, bandes, niveau, emprise, zone_etude, sortie, annee_debut, annee_fin, seuil):
# def __init__(self, capteur, bandes, niveau, emprise, zone_etude, sortie, annee_debut, annee_fin, seuil):
def __init__(self, config):
"""
Créé une instance de la classe 'Archive'.
"""
self._capteur = capteur
self.niveau = niveau
if bandes == "RGB" :
self.extent_img = Satellites.SATELLITE[self._capteur][niveau][:-1]
else :
self.extent_img = Satellites.SATELLITE[self._capteur][niveau]
if self.niveau == "LEVEL2A" :
self.nuage = Satellites.SATELLITE[self._capteur]["NUAGE"]
else :
self.nuage = None
self.liste_annees = []
for i in range(annee_debut, annee_fin) :
self.liste_annees.append(i)
self.liste_annees.append(annee_fin)
self.emprise = emprise
self.zone_etude = zone_etude
self.dossier_sortie = sortie
self.seuil_nuage = seuil
self.liste_archive = []
self.logger = Outils.Log("log", "Archive")
self.serveur = Satellites.SATELLITE[self._capteur]["serveur"]
self.resto = Satellites.SATELLITE[self._capteur]["resto"]
self.token_type = Satellites.SATELLITE[self._capteur]["token_type"]
Outils.get_variable(self, config)
self.liste_archive = []
self.url = ''
def utm_to_latlng(self, zone, easting, northing):
"""
Méthode pour convertir la projection UTM en coordonnées géographique
def vector_projection_with_epsg(self, chemin_vecteur, output_path, epsg=32631):
driver = ogr.GetDriverByName('ESRI Shapefile')
:param zone: Zone en projection UTM.
:type zone: Entier
projection_reference = osr.SpatialReference()
projection_reference.ImportFromEPSG(epsg)
:param easting: Coordonnées UTM en x.
:type easting: Flottant
shapefile = driver.Open(chemin_vecteur)
layer = shapefile.GetLayer()
projection_vector = layer.GetSpatialRef()
:param northing: Coordonnées UTM en y.
:type northing: Flottant
transformation = osr.CoordinateTransformation(projection_vector, projection_reference)
:param northernHemisphere: Vrai si la zone se situe dans l'hémisphère Nord, faux sinon.
:type northernHemisphere: Booléen
vecteur_reproj = output_path
:returns: Tuple d'entiers représentant la longitude et la latitude.
"""
if os.path.exists(vecteur_reproj):
driver.DeleteDataSource(vecteur_reproj)
systeme_utm = osr.SpatialReference()
outDataSet = driver.CreateDataSource(vecteur_reproj)
outLayer = outDataSet.CreateLayer(layer.GetName(), projection_reference, geom_type=layer.GetLayerDefn().GetGeomType())
# Initialise le système de coordonnées géographique vers Lat/Long
systeme_utm.SetWellKnownGeogCS("WGS84")
systeme_utm.SetUTM(zone, northing > 0)
# add fields
inLayerDefn = layer.GetLayerDefn()
for i in range(0, inLayerDefn.GetFieldCount()):
fieldDefn = inLayerDefn.GetFieldDefn(i)
outLayer.CreateField(fieldDefn)
# Duplique UNIQUEMENT le système de coordonnées géographique
systeme_lat_long = systeme_utm.CloneGeogCS()
outLayerDefn = outLayer.GetLayerDefn()
# Creation de méthode de conversion
# (<de>, <vers>)
convertion_utm = osr.CoordinateTransformation(systeme_utm, systeme_lat_long)
inFeature = layer.GetNextFeature()
# Renvoie lon, lat, altitude
longitude, latitude, altitude = convertion_utm.TransformPoint(easting, northing, 0)
while inFeature:
# get the input geometry
geom = inFeature.GetGeometryRef()
# reproject the geometry
geom.Transform(transformation)
# create a new feature
outFeature = ogr.Feature(outLayerDefn)
# set the geometry and attribute
outFeature.SetGeometry(geom)
for i in range(0, outLayerDefn.GetFieldCount()):
outFeature.SetField(outLayerDefn.GetFieldDefn(i).GetNameRef(), inFeature.GetField(i))
# add the feature to the shapefile
outLayer.CreateFeature(outFeature)
# dereference the features and get the next input feature
outFeature = None
inFeature = layer.GetNextFeature()
return (longitude, latitude)
# Save and close the shapefiles
inDataSet = None
outDataSet = None
def coord_box_dd(self):
"""
......@@ -144,8 +136,8 @@ class Archive():
if os.path.exists(utm_outfile):
os.remove(utm_outfile)
process_tocall = ['ogr2ogr', '-f', 'Esri Shapefile', '-t_srs', 'EPSG:32631', utm_outfile, self.emprise]
subprocess.call(process_tocall)
# wgs84 : 4326
self.vector_projection_with_epsg(self.emprise, utm_outfile, epsg=4326)
# To get shapefile extent
# Avec import ogr
......@@ -164,15 +156,17 @@ class Archive():
## Close source data
data_source.Destroy()
# Coordinates min in decimal degrees
LL_min = self.utm_to_latlng(31, extent_[0], extent_[2])
area_coord_corner = str(extent_[2]) + ',' + str(extent_[0]) + ',' + str(extent_[3]) + ',' + str(extent_[1])
return "{},{},{},{}".format(extent_[2], extent_[0], extent_[3], extent_[1])
def listing_by_tile(self):
# Coordinates max in decimal degrees
LL_max = self.utm_to_latlng(31, extent_[1], extent_[3])
for tuile in self.liste_tuiles :
area_coord_corner = str(LL_min[0]) + ',' + str(LL_min[1]) + ',' + str(LL_max[0]) + ',' + str(LL_max[1])
self.requete['location'] = tuile
return area_coord_corner
print(urlencode(self.requete))
def listing(self):
"""
......@@ -181,15 +175,33 @@ class Archive():
nbImage = 0
if hasattr(self, 'liste_tuiles'):
self.listing_by_tile()
# print(urlencode(self.requete))
# print(self.coord_box_dd())
self.requete['box'] = self.coord_box_dd()
# sys.exit(1)
self.liste_annees = ["2019"]
self.niveau = 'LEVEL2A'
# Boucle sur les annees
self.logger.info("Images availables")
# self.logger.info("Images availables")
for annee in self.liste_annees:
self.logger.info("=============== {0} ===============".format(annee))
self.url = "{0}/{1}/api/collections/{2}/search.json?lang=fr&processingLevel={3}&_pretty=true&q={4}&box={5}&maxRecord=500".format(self.serveur, self.resto, self._capteur, self.niveau, annee, self.coord_box_dd())
if self.emprise :
# self.url = "{0}/{1}/api/collections/{2}/search.json?lang=fr&processingLevel={3}&_pretty=true&q={4}&box={5}&maxRecord=500".format(self.serveur, self.resto, self.capteur, self.niveau, annee, self.coord_box_dd())
self.url = "{0}/{1}/api/collections/{2}/search.json?{3}".format(self.serveur, self.resto, self.capteur, urlencode(self.requete))
else :
self.url = "{0}/{1}/api/collections/{2}/search.json?lang=fr&processingLevel={3}&_pretty=true&q={4}&location={5}&maxRecord=500".format(self.serveur, self.resto, self.capteur, self.niveau, annee, self.liste_tuiles[0])
# S'il existe une autre image, suivante = vrai, faux sinon
suivante = True
print(self.url)
# Tant qu'il existe une image à traiter
while suivante:
......@@ -212,7 +224,7 @@ class Archive():
link_archive = data_Dict['features'][d]['properties']['services']['download']['url'].replace("\\", "")
url_archive = link_archive.replace(self.resto, "rocket/#")
archive_download = url_archive.replace("/download", "") # Url de l'archive à télécharger
out_archive = "{0}/{1}.tgz".format(self.dossier_sortie, name_archive)# Nom de sortie de l'archive
out_archive = "{0}/{1}.zip".format(self.dossier_sortie, name_archive) # Nom de sortie de l'archive
if "SENTINEL2X" not in out_archive or self.niveau == "LEVEL3A":
self.liste_archive.append([archive_download, out_archive, feature_id])
......@@ -239,7 +251,7 @@ class Archive():
self.logger.info("{0} image(s) correspondent aux critères.".format(len(self.liste_archive)))
def download_auto(self, identifiant, mdp, proxy="", extraction=True, groupe="Date"):
def download_auto(self):
"""
Méthode pour télécharger les archives sur le site Theia.
......@@ -256,17 +268,18 @@ class Archive():
# Url pour obtenir l'authentification
url = "{0}/services/authenticate/".format(self.serveur)
# Dictionnaire contenant les informations du proxy s'il existe
proxyDict = {
"http" : "{0}".format(proxy), \
"https" : "{0}".format(proxy), \
"ftp" : "{0}".format(proxy) \
"http" : "{0}".format(self.proxy), \
"https" : "{0}".format(self.proxy), \
"ftp" : "{0}".format(self.proxy) \
}
# Dictionnaire contenant les informations d'authentification
payload = {
'ident' : "{0}".format(identifiant),\
'pass' : "{0}".format(mdp)
'ident' : "{0}".format(self.id),\
'pass' : "{0}".format(self.mdp)
}
# Requête pour obtenir le jeton d'identification
......@@ -331,7 +344,6 @@ class Archive():
else :
sauvegarde[str(date)] = {}
sauvegarde[str(date)][nom_image] = "False"
sauvegarde[str(date)]["NDVI"] = "False"
if date in dico_date.keys() :
dico_date[date].append(l)
......@@ -352,17 +364,18 @@ class Archive():
self.logger.info("Requête pour l'archive : {0}".format(img[1].split("/")[-1]))
# Url de l'archive
url = "{0}/{1}/collections/{2}/{3}/download/?issuerId=theia".format(self.serveur, self.resto, self._capteur, img[2])
url = "{0}/{1}/collections/{2}/{3}/download/?issuerId=theia".format(self.serveur, self.resto, self.capteur, img[2])
self.logger.debug("url : {}".format(url))
# Requête pour récupérer l'archive
reponse = requests.get(url, headers=head, proxies=proxyDict)
if extraction :
if self.extraction :
# Ajout de l'archive à la liste
liste_content.append(reponse.content)
del reponse
else :
if groupe == "Tuile" :
if self.groupe == "Tuile" :
dossier_archive = "{0}/{1}/Archive".format(self.dossier_sortie, id_tuile.search(img[1]).group(0))
else:
dossier_archive = "{0}/{1}/Archive".format(self.dossier_sortie, cle[:4])
......@@ -371,10 +384,11 @@ class Archive():
if not os.path.exists(dossier_archive):
os.makedirs(dossier_archive)
print("{0}/{1}".format(dossier_archive, img[1].split("/")[-1]))
with open("{0}/{1}".format(dossier_archive, img[1].split("/")[-1]), "wb") as fichier:
fichier.write(reponse.content)
if extraction :
if self.extraction :
# Traitement des images (fusion, découpage selon la zone d'étude ...)
self.traitement_images(cle, liste_content)
del liste_content
......@@ -499,6 +513,7 @@ class Archive():
# Pour chaque archive
for idx, content in enumerate(liste_content) :
# Lecture de l'archive
tzip = zipfile.ZipFile(io.BytesIO(content))
......@@ -518,6 +533,8 @@ class Archive():
mmap_name = "/vsimem/"+uuid4().hex
liste_mem.append(mmap_name)
gdal.FileFromMemBuffer(mmap_name, tzip.read(img))
print(mmap_name)
sys.exit(1)
liste_bandes.append(gdal.Open(mmap_name))
# On fusionne les différentes bandes en une seule image
......
#!/bin/env python3
# -*- coding: utf-8 -*-
import sys
import argparse
import logging
from logging.handlers import RotatingFileHandler
......@@ -9,6 +10,10 @@ from contextlib import contextmanager
import inspect
from osgeo import gdal
import app.Constantes as Constantes
import configparser
import ast
from datetime import datetime
import app.Satellites as Satellites
class Fusion(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
......@@ -119,3 +124,123 @@ def clip(image, cut, form="Mem", dst="", type_sortie=gdal.GDT_Float32, nodata="N
cutlineDSName=cut, outputType=type_sortie , format=form, dstNodata=nodata)
return gdal.Warp(dst, image, options=option_clip)
def checkDate(date):
d = date.split('-')
try:
if not date :
annee = datetime.now().year
if datetime.now().month < 10 :
mois = "0{}".format(datetime.now().month)
else :
mois = datetime.now().month
if datetime.now().day < 10 :
jour = "0{}".format(datetime.now().day)
else :
jour = datetime.now().day
elif len(d) == 1 :
annee = d[0]
mois = "01"
jour = "01"
elif len(d) == 2 :
annee = d[0]
mois = d[1]
jour = "01"
elif len(d) == 3 :
annee = d[0]
mois = d[1]
jour = d[2]
datetime(int(annee), int(mois), int(jour))
return "{annee}-{mois}-{jour}".format(annee=annee, mois=mois, jour=jour)
except ValueError:
print("Format invalide")
sys.exit(1)
except IndexError:
print("La date doit être au format 'AAAA-MM-JJ', 'AAAA-MM' ou 'AAAA'")
sys.exit(1)
def get_variable(self, config):
"""
Récupération des variables dans le fichier de configuration
"""
configfile = configparser.ConfigParser()
configfile.read(config)
self.requete = dict()
self.requete["lang"] = "fr"
self.requete["_pretty"] = "true"
self.requete["maxRecord"] = 500
# Capteur utilisé
self.capteur = configfile["satellite"]["capteur"]
self.bandes = configfile["satellite"]["bandes"]
self.requete["processingLevel"] = configfile["satellite"]["processingLevel"]
# Dossier contenant les résultats
self.dossier_sortie = configfile["sortie"]["chemin"]
try:
if str2bool(configfile["sortie"]["extraction"]):
self.extraction = False
else :
self.extraction = True
except :
self.extraction = True
self.groupe = configfile["sortie"]["groupe"]
# Date de début et de fin de la recherche
try:
# self.date_debut = configfile["donnees"]["date_debut"]
self.requete["startDate"] = checkDate(configfile["donnees"]["date_debut"])
except Exception as e:
raise "L'année de départ est requise."
self.requete["completionDate"] = checkDate(configfile["donnees"]["date_fin"])
self.seuil_nuage = float(configfile["donnees"]["seuil_nuage"])/100.0 if configfile["donnees"]["seuil_nuage"] else 0.0
# Emprise et zone de l'étude
self.emprise = configfile["donnees"]["chemin_emprise"]
if self.emprise :
self.zone_etude = configfile["donnees"]["chemin_zone_etude"]
if not self.zone_etude :
self.zone_etude = self.emprise
else :
try:
self.liste_tuiles = list(ast.literal_eval(configfile["donnees"]["liste_tuiles"]))
except Exception as e:
raise "Aucune emprise ni aucune tuile n'a été renseigné."
self.nombre_image = configfile["donnees"]["nombre_image"]
# Identifiant, mot de passe et proxy pour le téléchargement des images Théia
self.id = configfile["theia"]["identifiant"]
self.mdp = configfile["theia"]["mdp"]
self.proxy = configfile["theia"]["proxy"]
if self.bandes == "RGB" :
self.extent_img = Satellites.SATELLITE[self.capteur][self.requete["processingLevel"]][:-1]
else :
self.extent_img = Satellites.SATELLITE[self.capteur][self.requete["processingLevel"]]
if self.requete["processingLevel"] == "LEVEL2A" :
self.nuage = Satellites.SATELLITE[self.capteur]["NUAGE"]
else :
self.nuage = None
self.serveur = Satellites.SATELLITE[self.capteur]["serveur"]
self.resto = Satellites.SATELLITE[self.capteur]["resto"]
self.token_type = Satellites.SATELLITE[self.capteur]["token_type"]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import configparser
import datetime
import glob
from osgeo import gdal
import otbApplication as otb
from app import Archive
def str2bool(v):
return v.lower() in ("true")
class Processing(object):
def __init__(self):
pass
def i_download(self):
"""
Méthode pour télécharger les images sur le site Theia Land.
"""
if not self.annee_fin :
self.annee_fin = datetime.datetime.now().year
self.check_download = Archive.Archive(self.capteur, self.bandes, self.niveau, self.emprise, self.zone_etude,\
self.resultats, self.annee_debut, int(self.annee_fin), self.seuil_nuage)
self.check_download.listing()
self.check_download.download_auto(self.id, self.mdp, self.proxy, extraction=self.extraction, groupe=self.groupe)
self.liste_dossier = dict()
for annee in os.listdir(self.resultats):
with open("{}/{}/dates.txt".format(self.resultats, annee), "w") as fichier :
for image in sorted(glob.glob("{}/{}/Images/*".format(self.resultats, annee))) :
fichier.write("{}\n".format(os.path.basename(image)[:-4]))
self.liste_dossier[annee] = sorted([x for x in glob.glob("{}/{}/Images/*".format(self.resultats, annee)) if x.endswith(".tif")])
def otbPhenologie(self):
"""
Appel le module OTB : 'SigmoFitting' sur l'image composée de l'empilement des NDVI
"""
otb_Phenologie = otb.Registry.CreateApplication("SigmoFitting")
otb_Phenologie.SetParameterString("mode", "metrics")
for annee in self.liste_dossier :
otb_Phenologie.SetParameterString("in", "{}/{}/NDVI/stack_ndvi.tif".format(self.resultats, annee))
otb_Phenologie.SetParameterString("dates", "{}/{}/dates.txt".format(self.resultats, annee))
otb_Phenologie.SetParameterString("out", "{}/{}/metrics.tif".format(self.resultats, annee))
otb_Phenologie.ExecuteAndWriteOutput()
def calcul_ndvi(self):
"""
Méthode effectuant le calcul du NDVI via le module OTB : 'RadiometricIndices'
"""
options_vrt = gdal.ParseCommandLine('-resolution highest -separate')
otb_NDVI = otb.Registry.CreateApplication("RadiometricIndices")
otb_NDVI.SetParameterInt("channels.blue", 1)
otb_NDVI.SetParameterInt("channels.green", 2)
otb_NDVI.SetParameterInt("channels.red", 3)
otb_NDVI.SetParameterInt("channels.nir", 4)
otb_NDVI.SetParameterStringList("list", ["Vegetation:NDVI"])
sauvegarde = configparser.ConfigParser()
for annee in self.liste_dossier :
sauvegarde.read("{}/{}/sauvegarde.ini".format(self.resultats, annee))
dossier_NDVI = "{}/{}/NDVI".format(self.resultats, annee)
if not os.path.exists(dossier_NDVI) :
os.makedirs(dossier_NDVI)
for img in self.liste_dossier[annee] :
chemin_ndvi = "{}/ndvi_{}".format(dossier_NDVI, os.path.basename(img))
if not str2bool(sauvegarde[os.path.basename(img)[:-4]]["NDVI"]):
otb_NDVI.SetParameterString("in", img)
otb_NDVI.SetParameterString("out", chemin_ndvi)
otb_NDVI.ExecuteAndWriteOutput()
sauvegarde[os.path.basename(img)[:-4]]["NDVI"] = "True"
liste_ndvi = sorted([x for x in glob.glob("{}/*".format(dossier_NDVI)) if x.endswith(".tif") and "stack" not in x])
vrt = gdal.BuildVRT("", liste_ndvi, options=options_vrt)
gdal.Translate("{}/{}/NDVI/stack_ndvi.tif".format(self.resultats, annee), vrt)
with open("{}/{}/sauvegarde.ini".format(self.resultats, annee), 'w') as configfile:
sauvegarde.write(configfile)
def i_images_processing(self):
"""
Calul le ndvi, fusionnne en une seule image puis lance le module OTBPhenology
"""
self.calcul_ndvi()
self.otbPhenologie()
[donnees]
# chemin/vers/shapefile/emprise
chemin_emprise =
# chemin/vers/shapefile/zone_etude
# Si vide, zone d'étude = emprise
chemin_zone_etude =
# Année à partir de laquelle les images ont été prises
annee_debut =
# Année limite d'acquisition des images
# Si vide, l'année de fin correspond à l'année actuelle
annee_fin =
# Pourcentage maximal d'ennuagement
seuil_nuage = 5.0
[sortie]
# chemin/dossier/resultats
chemin =
# Manière de regrouper les images/archives
# Possibilités :- Date (Regroupe les images selon leurs date d'acquisition)
# - Tuile (Regroupe les images en fonction de la tuile sentinel2 auquelle elles correspondent )
groupe = Date
# Vrai si extraction des images des archives, faux si téléchagement de celles-ci
extraction = True
[theia]
# Identifiant Theia-land
identifiant =
# Mot de passe Theia-land
mdp =
# Proxy de connection si nécessaire
proxy =
[satellite]
# Type du satellite source
# Possibilités :- SENTINEL2
# - LANDSAT8
capteur = SENTINEL2