Commit b41ffeaa authored by Commandre Benjamin's avatar Commandre Benjamin
Browse files

Fichier d'installation + doc

parent ccc53b79
......@@ -18,65 +18,73 @@ import app.Outils as Outils
class Archive():
"""
Class to list, download and unpack Theia image archive because of a shapefile (box).
This shapefile get extent of the area.
:param captor: Name of the satellite (ex: Landsat or SpotWorldHeritage ...).
Name used to the url on website Theia Land
:type captor: str
:param list_year: Processing's year (string for one year)
:type list_year: list of str
:param box: Path of the study area
:type box: str
:param folder: Path of the source folder
:type folder: str
:param repertory: Name of the archive's folder
:type repertory: str
Classe pour lister, télécharger les archives via site Theia selon un shapefile représentant la zone d'étude.
:param capteur: Nom du satellite utilisé (ex: Landsat, Sentinel2, ...).
:type capteur: Chaîne de caractères
:param niveau: Niveau de traitement voulu.
:type niveau: Chaîne de caractères
:param emprise: Chemin vers le shapefile représentant la zone d'étude.
:type emprise: Chaîne de caractères
:param sortie: Chemin du dossier de sortie.
:type sortie: Chaîne de caractères
:param annee_debut: Année à partir de laquelle on cherche les images.
:type annee_debut: Entier
:param annee_fin: Année jusqu'à laquelle on cherche les images.
:type annee_fin: Entier
"""
def __init__(self, captor, niveau, emprise, repertory, start_year, end_year):
def __init__(self, capteur, niveau, emprise, sortie, annee_debut, annee_fin):
"""
Create a new 'Archive' instance
Créé une instance de la classe 'Archive'.
"""
self._captor = captor
self._capteur = capteur
self.niveau = niveau
self._list_year = []
self.liste_annees = []
for i in range(int(start_year), end_year) :
self._list_year.append(i)
for i in range(annee_debut, annee_fin) :
self.liste_annees.append(i)
self._list_year.append(end_year)
self.liste_annees.append(annee_fin)
self._box = emprise
self._repertory = repertory
self.emprise = emprise
self.dossier_sortie = sortie
self.emprise = emprise
self.list_archive = []
self.liste_archive = []
self.logger = Outils.Log("log", "Archive")
self.server = Satellites.SATELLITE[self._captor]["server"]
self.resto = Satellites.SATELLITE[self._captor]["resto"]
self.token_type = Satellites.SATELLITE[self._captor]["token_type"]
self.serveur = Satellites.SATELLITE[self._capteur]["serveur"]
self.resto = Satellites.SATELLITE[self._capteur]["resto"]
self.token_type = Satellites.SATELLITE[self._capteur]["token_type"]
self.url = ''
def utm_to_latlng(self, zone, easting, northing, northernHemisphere=True):
"""
Function to convert UTM to geographic coordinates
Méthode pour convertir la projection UTM en coordonnées géographique
:param zone: Zone en projection UTM.
:type zone: Entier
:param easting: Coordonnées UTM en x.
:type easting: Flottant
@param zone: UTM zone
@type zone: int
@param easting: Coordinates UTM in x
@type easting: float
@param northing: Coordinates UTM in y
@type northing: float
@param northernHemisphere: North hemisphere or not
@type northernHemisphere: boolean
:param northing: Coordonnées UTM en y.
:type northing: Flottant
:returns: tuple -- integer on the **longitude** and **latitude**
:param northernHemisphere: Vrai si la zone se situe dans l'hémisphère Nord, faux sinon.
:type northernHemisphere: Booléen
:returns: Tuple d'entiers représentant la longitude et la latitude.
Source : http://www.ibm.com/developerworks/java/library/j-coordconvert/index.html
"""
......@@ -133,21 +141,20 @@ class Archive():
def coord_box_dd(self):
"""
Function to get area's coordinates of shapefile
:returns: str -- **area_coord_corner** : Area coordinates corner
Méthode pour obtenir les coordonnées de la zone à partir du shapefile.
--> Left bottom on x, Left bottom on y, Right top on x, Right top on y
:returns: Une chaîne de caractères représentant les coordonnées des coins situés \
en bas à gauche et en haut à droite.
"""
# Processus to convert the UTM shapefile
# in decimal degrees shapefile with ogr2ogr in command line
utm_outfile = "{0}/UTM_{1}".format(os.path.split(self._box)[0], os.path.split(self._box)[1])
utm_outfile = "{0}/UTM_{1}".format(os.path.split(self.emprise)[0], os.path.split(self.emprise)[1])
if os.path.exists(utm_outfile):
os.remove(utm_outfile)
process_tocall = ['ogr2ogr', '-f', 'Esri Shapefile', '-t_srs', 'EPSG:32631', utm_outfile, self._box]
process_tocall = ['ogr2ogr', '-f', 'Esri Shapefile', '-t_srs', 'EPSG:32631', utm_outfile, self.emprise]
subprocess.call(process_tocall)
# To get shapefile extent
......@@ -173,95 +180,110 @@ class Archive():
# Coordinates max in decimal degrees
LL_max = self.utm_to_latlng(31, extent_[1], extent_[3], northernHemisphere=True)
# AdressUrl = 'http://spirit.cnes.fr/resto/Landsat/?format=html&lang=fr&q=2013&box=' + str(LL_Min[0]) + ',' + str(LL_Min[1]) + ',' + str(LL_Max[0]) + ',' + str(LL_Max[1])
area_coord_corner = str(LL_min[0]) + ',' + str(LL_min[1]) + ',' + str(LL_max[0]) + ',' + str(LL_max[1])
return area_coord_corner
def listing(self):
"""
Function to list available archive on plateform Theia Land, and on the area
Méthode pour lister les images disponibles sur la plateforme 'Theia Land' correspondant à la zone.
"""
nbImage = 0
# Loop on the years
# Boucle sur les annees
self.logger.info("Images availables")
for year in self._list_year:
for annee in self.liste_annees:
self.logger.info("=============== {0} ===============".format(year))
self.url = "{0}/{1}/api/collections/{2}/search.json?lang=fr&processingLevel={3}&_pretty=true&q={4}&box={5}&maxRecord=500".format(self.server, self.resto, self._captor, self.niveau, year, self.coord_box_dd())
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())
# Initialisation variable for a next page
# There is a next page, next = 1
# There isn't next page, next = 0
next_ = 1
# S'il existe une autre image, suivante = vrai, faux sinon
suivante = True
# To know path to download images
while next_ == 1:
# 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 in the database
data = urllib.request.urlopen(req).read() # Read in the database
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) # Remove "null" because Python don't like
new_data = re.sub(b"false", b"False", new_data) # Remove "false" and replace by False (Python know False with a capital letter F)
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
# Transform the data in dictionary
# Conversion des données sous la forme d'un dictionnaire
data_Dict = defaultdict(list)
data_Dict = UserDict(eval(new_data))
# Select archives to download
# 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", "") # Path to download
out_archive = "{0}/{1}.tgz".format(self._repertory, name_archive)# Name after download
# if len(self.list_archive) == 0 : self.list_archive.append([archive_download, out_archive, feature_id])
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.list_archive.append([archive_download, out_archive, feature_id])
self.liste_archive.append([archive_download, out_archive, feature_id])
# Verify if there is another page (next)
# 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("\\", "")
next_ = 1
suivante = True
break
else :
next_ = 0
suivante = False
except Exception as e:
self.logger.error("Error connexion or error variable : {0}".format(e))
sys.exit(1)
if nbImage != len(self.list_archive) :
if not os.path.exists("{}/{}/Images".format(self._repertory, year)) :
os.makedirs("{}/{}/Images".format(self._repertory, year))
# 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.list_archive)
nbImage = len(self.liste_archive)
self.logger.info("{0} image(s) matched the criteria.".format(len(self.list_archive)))
self.logger.info("{0} image(s) correspondent aux critères.".format(len(self.liste_archive)))
def download_auto(self, user, password, proxy=""):
def download_auto(self, identifiant, mdp, proxy=""):
"""
Function to download images archive automatically on Theia land data center.
Méthode pour télécharger les archives sur le site Theia.
:param identifiant: Identifant pour le site Theia.
:type identifiant: Chaîne de caractères
:param mdp: Mot de passe pour le site Theia.
:type mdp: Chaîne de caractères
:param proxy: Information du proxy.
:type proxy: Chaîne de caractères
"""
url = "{0}/services/authenticate/".format(self.server)
# 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) \
}
# Dictionnaire contenant les informations d'authentification
payload = {
'ident' : "{0}".format(user),\
'pass' : "{0}".format(password)
'ident' : "{0}".format(identifiant),\
'pass' : "{0}".format(mdp)
}
# Requête pour obtenir le jeton d'identification
reponse = requests.post(url, data=payload, proxies=proxyDict)
# Récupération du jeton d'identification au format texte ou json
try:
if "text" in reponse.headers["Content-Type"] :
token = reponse.text
......@@ -277,18 +299,27 @@ class Archive():
regex = 'SENTINEL(.+?)_(.+?)-'
# Dictionnaire des images à traiter regrouper par date
dico_date = dict()
sauvegarde = configparser.ConfigParser()
for l in self.list_archive :
# Pour toutes les images disponible
for l in self.liste_archive :
# Récupération de la date quand l'image a été prise
date = re.search(regex, l[1]).group(2)
# Récupération du nom de l'image
nom_image = l[1].split("/")[-1][:-4]
sauvegarde.read("{}/{}/sauvegarde.ini".format(self._repertory, date[:4]))
# Lecture du fichier de sauvegarde
sauvegarde.read("{}/{}/sauvegarde.ini".format(self.dossier_sortie, date[:4]))
# Si la date existe dans le fichier de sauvegarde ...
if str(date) in sauvegarde.keys() :
# ... mais pas l'image, on la rajoute dans la liste des images à traiter.
if nom_image not in sauvegarde[str(date)].keys() :
sauvegarde[str(date)][nom_image] = "False"
......@@ -299,6 +330,7 @@ class Archive():
else :
dico_date[date] = [l]
# ... 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"
......@@ -308,6 +340,7 @@ class Archive():
else :
dico_date[date] = [l]
# Si la date n'existe pas dans le fichier de sauvegarde, on la rajoute ainsi que l'image dans la liste des images à traiter.
else :
sauvegarde[str(date)] = {}
sauvegarde[str(date)][nom_image] = "False"
......@@ -318,33 +351,57 @@ class Archive():
else :
dico_date[date] = [l]
with open("{}/{}/sauvegarde.ini".format(self._repertory, date[:4]), 'w') as configfile:
# Sauvegarde du fichier de sauvegarde mis à jour.
with open("{}/{}/sauvegarde.ini".format(self.dossier_sortie, date[:4]), 'w') as configfile:
sauvegarde.write(configfile)
# Pour toutes les dates à traiter
for cle in sorted(dico_date):
# Liste des archives
liste_content = []
# Pour toutes les images prisent à cette date
for idx, img in enumerate(dico_date[cle]):
self.logger.info("Requête pour l'archive : {0}".format(img[1].split("/")[-1]))
url = "{0}/{1}/collections/{2}/{3}/download/?issuerId=theia".format(self.server, self.resto, self._captor, img[2])
# Url de l'archive
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)
# Ajout de l'archive à la liste
liste_content.append(reponse.content)
print(type(reponse.content))
del reponse
self.pourc_cloud(cle, liste_content)
# Traitement des images (fusion, découpage selon la zone d'étude ...)
self.traitement_images(cle, liste_content)
del liste_content
sauvegarde.read("{}/{}/sauvegarde.ini".format(self._repertory, cle[:4]))
# Mis à jour du fichier de sauvegarde
# Lecture du fichier de sauvegarde
sauvegarde.read("{}/{}/sauvegarde.ini".format(self.dossier_sortie, cle[:4]))
# Pour toutes les images traitées à cette date
for img in dico_date[cle] :
# Image traitée passe à vrai dans le fichier de sauvegarde
sauvegarde[str(cle)][img[1].split("/")[-1][:-4]] = "True"
with open("{}/{}/sauvegarde.ini".format(self._repertory, cle[:4]), 'w') as configfile:
# Sauvegarde du fichier de sauvegarde mis à jour.
with open("{}/{}/sauvegarde.ini".format(self.dossier_sortie, cle[:4]), 'w') as configfile:
sauvegarde.write(configfile)
self.logger.info("All images have been downloaded !")
def calcul_aire(self, tuiles_image):
"""
Méthode pour fusionner un ensemble d'images et calculer l'aire de l'image obtenue.
:param tuiles_image: Liste des images à fusionner.
:type tuiles_image: Liste d'images
:returns: Un tuple contenant l'image fusionnée ainsi que son aire.
"""
option_warp = gdal.WarpOptions(format='Mem', resampleAlg="lanczos")
mosaic_image = gdal.Warp("", tuiles_image, options=option_warp)
......@@ -361,7 +418,15 @@ class Archive():
return mosaic_image, area
def ecriture_geotiff(self, dataset, chemin):
"""
Méthode pour enregistrer une image au format GTiff.
:param dataset: Données de l'image.
:type dataset: GDALDataset
:param chemin: Chemin de l'image de sortie.
:type chemin: Chaîne de caractères
"""
gdal.AllRegister()
driver = gdal.GetDriverByName('GTiff')
......@@ -377,38 +442,64 @@ class Archive():
outdata = None
def pourc_cloud(self, date, liste_content):
def traitement_images(self, date, liste_content):
"""
Pour une date donnée, extrait pour chaque archive correspondant à cette date
les images correspondant aux bandes rouge, verte, bleue et proche infrarouge,
les combines en une seule image, effectue la mosaïque des images,
coupe selon l'emprise de la zone d'étude puis, si l'aire de l'image est supérieure à 0 l'enregistre.
:param date: Date à laquelle ont été prises les images.
:type date: Entier
:param liste_content: Liste des archives contenant les images.
:type liste_content: Liste d'octets
"""
self.logger.info("Date : {0} -> {1} image(s)".format(date, len(liste_content)))
# Extension correspondant respectivement aux bandes rouge, verte, bleue et proche infrarouge
extent_img = ['_FRC_B2.tif', '_FRC_B3.tif', '_FRC_B4.tif', '_FRC_B8.tif']
tuiles_image = []
# Options Gdal pour la fusion des bandes
options_vrt = gdal.BuildVRTOptions(separate=True)
options_translate = gdal.TranslateOptions(format="Mem")
self.logger.info("Extraction des images")
# Pour chaque archive
for idx, content in enumerate(liste_content) :
# Lecture de l'archive
tzip = zipfile.ZipFile(io.BytesIO(content))
# Images list in the archive
# Liste des fichiers dans l'archive
zip_img = tzip.namelist()
# Liste contenant les images voulues
liste_bandes = []
liste_mem = []
# Pour tous les fichiers dans l'archive
for id_ext, extension in enumerate(extent_img) :
# Si il s'agit d'une bande voulue (R,G,B,PIR)
img = [f for f in zip_img if extension in f][0]
# On charge l'image en mémoire
mmap_name = "/vsimem/"+uuid4().hex
liste_mem.append(mmap_name)
gdal.FileFromMemBuffer(mmap_name, tzip.read(img))
liste_bandes.append(gdal.Open(mmap_name))
# On fusionne les différentes bandes en une seule image
# on découpe l'image selon l'emprise
# et on conserve l'image obtenue
vrt = gdal.BuildVRT("", liste_bandes, options=options_vrt)
tuiles_image.append(Outils.clip(gdal.Translate("", vrt, options=options_translate), self.emprise ))
# On libère la mémoire
for mmap_name in liste_mem :
gdal.Unlink(mmap_name)
......@@ -419,19 +510,21 @@ class Archive():
del liste_content
# On effectue une mosaïque des images qu'on découpe selon l'emprise,
# puis on calcule l'aire de l'image ainsi obtenue
self.logger.info("Calcul de l'aire")
image, aire = self.calcul_aire(tuiles_image)
del tuiles_image
# Si l'aire est positive on enregistre l'image
if aire > 0.0 :
self.logger.info("Sauvegarde des images")
dossier = "{0}/{1}/Images".format(self._repertory, date[:4])
dossier = "{0}/{1}/Images".format(self.dossier_sortie, date[:4])
self.logger.debug("Dossier image : {0}".format(dossier))
self.ecriture_geotiff(image, "{0}/{1}.tif".format(dossier,date))
del image
del image
\ No newline at end of file
......@@ -8,7 +8,7 @@ import signal
from contextlib import contextmanager
import inspect
from osgeo import gdal
import Constantes
import app.Constantes as Constantes
class Fusion(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
......@@ -98,6 +98,22 @@ def limitation_temporelle(secondes):
# pass
def clip(image, cut, form="Mem", dst=""):
"""
Découpe une image selon un shapefile donné. Si le type de l'image en sortie
n'est pas spécifié, l'image sera retournée en pointeur mémoire et ne sera pas sauvegardée.
:param image: Chemin vers l'image dans laquelle on veut découper l'emprise.
:type image: Chaîne de caractères
:param cut: Chemin vers le shapefile représentant l'emprise.
:type cut: Chaîne de caractères
:param form: Format de l'image en sortie.
:type form: Chaîne de caractères
:param dst: Chemin de l'image en sortie.
:type dst: Chaîne de caractères
"""
option_clip = gdal.WarpOptions(cropToCutline=True,\
cutlineDSName=cut, outputType=gdal.GDT_Float32 , format=form, dstNodata='NaN')
......
......@@ -27,7 +27,7 @@ class Processing(object):
if not self.annee_fin :
self.annee_fin = datetime.datetime.now().year
self.check_download = Archive.Archive(self.capteur, self.niveau, self.emprise, self.resultats, self.annee_debut, self.annee_fin)
self.check_download = Archive.Archive(self.capteur, self.niveau, self.emprise, self.resultats, self.annee_debut, int(self.annee_fin))
self.check_download.listing()
self.check_download.download_auto(self.id, self.mdp, self.proxy)
......@@ -41,6 +41,9 @@ class Processing(object):
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")
......@@ -94,7 +97,7 @@ class Processing(object):
def i_images_processing(self):
"""
Calul le ndvi, fusionnne en une seule image puis lance le module OTBPhenologie
Calul le ndvi, fusionnne en une seule image puis lance le module OTBPhenology
"""
self.calcul_ndvi()
self.otbPhenologie()
\ No newline at end of file
......@@ -9,7 +9,7 @@ SATELLITE = collections.defaultdict(dict)
########################## SENTINEL2 ###############################
SATELLITE["SENTINEL2"]["server"] = "https://theia.cnes.fr/atdistrib"
SATELLITE["SENTINEL2"]["serveur"] = "https://theia.cnes.fr/atdistrib"
SATELLITE["SENTINEL2"]["resto"] = "resto2"
SATELLITE["SENTINEL2"]["token_type"] = "text"
SATELLITE["SENTINEL2"]["R"] = 2
......@@ -17,7 +17,7 @@ SATELLITE["SENTINEL2"]["PIR"] = 3
########################## LANDSAT #################################
SATELLITE["Landsat"]["server"] = "https://theia-landsat.cnes.fr"
SATELLITE["Landsat"]["serveur"] = "https://theia-landsat.cnes.fr"
SATELLITE["Landsat"]["resto"] = "resto"
SATELLITE["Landsat"]["token_type"] = "json"
SATELLITE["Landsat"]["R"] = 3
......
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Sentinel-3A
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: a88ad48e93a6109d7a7da9e9f0fdea0f
tags: 645f666f9bcd5a90fca523b33c5a78b7
;; -*- coding: utf-8; mode: Lisp; -*-
;; style file for xindy
;; filename: LICRcyr2utf8.xdy