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 / data_handler.py
Size: Mime:
#!/usr/bin/env python
# GeoJSON data handler
#
# This class queries the GEOMAR GeoServer for updates of connected platforms. Uses a JSON-file to store data
#
# TODO: Mapping of properties should be configurable
# TODO: Fix Course over ground
# TODO: Add description to platform output (for use in log)
#
# Copyright (c) 2019 GEOMAR Helmholtz Centre for Ocean Research Kiel
#
# Author:      Patrick Leibold
# Last change: 2019-10-20

import os
#import urllib.request
import requests
import geojson
import json
import logging
import calendar

from iso8601 import parse_date


class DataHandler:
    """
    Very simple and straightforward data handler to retrieve
    and store GeoJSON data from maps.geomar.de/geoserver
    """

    # Class variables
    __config = {}
    __data_url = ""
    __platformlist_file = ""
    __platforms = {}
    __updates = {}

    def __init__(self, config):
        """Class constructor"""
        self.__config = config
        self.__data_url = self.__config["General"]["GeoJsonUrl"]
        # enable overwriting platforms file location by environment variable
        self.__platformlist_file = os.environ.get('SAILDRONE_PLATFORMS_LIST_FILE') \
                                   or self.__config["General"]["PlatformsListFileName"]

        logging.info("Data handler initialized")

    def get_all_platforms(self):
        """Returns all downloaded platforms"""
        return self.__platforms

    def get_updated_platforms(self):
        """Returns only updated platforms since the last server call"""
        return self.__updates

    def try_load(self):
        """Tries to load new GeoJSON data from the Geoserver"""
        if self.__data_url == "":
            return

        # Empty list of updates
        self.__updates = {}

        # Load geoJSON data from server
        with requests.get(self.__data_url) as response:
            collection = response.json()

        # Read platform list from file or create if not existing
        if not os.path.exists(self.__platformlist_file):
            with open(self.__platformlist_file, 'w') as outfile:
                outfile.write("{}")
        else:
            with open(self.__platformlist_file, 'r') as infile:
                self.__platforms = json.load(infile)

        for feature in collection['features']:
            key = str(feature['properties']["platform_id"])

            # Convert ISO timestamp to UNIX timestamp
            timestamp = self.__convert_timestamp(feature['properties']["obs_timestamp"])

            # Update platforms
            if key in self.__platforms:
                platform = self.__platforms[key]
                if platform["timestamp"] != timestamp:
                    # Update platform
                    platform["timestamp"] = timestamp
                    platform["latitude"] = feature['geometry']['coordinates'][1]
                    platform["longitude"] = feature['geometry']['coordinates'][0]
                    platform["speed_over_ground"] = self.__handle_null(feature['properties']["speed_over_ground"])
                    platform["course_over_ground"] = self.__handle_null(feature['properties']["heading"])  # FIXME
                    platform["heading"] = self.__handle_null(feature['properties']["heading"])
                    # Add platform to updated items
                    self.__updates[key] = platform
            else:
                # Create new platform
                platform = {
                    "timestamp": timestamp,
                    "latitude": feature['geometry']['coordinates'][1],
                    "longitude": feature['geometry']['coordinates'][0],
                    "speed_over_ground": self.__handle_null(feature['properties']["speed_over_ground"]),
                    "course_over_ground": self.__handle_null(feature['properties']["heading"]),    # FIXME
                    "heading": self.__handle_null(feature['properties']["heading"]),
                    "enabled": True
                }
                # Add platform to updated items
                self.__updates[key] = platform
            # Add platform to list
            self.__platforms[key] = platform

        # If there are updated platforms: Write platform list back to file
        if len(self.__updates.keys()) > 0:
            self.__dump_platforms()

    def __dump_platforms(self):
        """Writes platform list to file"""
        with open(self.__platformlist_file, 'w') as outfile:
            json.dump(self.__platforms, outfile, sort_keys=False)

    @staticmethod
    def __convert_timestamp(obs_timestamp):
        """Converts timestamp supplied by Geoserver to format expected by Saildrone API
        :param obs_timestamp Timestamp in ISO Format as supplied by Geoserver
        :returns POSIX timestamp to be used in Saildrone API
        """
        parsed = parse_date(obs_timestamp.replace("Z", "+00:00"))
        timestamp = int(calendar.timegm(parsed.timetuple()))
        # alternative: timestamp = parsed.timestamp()
        return timestamp

    @staticmethod
    def __handle_null(value):
        """Converts JSON null to number"""
        if value is None:
            return 0
        return value