#!/usr/bin/python3
# -*- coding: utf-8 -*-
########################################################################
#
# This file is part of python module <pyspc>.
# Copyright (C) 2013-2021 R. Marty
# (renaud.marty@developpement-durable.gouv.fr)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see COPYING.txt).
# If not, see <http://www.gnu.org/licenses/>.
#
########################################################################
"""
Données d'observation et de prévision - Météo-France - OPEN DATA
"""
import os.path
import numpy as np
import pandas as pnd
from pyspc.convention.meteofrance import MDG_PREFIX
import pyspc.core.exception as _exception
[docs]
class MF_OpenData():
"""
Lecteur des données de METEO.DATA.GOUV.FR.
Attributes
----------
filename : str
Fichier de données
prefix : str
Préfixe du fichier de données
timestep : datetime.timedelta
Pas de temps des données
dept : str
Département des stations de mesure
period : str
Période des données
varname : str, None
Grandeurs (seulement si prefix=='Q')
compressed : bool
Format compressé
"""
[docs]
def __init__(self, filename=None):
"""
Instanciation du lecteur du fichier METEO.DATA.GOUV.FR.
Parameters
----------
filename : str
Fichier de données
Examples
--------
>>> from pyspc.data.meteofrance import MF_OpenData
>>> f = 'Q_07_latest-2023-2024_RR-T-Vent.csv.gz'
>>> data = MF_OpenData(filename=f)
>>> data
*******************************************
*********** MF - OPEN DATA ****************
*******************************************
* NOM FICHIER = Q_07_latest-2023-2024_RR-T-Vent.csv.gz
* PREFIXE = Q
* PAS DE TEMPS = 1 day, 0:00:00
* DEPARTEMENT = 07
* PERIOD = latest-2023-2024
* GRANDEURS = RR-T-Vent
* FORMAT COMPRESSE = True
*************************************
"""
self.filename = filename
info = self.split_basename(self.filename)
self.prefix = info[0]
self.dept = info[1]
self.period = info[2]
self.timestep = info[3]
self.varname = info[4]
self.compressed = info[5]
def __str__(self):
"""Afficher les méta-données de l'instance."""
text = """
*******************************************
*********** MF - OPEN DATA ****************
*******************************************
* NOM FICHIER = {filename}
* PREFIXE = {prefix}
* PAS DE TEMPS = {timestep}
* DEPARTEMENT = {dept}
* PERIOD = {period}
* GRANDEURS = {varname}
* FORMAT COMPRESSE = {compressed}
*************************************
"""
return text.format(**vars(self))
[docs]
def read(self, codes=None):
"""
Lecture du fichier de données METEO.DATA.GOUV.FR.
Parameters
----------
codes : list
Identifiants des postes météo.
Information uniquement utilisée lors de la lecture en mode
dégradée, mode imposé par une mémoire insuffisante.
Returns
-------
pandas.DataFrame
Tableau des données
Examples
--------
>>> from pyspc.data.meteofrance import MF_OpenData
>>> reader = MF_OpenData(filename='data/data/mf/open_data/'
... 'MN_43_previous-2020-2022.csv.gz')
>>> content = reader.read()
>>> content
NUM_POSTE NOM_USUEL [...] ALTI AAAAMMJJHHMN RR QRR
0 43091005 LES ESTABLES_SAPC [...] 1350 202105101100 0.800 9
1 43091005 LES ESTABLES_SAPC [...] 1350 202105101106 0.400 9
2 43091005 LES ESTABLES_SAPC [...] 1350 202105101112 0.600 9
3 43091005 LES ESTABLES_SAPC [...] 1350 202105101118 1.400 9
4 43091005 LES ESTABLES_SAPC [...] 1350 202105101124 1.400 9
5 43091005 LES ESTABLES_SAPC [...] 1350 202105101130 1.200 9
6 43091005 LES ESTABLES_SAPC [...] 1350 202105101136 0.600 9
7 43091005 LES ESTABLES_SAPC [...] 1350 202105101142 0.700 9
8 43091005 LES ESTABLES_SAPC [...] 1350 202105101148 1.000 9
9 43091005 LES ESTABLES_SAPC [...] 1350 202105101154 1.200 9
10 43091005 LES ESTABLES_SAPC [...] 1350 202105101200 2.200 9
11 43091005 LES ESTABLES_SAPC [...] 1350 202105101206 1.400 9
12 43091005 LES ESTABLES_SAPC [...] 1350 202105101212 0.800 9
13 43091005 LES ESTABLES_SAPC [...] 1350 202105101218 0.800 9
14 43091005 LES ESTABLES_SAPC [...] 1350 202105101224 0.800 9
15 43091005 LES ESTABLES_SAPC [...] 1350 202105101230 1.100 9
16 43091005 LES ESTABLES_SAPC [...] 1350 202105101236 1.000 9
17 43091005 LES ESTABLES_SAPC [...] 1350 202105101242 0.800 9
18 43091005 LES ESTABLES_SAPC [...] 1350 202105101248 1.000 9
19 43091005 LES ESTABLES_SAPC [...] 1350 202105101254 1.200 9
20 43130002 MAZET-VOLAMONT [...] 1130 202105101100 0.800 9
21 43130002 MAZET-VOLAMONT [...] 1130 202105101106 0.600 9
22 43130002 MAZET-VOLAMONT [...] 1130 202105101112 0.600 9
23 43130002 MAZET-VOLAMONT [...] 1130 202105101118 1.000 9
24 43130002 MAZET-VOLAMONT [...] 1130 202105101124 0.800 9
25 43130002 MAZET-VOLAMONT [...] 1130 202105101130 1.000 9
26 43130002 MAZET-VOLAMONT [...] 1130 202105101136 1.400 9
27 43130002 MAZET-VOLAMONT [...] 1130 202105101142 1.000 9
28 43130002 MAZET-VOLAMONT [...] 1130 202105101148 1.200 9
29 43130002 MAZET-VOLAMONT [...] 1130 202105101154 1.000 9
30 43130002 MAZET-VOLAMONT [...] 1130 202105101200 1.000 9
31 43130002 MAZET-VOLAMONT [...] 1130 202105101206 0.800 9
32 43130002 MAZET-VOLAMONT [...] 1130 202105101212 1.700 9
33 43130002 MAZET-VOLAMONT [...] 1130 202105101218 1.200 9
34 43130002 MAZET-VOLAMONT [...] 1130 202105101224 1.400 9
35 43130002 MAZET-VOLAMONT [...] 1130 202105101230 1.000 9
36 43130002 MAZET-VOLAMONT [...] 1130 202105101236 0.800 9
37 43130002 MAZET-VOLAMONT [...] 1130 202105101242 1.400 9
38 43130002 MAZET-VOLAMONT [...] 1130 202105101248 1.200 9
39 43130002 MAZET-VOLAMONT [...] 1130 202105101254 1.400 9
"""
converters = {'NUM_POSTE': lambda x: str(x).strip()}
usecols = []
if self.prefix == 'MN':
converters['AAAAMMJJHHMN'] = str
usecols = ['RR']
elif self.prefix == 'H':
converters['AAAAMMJJHH'] = str
# usecols = ['RR1', ' T']
usecols = ['RR1']
elif self.prefix == 'Q':
converters['AAAAMMJJ'] = str
usecols = ['RR', 'TNTXM']
usecols.extend(converters.keys())
# MODES DE LECTURE DANS ORDRE DE PREFERENCE
funcs = {
1: pnd.read_csv,
2: pnd.read_csv,
3: self._read_serial,
4: self._read_serial,
}
kwargs = {
1: {'sep': ';', 'header': 0, 'converters': converters},
2: {'sep': ';', 'header': 0, 'converters': converters,
'usecols': usecols},
3: {'sep': ';', 'header': 0, 'converters': converters,
'usecols': usecols, 'codes': None},
4: {'sep': ';', 'header': 0, 'converters': converters,
'usecols': usecols, 'codes': codes},
}
for mode in funcs.keys():
func = funcs[mode]
kw = kwargs[mode]
try:
df = func(self.filename, **kw)
except (pnd.errors.ParserError, MemoryError) as me:
if mode == 1:
_exception.Warning(
None,
"pandas ne peut lire le fichier en 1 seule passe. "
"Seules les principales colonnes sont conservées: "
f"{usecols}")
continue
if mode == 2:
_exception.Warning(
None,
"pandas ne peut lire le fichier malgré la réduction "
"des colonnes lues. La lecture en série est réalisée.")
continue
if mode == 3 and not isinstance(codes, list):
raise MemoryError(
"La lecture en série ne peut lire le fichier en "
"entier. Veuillez renseigner une liste de codes."
) from me
if mode == 3:
_exception.Warning(
None,
"La lecture en série ne peut lire le fichier en "
"entier. La liste de codes est appliquée.")
continue
if mode == 4:
raise MemoryError(
"Aucun mode de lecture, parmi les 4 codés, ne permet "
"de lire ce fichier trop volumineux pour pyspc"
) from me
else:
break
return df
def _read_serial(self, filename, sep=None, header=None,
converters=None, usecols=None, codes=None):
"""Lecteur en série des fichiers MF."""
_exception.raise_valueerror(
not os.path.exists(filename), f"Fichier manquant '{filename}'")
if filename.endswith('.gz'):
import gzip
my_open = gzip.open
else:
my_open = open
k = -1
records = []
hindex = []
with my_open(filename, 'rt') as txt:
for line in txt:
info = line.strip().split(sep)
k += 1
if k == header:
hinfo = info
hindex = [hinfo.index(h) for h in usecols]
continue
ridx = hindex.index(hinfo.index('NUM_POSTE'))
r = self._info2record(info, hinfo, hindex, converters)
if (not isinstance(codes, list)) or (
isinstance(codes, list) and r[ridx] in codes):
records.append(r)
df = pnd.DataFrame.from_records(records, columns=usecols)
df = df[[hinfo[h] for h in sorted(hindex)]]
return df
def _info2record(self, info, hinfo, hindex, converters):
"""Convertir."""
record = []
for idx in hindex:
try:
val = converters.get(hinfo[idx], float)(info[idx])
except ValueError:
val = np.nan
record.append(val)
return tuple(record)
[docs]
@staticmethod
def split_basename(filename=None):
"""
Extraire les informations depuis le nom du fichier.
Parameters
----------
filename : str
Fichier de données METEO.DATA.GOUV.FR.
Returns
-------
prefix : str
Préfixe du fichier de données
dept : str
Département des stations de mesure
period : str
Période des données
timestep : datetime.timedelta
Pas de temps des données
varname : str, None
Grandeurs (seulement si prefix=='Q')
compressed : bool
Format compressé
Examples
--------
>>> from pyspc.data.meteofrance import MF_OpenData
>>> f = 'Q_07_latest-2023-2024_RR-T-Vent.csv.gz'
>>> ['prefix', 'timestep', 'dept', 'period', 'varname',
... 'compressed'] = MF_OpenData.split_basename(filename=f)
>>> prefix
Q
>>> timestep
1 day, 0:00:00
>>> dept
07
>>> period
latest-2023-2024
>>> varname
RR-T-Vent
>>> compressed
True
"""
if filename is None:
return None, None, None, None, None, None
basename = os.path.splitext(os.path.basename(filename))[0]
compressed = False
if filename.endswith('.gz'):
basename = os.path.splitext(basename)[0]
compressed = True
try:
info = basename.split('_')
prefix = info.pop(0)
dept = info.pop(0)
period = info.pop(0)
timestep = MDG_PREFIX[prefix]
except (IndexError, ValueError) as ive:
raise ValueError("Le nom de fichier ne respecte pas le "
"nommage de METEO.DATA.GOUV.FR.") from ive
except KeyError as ke:
raise ValueError(f"Le préfixe '{prefix}' est inconnu") from ke
try:
varname = info.pop(0)
except IndexError:
varname = None
return prefix, dept, period, timestep, varname, compressed