diff --git a/app/Archive.py b/app/Archive.py index 98b0806c88ed61d5e887f0f518876f809a50e9ec..76f271e026eb4282fd5a65698eb9535c55210a4e 100644 --- a/app/Archive.py +++ b/app/Archive.py @@ -6,14 +6,15 @@ 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 @@ -52,82 +53,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 +133,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,82 +153,88 @@ 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): + + nbImage = 0 + + for tuile in self.liste_tuiles : + + self.requete['location'] = tuile - # Coordinates max in decimal degrees - LL_max = self.utm_to_latlng(31, extent_[1], extent_[3]) + self.url = "{0}/{1}/api/collections/{2}/search.json?{3}".format(self.serveur, self.resto, self.capteur, urlencode(self.requete)) - area_coord_corner = str(LL_min[0]) + ',' + str(LL_min[1]) + ',' + str(LL_max[0]) + ',' + str(LL_max[1]) + self.get_liste_images() - return area_coord_corner + def listing_by_coord(self): + + self.requete['box'] = self.coord_box_dd() + + self.url = "{0}/{1}/api/collections/{2}/search.json?{3}".format(self.serveur, self.resto, self.capteur, urlencode(self.requete)) + + self.get_liste_images() + + def get_liste_images(self): + + # S'il existe une autre image, suivante = vrai, faux sinon + suivante = True + + # Tant qu'il existe une image à traiter + while suivante: + + try : + request_headers = {"User-Agent": "Magic-browser"} + req = urllib.request.Request(self.url, headers = request_headers) # Connexion à la base de données + data = urllib.request.urlopen(req).read() # Lecture de la base de données + + new_data = re.sub(b"null", b"'null'", data) # Suppression de l'expression "null" pouvant causer une erreur Python + new_data = re.sub(b"false", b"False", new_data) # Renomme le booléen selon la syntaxe Python + + # Conversion des données sous la forme d'un dictionnaire + data_Dict = defaultdict(list) + data_Dict = UserDict(eval(new_data)) + + # Selection des archives à télécharger + for d in range(len(data_Dict['features'])): + name_archive = data_Dict['features'][d]['properties']['productIdentifier'] + feature_id = data_Dict["features"][d]['id'] + 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}.zip".format(self.dossier_sortie, name_archive) # Nom de sortie de l'archive + + if "SENTINEL2X" not in out_archive or self.requete["processingLevel"] == "LEVEL3A": + self.liste_archive.append([archive_download, out_archive, feature_id]) + + # Vérification si d'autres images sont disponibles + for link in data_Dict['properties']['links'] : + if link["title"] is 'suivant' : + self.url = link['href'].replace("\\", "") + suivante = True + break + else : + suivante = False + + except Exception as e: + self.logger.error("Error connexion or error variable : {0}".format(e)) + sys.exit(1) def listing(self): """ Méthode pour lister les images disponibles sur la plateforme 'Theia Land' correspondant à la zone. """ - nbImage = 0 - - # Boucle sur les annees - 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()) - # S'il existe une autre image, suivante = vrai, faux sinon - suivante = True - - # Tant qu'il existe une image à traiter - while suivante: - - try : - request_headers = {"User-Agent": "Magic-browser"} - req = urllib.request.Request(self.url, headers = request_headers) # Connexion à la base de données - data = urllib.request.urlopen(req).read() # Lecture de la base de données - - new_data = re.sub(b"null", b"'null'", data) # Suppression de l'expression "null" pouvant causer une erreur Python - new_data = re.sub(b"false", b"False", new_data) # Renomme le booléen selon la syntaxe Python - - # Conversion des données sous la forme d'un dictionnaire - data_Dict = defaultdict(list) - data_Dict = UserDict(eval(new_data)) - - # Selection des archives à télécharger - for d in range(len(data_Dict['features'])): - name_archive = data_Dict['features'][d]['properties']['productIdentifier'] - feature_id = data_Dict["features"][d]['id'] - 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 - - if "SENTINEL2X" not in out_archive or self.niveau == "LEVEL3A": - self.liste_archive.append([archive_download, out_archive, feature_id]) - - # Vérification si d'autres images sont disponibles - for link in data_Dict['properties']['links'] : - if link["title"] is 'suivant' : - self.url = link['href'].replace("\\", "") - suivante = True - break - else : - suivante = False - - except Exception as e: - self.logger.error("Error connexion or error variable : {0}".format(e)) - sys.exit(1) - - # Si aucune image n'est disponible pour une année, le dossier correspondant n'est pas créé - if nbImage != len(self.liste_archive) : - if not os.path.exists("{}/{}/Images".format(self.dossier_sortie, annee)) : - os.makedirs("{}/{}/Images".format(self.dossier_sortie, annee)) - - nbImage = len(self.liste_archive) + if hasattr(self, 'liste_tuiles'): + self.listing_by_tile() + else : + self.listing_by_coord() 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 +251,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 @@ -301,7 +297,7 @@ class Archive(): nom_image = l[1].split("/")[-1][:-4] # Lecture du fichier de sauvegarde - sauvegarde.read("{}/{}/sauvegarde.ini".format(self.dossier_sortie, date[:4])) + sauvegarde.read("{}/sauvegarde.ini".format(self.dossier_sortie)) # Si la date existe dans le fichier de sauvegarde ... if str(date) in sauvegarde.keys() : @@ -310,7 +306,6 @@ class Archive(): if nom_image not in sauvegarde[str(date)].keys() : sauvegarde[str(date)][nom_image] = "False" - sauvegarde[str(date)]["NDVI"] = "False" if date in dico_date.keys() : dico_date[date].append(l) @@ -320,8 +315,6 @@ class Archive(): # ... et que l'image exite mais n'a pas été traité, on la rajoute dans la liste des images à traiter. elif sauvegarde[str(date)][nom_image] == "False" : - sauvegarde[str(date)]["NDVI"] = "False" - if date in dico_date.keys() : dico_date[date].append(l) else : @@ -331,7 +324,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) @@ -339,7 +331,7 @@ class Archive(): dico_date[date] = [l] # Sauvegarde du fichier de sauvegarde mis à jour. - with open("{}/{}/sauvegarde.ini".format(self.dossier_sortie, date[:4]), 'w') as configfile: + with open("{}/sauvegarde.ini".format(self.dossier_sortie), 'w') as configfile: sauvegarde.write(configfile) # Pour toutes les dates à traiter @@ -352,36 +344,38 @@ 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 : - # Ajout de l'archive à la liste + if self.extraction : + # Ajout de l'archive à la liste liste_content.append(reponse.content) del reponse else : - if groupe == "Tuile" : - dossier_archive = "{0}/{1}/Archive".format(self.dossier_sortie, id_tuile.search(img[1]).group(0)) + if self.groupe == "Tuile" : + dossier_archive = "{0}/{1}".format(self.dossier_sortie, id_tuile.search(img[1]).group(0)) else: - dossier_archive = "{0}/{1}/Archive".format(self.dossier_sortie, cle[:4]) - + dossier_archive = "{0}/Archive/{1}".format(self.dossier_sortie, cle[:4]) 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 + # Mis à jour du fichier de sauvegarde # Lecture du fichier de sauvegarde - sauvegarde.read("{}/{}/sauvegarde.ini".format(self.dossier_sortie, cle[:4])) + sauvegarde.read("{}/sauvegarde.ini".format(self.dossier_sortie)) # Pour toutes les images traitées à cette date for img in dico_date[cle] : @@ -389,7 +383,7 @@ class Archive(): sauvegarde[str(cle)][img[1].split("/")[-1][:-4]] = "True" # Sauvegarde du fichier de sauvegarde mis à jour. - with open("{}/{}/sauvegarde.ini".format(self.dossier_sortie, cle[:4]), 'w') as configfile: + with open("{}/sauvegarde.ini".format(self.dossier_sortie), 'w') as configfile: sauvegarde.write(configfile) self.logger.info("All images have been downloaded !") @@ -487,7 +481,7 @@ class Archive(): """ - self.logger.info("Date : {0} -> {1} image(s)".format(date, len(liste_content))) + self.logger.info("Date : {0} ".format(date)) tuiles_image = [] tuiles_nuage = [] @@ -499,6 +493,7 @@ class Archive(): # Pour chaque archive for idx, content in enumerate(liste_content) : + # Lecture de l'archive tzip = zipfile.ZipFile(io.BytesIO(content)) @@ -518,7 +513,8 @@ class Archive(): mmap_name = "/vsimem/"+uuid4().hex liste_mem.append(mmap_name) gdal.FileFromMemBuffer(mmap_name, tzip.read(img)) - liste_bandes.append(gdal.Open(mmap_name)) + # liste_bandes.append(gdal.Open(mmap_name)) + liste_bandes.append(mmap_name) # On fusionne les différentes bandes en une seule image # on découpe l'image selon l'emprise @@ -545,7 +541,7 @@ class Archive(): gdal.FileFromMemBuffer(mmap_name, tzip.read(image_nuage)) liste_mem.append(mmap_name) - tuiles_nuage.append(Outils.clip(gdal.Open(mmap_name), self.zone_etude)) + tuiles_nuage.append(Outils.clip(mmap_name, self.zone_etude)) for mmap_name in liste_mem : gdal.Unlink(mmap_name) @@ -555,7 +551,7 @@ class Archive(): liste_bandes = None del tzip - liste_content[idx] = None + del liste_content[idx] del liste_content @@ -565,7 +561,10 @@ class Archive(): self.logger.info("Sauvegarde des images") - dossier = "{0}/{1}/Images".format(self.dossier_sortie, date[:4]) + dossier = "{0}/Images/{1}".format(self.dossier_sortie, date[:4]) + + if not os.path.exists(dossier): + os.makedirs(dossier) self.logger.debug("Dossier image : {0}".format(dossier)) diff --git a/app/Outils.py b/app/Outils.py index f78a986bd522feb96441f617c276fe3312f65b1d..fc2bf3dcd46a5b30fcd0d2ef5b49bf2b6c9f49ec 100644 --- a/app/Outils.py +++ b/app/Outils.py @@ -1,6 +1,7 @@ #!/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): @@ -77,25 +82,25 @@ class TimeoutException(Exception): pass @contextmanager def limitation_temporelle(secondes): - - def signal_handler(signum, frame): - raise TimeoutException - signal.signal(signal.SIGALRM, signal_handler) - signal.alarm(secondes) + def signal_handler(signum, frame): + raise TimeoutException - try: - yield - finally: - signal.alarm(0) + signal.signal(signal.SIGALRM, signal_handler) + signal.alarm(secondes) + + try: + yield + finally: + signal.alarm(0) # Utilisation : # # try: -# with limitation_temporelle(temps_en_seconde): -# appel_fonction() +# with limitation_temporelle(temps_en_seconde): +# appel_fonction() # except TimeoutException: -# pass +# pass def clip(image, cut, form="Mem", dst="", type_sortie=gdal.GDT_Float32, nodata="NaN"): """ @@ -118,4 +123,126 @@ def clip(image, cut, form="Mem", dst="", type_sortie=gdal.GDT_Float32, nodata="N option_clip = gdal.WarpOptions(cropToCutline=True,\ cutlineDSName=cut, outputType=type_sortie , format=form, dstNodata=nodata) - return gdal.Warp(dst, image, options=option_clip) \ No newline at end of file + return gdal.Warp(dst, image, options=option_clip) + +def str2bool(v): + return v.lower() in (["false"]) + +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"] diff --git a/app/Processing.py b/app/Processing.py deleted file mode 100644 index c9dd1db42912757bf500cdb097127691b4b414b7..0000000000000000000000000000000000000000 --- a/app/Processing.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/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() diff --git a/config.ini b/config.ini index 87b04152743b8ce3d21535e9f273cf74c7e3c5ac..35d1c38ff1366d87db7c40b7fc2cd56aa587363c 100644 --- a/config.ini +++ b/config.ini @@ -6,27 +6,38 @@ chemin_emprise = # Si vide, zone d'étude = emprise chemin_zone_etude = -# Année à partir de laquelle les images ont été prises -annee_debut = +# Liste des tuiles Sentinel2 à télécharger +# Si une emprise est spécifiée, celle-ci est privilégié +# Ex : ["T30ABC", "T31ABC", "T30DEF"] +liste_tuiles = -# Année limite d'acquisition des images -# Si vide, l'année de fin correspond à l'année actuelle -annee_fin = +# Nombre d'images à télécharger par tuile +# Si vide, télécharge toutes les images disponibles +nombre_image = + +# Date à partir de laquelle les images ont été prises +# La date doit être au format AAAA-MM-JJ, AAAA-MM ou AAAA +date_debut = + +# Date limite d'acquisition des images +# La date doit être au format AAAA-MM-JJ, AAAA-MM ou AAAA +# Si vide, la date de fin correspond au jour actuel +date_fin = # Pourcentage maximal d'ennuagement seuil_nuage = 5.0 [sortie] # chemin/dossier/resultats -chemin = +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 +groupe = Tuile # Vrai si extraction des images des archives, faux si téléchagement de celles-ci -extraction = True +extraction = False [theia] # Identifiant Theia-land @@ -47,7 +58,7 @@ capteur = SENTINEL2 # Niveau de traitement des images satellites # Possibilités :- LEVEL2A (Acquisition standard) # - LEVEL3A (Synthèse mensuelle) -processingLevel= LEVEL2A +processingLevel= LEVEL3A # Bandes d'intêrets # Possibilités :- RGB (Bandes Rouge, Verte, Bleue) diff --git a/install.sh b/install.sh deleted file mode 100755 index aed9b9feb111350c3355318cda268f6a8b3fab4f..0000000000000000000000000000000000000000 --- a/install.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/bash -# ========================================= -# Script d'installation -# ========================================= - -function erreur { - echo "Usage : $0 [--libs --maj --compil --all]" - echo "--libs : Installation des bibliothèques python" - echo "--maj : Téléchargement des fichiers sources pour OTB" - echo "--compil : Compilation d'OTB" - echo "--all : Téléchargement des sources et des bibliothèques python puis compilation d'OTB" - exit -} - -function confirme { - read -r -p "${1}Êtes-vous sûr de vouloir continuer ? [O/N] " response - if [[ $response == "o" || $response = "O" || $response == "y" || $response = "Y" ]]; then - echo -e "${ROUGE}\e[1mLauching generation in $prefix_dir\e[0m" - else - exit - fi -} - -set -e - -#Couleur 'Rouge' pour le terminal -ROUGE='\033[0;31m' - -# Dossier courant du script -CMD="$(readlink -e "${BASH_SOURCE[0]}")" -SH_DIR="$(dirname "$CMD")" -prefix_dir=$SH_DIR - -if [ ! -z $CXX ]; then - echo "Version du compilateur : $CXX" -else - CXX=`type -p g++` -fi - -CXXVersion=`${CXX} -dumpversion` -version_compilateur="5.0.0" - -if [ "$(printf '%s\n' "$version_compilateur" "$CXXVersion" | sort -V | head -n1)" = "$CXXVersion" ]; then - echo "Gcc version too old" - echo "Actual: ${CXXVersion}" - echo "Needed: ${version_compilateur}" - exit -fi - -# Erreur si manque ou mauvais argument -if [[ "$#" != "1" ]]; then - erreur -fi - -if [[ "$1" != "--libs" ]] && [[ "$1" != "--maj" ]] && [[ "$1" != "--compil" ]] && [[ "$1" != "--all" ]]; then - erreur -fi - -if [[ "$1" == "--libs" ]]; then - echo "Installation des bibliothèques python3" - sudo apt-get update - sudo apt-get install aptitude -y - sudo aptitude install python3-pip -y - sudo pip3 install -r "dépendances.txt" -fi - - -echo $prefix_dir/OTB - -if [[ "$1" != "--libs" ]]; then - - echo "Installation de la bibliothèque OTB dans : ${prefix_dir}" - confirme - - #---------------------------------------- - # Récupération des sources - if [[ "$1" == "--maj" ]] || [[ "$1" == "--all" ]]; then - # Clonage du repertoire OTB - if [ -d "OTB" ]; then - echo "Le répertoire OTB existe déjà ." - else - echo "Clonage OTB ..." - mkdir -p $prefix_dir/OTB - cd $prefix_dir/OTB - git clone https://gitlab.orfeo-toolbox.org/orfeotoolbox/otb.git - fi - fi - - #---------------------------------------- - # Compilation - if [[ "$1" != "--compil" ]] || [[ "$1" == "--all" ]] ; then - - sudo aptitude install make cmake-curses-gui build-essential libtool automake git libbz2-dev python-dev libboost-dev libboost-filesystem-dev libboost-serialization-dev libboost-system-dev zlib1g-dev libcurl4-gnutls-dev swig libgdal-dev - - - echo "Compilation d'OTB ..." - cd $prefix_dir/ - - mkdir -p build - mkdir -p build - cd build - - # Compilation d'OTB - cmake -DCMAKE_CXX_COMPILER=${CXX} -DCMAKE_CXX_FLAGS:STRING=-std=c++14 -DUSE_SYSTEM_BOOST=ON -DUSE_SYSTEM_CURL=ON -DUSE_SYSTEM_ZLIB=ON -DUSE_SYSTEM_GDAL=ON -DCMAKE_BUILD_TYPE=Release -DOTB_WRAP_PYTHON3:BOOL=ON -DGDAL_SB_EXTRA_OPTIONS:STRING="--with-python" -DCMAKE_INSTALL_PREFIX=$prefix_dir/OTB/install/ -DOTB_USE_QWT=OFF -DOTB_USE_GLEW=OFF -DOTB_USE_GLFW=OFF -DOTB_USE_GLUT=OFF -DOTB_USE_OPENGL=OFF -DOTB_USE_QT=OFF -DOTB_USE_QWT=OFF $prefix_dir/OTB/otb/SuperBuild/ - make --jobs=12 - - # Compilation des modules - cd $prefix_dir/OTB/build/OTB/build - - cmake -DCMAKE_CXX_COMPILER=${CXX} -DCMAKE_CXX_FLAGS:STRING=-std=c++14 -DModule_OTBPhenology:BOOL=ON -DModule_OTBTemporalGapFilling:BOOL=ON $prefix_dir/OTB/otb - make --jobs=12 - make install - fi - - echo "Installation terminée." - -fi diff --git a/main.py b/main.py index d69c70dfb28507e3ac56f5d7eb74596e8f9f7596..5de8db025ef2377d2371972355efdeeb527d1750 100644 --- a/main.py +++ b/main.py @@ -2,73 +2,21 @@ # -*- coding: utf-8 -*- import sys, time - -from osgeo import ogr - import configparser -from app.Processing import Processing -import app.Constantes as Constantes -import app.Outils as Outils - -def str2bool(v): - return v.lower() in (["false"]) - -class Telechargement(Processing): +import argparse - def __init__(self, parent=None): - super(Processing, self).__init__() - Processing.__init__(self) - - self.logger = Outils.Log("log", "Téléchargement") - - self.get_variable() - - def get_variable(self): - """ - Récupération des variables dans le fichier de configuration - """ - - configfile = configparser.ConfigParser() - configfile.read("config.ini") - - # Capteur utilisé - self.capteur = configfile["satellite"]["capteur"] - self.niveau = configfile["satellite"]["processingLevel"] - self.bandes = configfile["satellite"]["bandes"] - - # Dossier contenant les résultats - self.resultats = 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.annee_debut = int(configfile["donnees"]["annee_debut"]) - except Exception as e: - raise "L'année de départ est requise." +from app.Archive import Archive +import app.Constantes as Constantes - self.annee_fin = configfile["donnees"]["annee_fin"] - self.seuil_nuage = float(configfile["donnees"]["seuil_nuage"])/100.0 if configfile["donnees"]["seuil_nuage"] else 0.0 +from app.Outils import Log - # Emprise et zone de l'étude - self.emprise = configfile["donnees"]["chemin_emprise"] - self.zone_etude = configfile["donnees"]["chemin_zone_etude"] +class Telechargement(object): - if not self.zone_etude : - self.zone_etude = self.emprise + def __init__(self, config): + super(Telechargement, self).__init__() - # 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"] + self.logger = Log("log", "Téléchargement") + self.config = config def run(self): """ @@ -77,11 +25,9 @@ class Telechargement(Processing): # Début du processus debut = time.time() - # Recherche de nouvelles images non traitées et téléchargement de celles-ci le cas échéant - self.i_download() - - # Traitement des images - self.i_images_processing() + prog = Archive(self.config) + prog.listing() + prog.download_auto() # Fin du processus fin = time.time() @@ -92,5 +38,9 @@ class Telechargement(Processing): if __name__ == "__main__": - app = Telechargement() + parser = argparse.ArgumentParser() + parser.add_argument("-config", dest="config", help="Chemin du fichier de config", required=True) + args = parser.parse_args() + + app = Telechargement(args.config) sys.exit(app.run()) diff --git a/prepare_env.sh b/prepare_env.sh deleted file mode 100755 index 52d66be4f8e6bd77546010f3c5d94ec37ef4f72c..0000000000000000000000000000000000000000 --- a/prepare_env.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# Dossier courant du script. -CMD="$(readlink -e "${BASH_SOURCE[0]}")" -SH_DIR="$(dirname "$CMD")" -prefix_dir=$SH_DIR - - -export PATH=$PATH:$prefix_dir/OTB/install/bin -export PYTHONPATH=$prefix_dir/OTB/install/lib/otb/python3 -export ITK_AUTOLOAD_PATH=$prefix_dir/OTB/install/lib/otb/applications \ No newline at end of file