In [ ]:
import requests
import pandas as pd
pd.options.mode.chained_assignment = None  # default='warn'
import geopandas as gpd
from tqdm import tqdm
import shapely.geometry
In [ ]:
class telechargement_geo_spatial():
    """Méthode de télécharment des observations d'une observedProperties par intersection spatiale et filtre temporel

    Attributes:
        url_service (string): url du service sensorthing
        geom (geometry): geometry pour réaliser l'intersection
        obsP (string): observedProperty ciblée
        date_start (string): date de début d'observation
        date_end (string): date de fin d'observation
    """
    def __init__(self, url_service, geom,  obsP, date_start, date_end):
        self.url_service = url_service
        self.geom = geom
        self.obsP = obsP
        self.date_start = date_start
        self.date_end = date_end

        if self.geom.geom_type == 'MultiPolygon':
            print("MultipolygonE, conversion en polygone")
            self.geom =self.geom.geoms[0]

    def get_data(self,url_requete):
        """Télécharge les données d'une requête sensorthing avec itération dans les pages

        Args:
            url_requete (str): url à télécharger
        Returns:
            dataframe: dataframe (pandas) des données téléchargées
        """
        r=requests.get(url=url_requete)
        if r.status_code not in [200, 201, 202]:
            print(r.status_code)
            if r.status_code == 414:
                print("url trop grande = la géometrie en entrée est trop complexe \n \
                tentative d'intersection par la bounding box")

                self.geom=shapely.geometry.box(*self.geom.bounds, ccw=True)
                url_requete=self.inter_geo_temporelle()
                print(url_requete)
                r=requests.get(url=url_requete)

        data=r.json()
        df_all=pd.DataFrame(data['value'])
        while '@iot.nextLink' in data:
            r=requests.get(url=data['@iot.nextLink'])
            data=r.json()
            df_all=pd.concat([df_all,pd.DataFrame(data['value'])])
        return df_all

    def inter_geo_temporelle(self):
        """Façonne l'url de requête selon les différents arguments de la classe
        Returns:
            string: url avec les filtres
        """
        objet='Datastreams'
        rqt=f"$filter=geo.intersects(Thing/Locations/location, geography'{self.geom}') \
    and ObservedProperty/name eq'{self.obsP}'\
&$select=name,description,unitOfMeasurement,Observations,phenomenonTime\
&$expand=Thing/Locations($select=name,@iot.id,location)"
        url_rqt=f"{self.url_service}{objet}?{rqt}"
        print("Requête générée:\n")
        #print(url_rqt)
        return url_rqt

    def get_all(self,df):
        """Télécharge les observations des datastreams
        Args:
            df (dataframe): dataframe des datastreams avec une colonne contenant les lien vers les observations
        Returns:
            dataframe: dataframe des données téléchargées pour tous les datastreams avec les coordonnées géographiques de chaque observations
        """
        df_data=pd.DataFrame()
        print("\nTéléchargement en cours : \n")
        for idx,row in tqdm(df.iterrows(), total=df.shape[0]):
            #print(row['Observations@iot.navigationLink'])
            rqt=row['Observations@iot.navigationLink']
            rqt+=f"?$select=result,phenomenonTime&$filter=phenomenonTime ge {self.date_start} and phenomenonTime le {self.date_end}"
            df_inter=pd.DataFrame(self.get_data(rqt))
            df_inter['name']=row['name']
            df_inter['x']=row['Thing']['Locations'][0]['location']['geometry']['coordinates'][0]
            df_inter['y']=row['Thing']['Locations'][0]['location']['geometry']['coordinates'][1]
            df_data=pd.concat([df_data,df_inter])
            df_data=df_data.reset_index(drop=True)
        self.unite = row['unitOfMeasurement']['name']
        self.symbol = row['unitOfMeasurement']['symbol']
        return df_data

    def telechargement(self):
        """Fonction qui automatise la classe
        Returns:
            dataframe: dataframe des données téléchargées pour tous les datastreams avec les coordonnées géographiques de chaque observations
        """
        print(f"Recherche des Datastreams par l'ObservedProperty : {self.obsP} par intersection spatial sur la période : {self.date_start} {self.date_end}")
        url_rqt = self.inter_geo_temporelle()
        df_rqt = self.get_data(url_rqt)
        if len(df_rqt) == 0:
            print("\nIl n'y a pas d'objet dans la zone d'intersection, veuillez changer de zone")
            return 0
        else:
            print(f"\nNombre d'objet à téléchargerger :{len(df_rqt)}")
        #faire un retour si pas d'objet
        #refaire la requete si erreur 414 par bounding box
        df = self.get_all(df_rqt)
        df=df.rename(columns={'result':self.obsP})
        return df
In [ ]:
zone=gpd.read_file('/home/tloree/Téléchargements/bv2.json')
print(zone.crs)
zone=zone.to_crs('EPSG:4326')
print(zone.crs)
zone.plot()
epsg:3857
EPSG:4326
Out[ ]:
<Axes: >
In [ ]:
url_service='https://frost.geosas.fr/agri4cast/v1.0/'

geom=zone.geometry[0]
obsP="temperature"
date_start, date_end = '2016-06-01T00:00:00Z','2016-10-01T00:00:00Z'

instance = telechargement_geo_spatial(url_service, geom,  obsP, date_start, date_end)
df = instance.telechargement()
unite = instance.unite
symbol = instance.symbol

display(df)
MultipolygonE, conversion en polygone
Recherche des Datastreams par l'ObservedProperty : temperature par intersection spatial sur la période : 2016-06-01T00:00:00Z 2016-10-01T00:00:00Z
Requête générée:

414
url trop grande = la géometrie en entrée est trop complexe 
                 tentative d'intersection par la bounding box
Requête générée:

https://frost.geosas.fr/agri4cast/v1.0/Datastreams?$filter=geo.intersects(Thing/Locations/location, geography'POLYGON ((-3.306542346883707 48.12010680427483, -3.306542346883707 48.48722797229124, -4.137462844378464 48.48722797229124, -4.137462844378464 48.12010680427483, -3.306542346883707 48.12010680427483))')     and ObservedProperty/name eq'temperature'&$select=name,description,unitOfMeasurement,Observations,phenomenonTime&$expand=Thing/Locations($select=name,@iot.id,location)

Nombre d'objet à téléchargerger :3

Téléchargement en cours : 

100%|██████████| 3/3 [00:01<00:00,  2.96it/s]
temperature phenomenonTime name x y
0 290.991000 2016-08-29T02:00:00Z t_29263002 -4.098 48.228
1 289.429167 2016-08-30T02:00:00Z t_29263002 -4.098 48.228
2 289.786667 2016-08-31T02:00:00Z t_29263002 -4.098 48.228
3 289.357083 2016-09-01T02:00:00Z t_29263002 -4.098 48.228
4 287.677917 2016-09-02T02:00:00Z t_29263002 -4.098 48.228
... ... ... ... ... ...
729 287.465000 2016-09-26T02:00:00Z t_29278001 -3.730 48.170
730 289.824167 2016-09-27T02:00:00Z t_29278001 -3.730 48.170
731 287.527917 2016-09-28T02:00:00Z t_29278001 -3.730 48.170
732 287.055417 2016-09-29T02:00:00Z t_29278001 -3.730 48.170
733 285.899583 2016-09-30T02:00:00Z t_29278001 -3.730 48.170

734 rows × 5 columns

In [ ]:
for i in df['name'].unique():
    df_inter=df[df['name']==i]
    print(obsP,i)
    print(df_inter[obsP].describe())
temperature t_29263002
count    246.000000
mean     289.866627
std        2.256061
min      282.689706
25%      288.083229
50%      289.785417
75%      291.023379
max      297.372083
Name: temperature, dtype: float64
temperature t_29277001
count    244.000000
mean     289.529495
std        2.492328
min      285.966667
25%      287.517500
50%      289.348125
75%      290.884167
max      299.327083
Name: temperature, dtype: float64
temperature t_29278001
count    244.000000
mean     289.823108
std        2.351803
min      285.899583
25%      287.860417
50%      289.727083
75%      291.210417
max      298.786250
Name: temperature, dtype: float64
In [ ]:
#exemple de visulisation
import seaborn as sns
import matplotlib.pyplot as plt
In [ ]:
df['phenomenonTime']=pd.to_datetime(df['phenomenonTime'])
sns.lineplot(data=df, x='phenomenonTime',y=obsP,hue='name').set(title=f"ObservedProperties : {obsP}")
plt.xticks(rotation = 25)
plt.ylabel(f"{unite} ({symbol})")
Out[ ]:
Text(0, 0.5, 'Kelvin (K)')