Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
saildrone-push / common / saildrone.py
Size: Mime:
#!/usr/bin/env python
# Saildrone API implementation
#
# A very simple implementation of the Saildrone API (generate tokens, push vessels, retrieve timeseries data)
#
# TODO: Check params of get_data and push_vessel if within access scope
# TODO: More sophisticated response and error handling
# TODO: Test time series data and token generation
#
# Copyright (c) 2019 GEOMAR Helmholtz Centre for Ocean Research Kiel
#
# Author:      Patrick Leibold
# Last change: 2019-10-20

import os
import requests
import datetime
import logging


class SaildroneAPIError(Exception):
    pass


class SaildroneAPI:
    """Very simple and straightforward implementation of Saildrone API"""

    # Constants
    STATUS_OK = "ok"

    DATA_SET_VEHICLE = "vehicle"
    DATA_SET_OCEANOGRAPHIC = "oceanographic"
    DATA_SET_BIOGEOCHEMICAL = "biogeochemical"
    DATA_SET_ATMOSPHERIC = "atmospheric"
    DATA_SET_SPY_GLASS = "spyglass"

    # Endpoint URLs
    __base_url = "https://developer-mission.saildrone.com"
    __health_endpoint = "/health"
    __auth_endpoint = "/v1/auth"
    __access_endpoint = __auth_endpoint + "/access"
    __ais_endpoint = "/v1/ais"
    __timeseries_endpoint = "/v1/timeseries"

    # API specific properties
    __token = os.environ.get('SAILDRONE_API_TOKEN')
    __headers = {}
    __drones = dict()

    def __init__(self):
        """Class constructor"""
        self.__headers = {
            "content-type": "application/json",
            "accept": "application/json",
            "authorization": self.__token
        }

    def try_connect(self):
        """Tries to connect the Saildrone API by checking the health endpoint and retrieves access scope"""
        # Try to connect to API
        if self.__check_health():
            # Inflate drones data structure
            scope = self.__get_access_scope()
            for drone in scope:
                self.__drones[drone["drone_id"]] = drone
            logging.info("Connected to Saildrone API, {0} drones found.".format(len(self.__drones.keys())))
        else:
            logging.error("Could not connect to Saildrone API! Service might be down.")
            raise SaildroneAPIError("Could not connect to Saildrone API! Service might be down.")

    def __check_health(self):
        """Checks the health of the Saildrone API service"""
        url = self.__base_url + self.__health_endpoint
        response = self.__get(url)
        if "success" in response:
            return bool(response["success"])
        return False

    def __get_access_scope(self):
        """Retrieves the access scope of the underlying access token"""
        url = self.__base_url + self.__access_endpoint
        params = {
            "token": self.__token
        }
        response = self.__get(url, params)
        if "success" in response:
            if bool(response["success"]):
                return response["data"]["access"]
        return []

    def push_vessel(self, mmsi, timestamp, latitude, longitude, speed_over_ground=0, course_over_ground=0, heading=0):
        """Pushes platforms (unique mmsi/id!) to the Saildrone API AIS endpoint"""
        url = self.__base_url + self.__ais_endpoint
        params = {
            "mmsi": int(mmsi),
            "timestamp": int(timestamp),
            "latitude": float(latitude),
            "longitude": float(longitude),
            "sog": float(speed_over_ground),
            "cog": float(course_over_ground),
            "hdg": float(heading)
        }
        response = self.__post(url, params)
        if "success" in response:
            return bool(response["success"])
        return False

    def get_data(self, drone_id, data_set, start_date, end_date, interval=5, limit=500, order_by="desc", offset=0):
        """Retrieves timeseries data from specified drone limited by various parameters"""
        url = self.__base_url + self.__timeseries_endpoint + "/" + str(drone_id)
        params = {
            "data_set": data_set,
            "start_date": start_date,
            "end_date": end_date,
            "interval": int(interval),
            "limit": int(limit),
            "order_by": order_by,
            "offset": int(offset)
        }
        return self.__get(url, params)

    def __post(self, url, params={}):
        """Generic POST request which works on JSON data"""
        try:
            req = requests.post(url, json=params, headers=self.__headers)
            if not req.ok:
                logging.error(f'Got status {req.status_code} for POST:{url}: {req.text}')
                raise SaildroneAPIError(f'Got status {req.status_code} for POST:{url}: {req.text}')
            return req.json()
        except IOError:
            logging.error("Error while performing POST request to Saildrone API")
            return {}

    def __get(self, url, params={}):
        """Generic GET request"""
        try:
            req = requests.get(url, params, headers=self.__headers)
            if not req.ok:
                logging.error(f'Got status {req.status_code} for GET:{url}: {req.text}')
                raise SaildroneAPIError(f'Got status {req.status_code} for GET:{url}: {req.text}')

            return req.json()
        except IOError:
            logging.error("Error while performing GET request to Saildrone API")
            return {}