Stations.recreate_missing_destinations()   C
last analyzed

Complexity

Conditions 8

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 17
rs 6.6666
cc 8
1
from datetime import datetime
2
from enum import Enum
3
import json
4
import os
5
import requests
6
7
import ns_api
8
9
from nsmaps.local_settings import USERNAME, APIKEY
10
from nsmaps.logger import logger
11
12
13
class StationType(Enum):
14
    stoptreinstation = 1
15
    megastation = 2
16
    knooppuntIntercitystation = 3
17
    sneltreinstation = 4
18
    intercitystation = 5
19
    knooppuntStoptreinstation = 6
20
    facultatiefStation = 7
21
    knooppuntSneltreinstation = 8
22
23
24
class Station(object):
25
    def __init__(self, nsstation, data_dir, travel_time_min=None):
26
        self.nsstation = nsstation
27
        self.data_dir = data_dir
28
        self.travel_time_min = travel_time_min
29
30
    def get_name(self):
31
        return self.nsstation.names['long']
32
33
    def get_code(self):
34
        return self.nsstation.code
35
36
    def get_country_code(self):
37
        return self.nsstation.country
38
39
    def get_lat(self):
40
        return float(self.nsstation.lat)
41
42
    def get_lon(self):
43
        return float(self.nsstation.lon)
44
45
    def get_travel_time_filepath(self):
46
        return os.path.join(self.data_dir, 'traveltimes/traveltimes_from_' + self.get_code() + '.json')
47
48
    def has_travel_time_data(self):
49
        return os.path.exists(self.get_travel_time_filepath())
50
51
    def get_type(self):
52
        return self.nsstation.stationtype
53
54
    def __str__(self):
55
        return self.get_name() + ' (' +  self.get_code() + ')' + ', travel time: ' + str(self.travel_time_min)
56
57
58
class Stations(object):
59
    def __init__(self, data_dir, test=False):
60
        self.data_dir = data_dir
61
        self.stations = []
62
        nsapi = ns_api.NSAPI(USERNAME, APIKEY)
63
        nsapi_stations = nsapi.get_stations()
64
        for i, nsapi_station in enumerate(nsapi_stations):
65
            if test and i > 5 and nsapi_station.code != 'UT':
66
                continue
67
            if nsapi_station.country != 'NL':
68
                continue
69
            station = Station(nsapi_station, data_dir)
70
            self.stations.append(station)
71
72
    def __iter__(self):
73
        return self.stations.__iter__()
74
75
    def __len__(self):
76
        return self.stations.__len__()
77
78
    # def from_json(self, filename):
79
    #     stations_new = []
80
    #     with open(filename) as file:
81
    #         stations = json.load(file)['stations']
82
    #         for station in stations:
83
    #             self.find_station(self, station.name)
84
    #     return stations_new
85
86
    def find_station(self, name):
87
        for station in self.stations:
88
            if station.get_name() == name:
89
                return station
90
        return None
91
92
    def travel_times_from_json(self, filename):
93
        with open(filename) as file:
94
            travel_times = json.load(file)['stations']
95
            for travel_time in travel_times:
96
                station_name = travel_time['name']
97
                station = self.find_station(station_name)
98
                if station:
99
                    station.travel_time_min = int(travel_time['travel_time_min'])
100
101
    def update_station_data(self, filename_out):
102
        data = {'stations': []}
103
        for station in self.stations:
104
            # if station.country == "NL" and "Utrecht" in station.names['long']:
105
            travel_times_available = station.has_travel_time_data()
106
            contour_available = os.path.exists(os.path.join(self.data_dir, 'contours/' + station.get_code() + '.geojson'))
107
            data['stations'].append({'names': station.nsstation.names,
108
                                     'id': station.get_code(),
109
                                     'lon': station.get_lon(),
110
                                     'lat': station.get_lat(),
111
                                     'type': station.nsstation.stationtype,
112
                                     'travel_times_available': travel_times_available and contour_available})
113
        json_data = json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
114
        with open(os.path.join(self.data_dir, filename_out), 'w') as fileout:
115
            fileout.write(json_data)
116
117
    def get_stations_for_types(self, station_types):
118
        selected_stations = []
119
        for station in self.stations:
120
            for station_type in station_types:
121
                if station.nsstation.stationtype == station_type.name:
122
                    selected_stations.append(station)
123
        return selected_stations
124
125
    def create_traveltimes_data(self, stations_from, timestamp):
126
        """ timestamp format: DD-MM-YYYY hh:mm """
127
        for station_from in stations_from:
128
            filename_out = station_from.get_travel_time_filepath()
129
            if os.path.exists(filename_out):
130
                logger.warning('File ' + filename_out + ' already exists. Will not overwrite. Return.')
131
                continue
132
            json_data = self.create_trip_data_from_station(station_from, timestamp)
133
            with open(filename_out, 'w') as fileout:
134
                fileout.write(json_data)
135
136
    def get_station_code(self, station_name):
137
        for station in self.stations:
138
            if station.get_name() == station_name:
139
                return station.get_code()
140
        return None
141
142
    def create_trip_data_from_station(self, station_from, timestamp):
143
        """ timestamp format: DD-MM-YYYY hh:mm """
144
        via = ""
145
        data = {'stations': []}
146
        data['stations'].append({'name': station_from.get_name(),
147
                                 'id': station_from.get_code(),
148
                                 'travel_time_min': 0,
149
                                 'travel_time_planned': "0:00"})
150
        nsapi = ns_api.NSAPI(USERNAME, APIKEY)
151
        for station in self.stations:
152
            if station.get_code() == station_from.get_code():
153
                continue
154
            trips = []
155
            try:
156
                trips = nsapi.get_trips(timestamp, station_from.get_code(), via, station.get_code())
157
            except TypeError as error:
158
                # this is a bug in ns-api, should return empty trips in case there are no results
159
                logger.error('Error while trying to get trips for destination: ' + station.get_name() + ', from: ' + station_from.get_name())
160
                continue
161
            except requests.exceptions.HTTPError as error:
162
                # 500: Internal Server Error does always happen for some stations (example are Eijs-Wittem and Kerkrade-West)
163
                logger.error('HTTP Error while trying to get trips for destination: ' + station.get_name() + ', from: ' + station_from.get_name())
164
                continue
165
166
            if not trips:
167
                continue
168
169
            shortest_trip = trips[0]
170
            for trip in trips:
171
                travel_time = datetime.strptime(trip.travel_time_planned, "%H:%M").time()
172
                trip.travel_time_min = travel_time.hour * 60 + travel_time.minute
173
                if trip.travel_time_min < shortest_trip.travel_time_min:
174
                    shortest_trip = trip
175
176
            logger.info(shortest_trip.departure + ' - ' + shortest_trip.destination)
177
            data['stations'].append({'name': shortest_trip.destination,
178
                                     'id': self.get_station_code(shortest_trip.destination),
179
                                     'travel_time_min': shortest_trip.travel_time_min,
180
                                     'travel_time_planned': shortest_trip.travel_time_planned})
181
            # time.sleep(0.3)  # balance load on the NS server
182
        json_data = json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
183
        return json_data
184
185
    def get_missing_destinations(self, filename_json):
186
        self.travel_times_from_json(filename_json)
187
        missing_stations = []
188
        for station in self.stations:
189
            if station.travel_time_min is None:
190
                missing_stations.append(station)
191
        return missing_stations
192
193
    def recreate_missing_destinations(self, departure_timestamp, dry_run=False):
194
        ignore_station_ids = ['HRY', 'WTM', 'KRW', 'VMW', 'RTST', 'WIJ', 'SPV', 'SPH']
195
        for station in self.stations:
196
            if not station.has_travel_time_data():
197
                continue
198
            stations_missing = self.get_missing_destinations(station.get_travel_time_filepath())
199
            stations_missing_filtered = []
200
            for station_missing in stations_missing:
201
                if station_missing.get_code() not in ignore_station_ids:
202
                    stations_missing_filtered.append(stations_missing)
203
                    logger.info(station.get_name() + ' has missing station: ' + station_missing.get_name())
204
            if stations_missing_filtered and not dry_run:
205
                json_data = self.create_trip_data_from_station(station, departure_timestamp)
206
                with open(station.get_travel_time_filepath(), 'w') as fileout:
207
                    fileout.write(json_data)
208
            else:
209
                logger.info('No missing destinations for ' + station.get_name() + ' with ' + str(len(ignore_station_ids)) + ' ignored.')
210
211