Completed
Push — master ( e6f76f...dfb20f )
by Bart
01:02
created

Contour.load()   A

Complexity

Conditions 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 3
rs 10
1
import sys
2
import os
3
import math
4
5
from timeit import default_timer as timer
6
from multiprocessing import Process, Queue
7
8
import numpy
9
from scipy.spatial import KDTree
10
11
import matplotlib.pyplot as plt
12
from matplotlib.figure import Figure
13
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
14
15
import geojson
16
import geojsoncontour
17
18
import nsmaps
19
from nsmaps.logger import logger
20
21
22
def dotproduct(v1, v2):
23
    return sum((a * b) for a, b in zip(v1, v2))
24
25
26
def length(v):
27
    return math.sqrt(dotproduct(v, v))
28
29
30
def angle(v1, v2):
31
    return math.acos(dotproduct(v1, v2) / (length(v1) * length(v2)))
32
33
34
class ContourData(object):
35
    def __init__(self):
36
        self.Z = None
37
        self.index_begin = 0
38
39
40
class ContourPlotConfig(object):
41
    def __init__(self):
42
        self.stepsize_deg = 0.005
43
        self.n_processes = 4
44
        self.cycle_speed_kmh = 18.0
45
        self.n_nearest = 20
46
        self.lon_start = 3.0
47
        self.lat_start = 50.5
48
        self.delta_deg = 6.5
49
        self.lon_end = self.lon_start + self.delta_deg
50
        self.lat_end = self.lat_start + self.delta_deg / 2.0
51
        self.min_angle_between_segments = 7
52
53
    def print_bounding_box(self):
54
        print(
55
            '[[' + str(self.lon_start) + ',' + str(self.lat_start) + '],'
56
            '[' + str(self.lon_start) + ',' + str(self.lat_end) + '],'
57
            '[' + str(self.lon_end) + ',' + str(self.lat_end) + '],'
58
            '[' + str(self.lon_end) + ',' + str(self.lat_start) + '],'
59
            '[' + str(self.lon_start) + ',' + str(self.lat_start) + ']]'
60
        )
61
62
63
class TestConfig(ContourPlotConfig):
64
    def __init__(self):
65
        super().__init__()
66
        self.stepsize_deg = 0.005
67
        self.n_processes = 4
68
        self.lon_start = 4.8
69
        self.lat_start = 52.0
70
        self.delta_deg = 1.0
71
        self.lon_end = self.lon_start + self.delta_deg
72
        self.lat_end = self.lat_start + self.delta_deg / 2.0
73
        self.min_angle_between_segments = 7
74
        self.latrange = []
75
        self.lonrange = []
76
        self.Z = [[]]
77
78
79
class Contour(object):
80
    def __init__(self, departure_station, stations, config):
81
        self.departure_station = departure_station
82
        self.stations = stations
83
        self.config = config
84
85
    def create_contour_data(self):
86
        logger.info('BEGIN')
87
        if self.departure_station.has_travel_time_data():
88
            self.stations.travel_times_from_json(self.departure_station.get_travel_time_filepath())
89
        else:
90
            logger.error('Input file ' + self.departure_station.get_travel_time_filepath() + ' not found. Skipping station.')
91
92
        start = timer()
93
        numpy.set_printoptions(3, threshold=100, suppress=True)  # .3f
94
95
        altitude = 0.0
96
        self.lonrange = numpy.arange(self.config.lon_start, self.config.lon_end, self.config.stepsize_deg)
97
        self.latrange = numpy.arange(self.config.lat_start, self.config.lat_end, self.config.stepsize_deg / 2.0)
98
        self.Z = numpy.zeros((int(self.lonrange.shape[0]), int(self.latrange.shape[0])))
99
        gps = nsmaps.utilgeo.GPS()
100
101
        positions = []
102
        for station in self.stations:
103
            x, y, z = gps.lla2ecef([station.get_lat(), station.get_lon(), altitude])
104
            positions.append([x, y, z])
105
106
        logger.info('starting spatial interpolation')
107
108
        # tree to find nearest neighbors
109
        tree = KDTree(positions)
110
111
        queue = Queue()
112
        processes = []
113
        if self.config.n_nearest > len(self.stations):
114
            self.config.n_nearest = len(self.stations)
115
        latrange_per_process = int(len(self.latrange)/self.config.n_processes)
116
        for i in range(0, self.config.n_processes):
117
            begin = i * latrange_per_process
118
            end = (i+1) * latrange_per_process
119
            latrange_part = self.latrange[begin:end]
120
            process = Process(target=self.interpolate_travel_time, args=(queue, i, self.stations.stations, tree, gps, latrange_part,
121
                                                                         self.lonrange, altitude, self.config.n_nearest, self.config.cycle_speed_kmh))
122
            processes.append(process)
123
124
        for process in processes:
125
            process.start()
126
127
        # get from the queue and append the values
128
        for i in range(0, self.config.n_processes):
129
            data = queue.get()
130
            index_begin = data.index_begin
131
            begin = int(index_begin*len(self.latrange)/self.config.n_processes)
132
            end = int((index_begin+1)*len(self.latrange)/self.config.n_processes)
133
            self.Z[0:][begin:end] = data.Z
134
135
        for process in processes:
136
            process.join()
137
138
        end = timer()
139
        logger.info('finished spatial interpolation in ' + str(end - start) + ' [sec]')
140
        logger.info('END')
141
142
    @property
143
    def data_filename(self):
144
        return 'data/contour_data_' + self.departure_station.get_code() + '.npz'
145
146
    def load(self):
147
        with open(self.data_filename, 'rb') as filein:
148
            self.Z = numpy.load(filein)
149
150
    def save(self):
151
        with open(self.data_filename, 'wb') as fileout:
152
            numpy.save(fileout, self.Z)
153
154
    def create_geojson(self, filepath, stroke_width=1, levels=[], norm=None, overwrite=False):
155
        if not overwrite and os.path.exists(filepath):
156
            logger.error('Output file ' + filepath + ' already exists. Will not override.')
157
            return
158
159
        figure = Figure(frameon=False)
160
        FigureCanvas(figure)
161
        ax = figure.add_subplot(111)
162
        # contours = plt.contourf(lonrange, latrange, Z, levels=levels, cmap=plt.cm.plasma)
163
        contours = ax.contour(
164
            self.lonrange, self.latrange, self.Z,
165
            levels=levels,
166
            norm=norm,
167
            cmap=plt.cm.jet,
168
            linewidths=3
169
        )
170
171
        ndigits = len(str(int(1.0 / self.config.stepsize_deg))) + 1
172
        logger.info('converting contour to geojson file: ' + filepath)
173
        geojsoncontour.contour_to_geojson(
174
            contour=contours,
175
            geojson_filepath=filepath,
176
            contour_levels=levels,
177
            min_angle_deg=self.config.min_angle_between_segments,
178
            ndigits=ndigits,
179
            unit='min',
180
            stroke_width=stroke_width
181
        )
182
183
        cbar = figure.colorbar(contours, format='%d', orientation='horizontal')
184
        cbar.set_label('Travel time [minutes]')
185
        # cbar.set_ticks(self.config.colorbar_ticks)
186
        ax.set_visible(False)
187
        figure.savefig(
188
            filepath.replace('.geojson', '') + "_colorbar.png",
189
            dpi=90,
190
            bbox_inches='tight',
191
            pad_inches=0,
192
            transparent=True,
193
        )
194
195
    @staticmethod
196
    def interpolate_travel_time(q, position, stations, kdtree, gps, latrange, lonrange, altitude, n_nearest, cycle_speed_kmh):
197
        # n_nearest: check N nearest stations as best start for cycle route
198
        logger.info('interpolate_travel_time')
199
        Z = numpy.zeros((int(latrange.shape[0]), int(lonrange.shape[0])))
200
        for i, lat in enumerate(latrange):
201
            if i % (len(latrange) / 10) == 0:
202
                logger.debug(str(int(i / len(latrange) * 100)) + '%')
203
204
            for j, lon in enumerate(lonrange):
205
                x, y, z = gps.lla2ecef([lat, lon, altitude])
206
                distances, indexes = kdtree.query([x, y, z], n_nearest)
207
                min_travel_time = sys.float_info.max
208
                for distance, index in zip(distances, indexes):
209
                    if stations[index].travel_time_min is None:
210
                        continue
211
                    travel_time = stations[index].travel_time_min + distance / 1000.0 / cycle_speed_kmh * 60.0
212
                    if travel_time < min_travel_time:
213
                        min_travel_time = travel_time
214
                Z[i][j] = min_travel_time
215
        data = ContourData()
216
        data.index_begin = position
217
        data.Z = Z
218
        q.put(data)
219
        logger.info('end interpolate_travel_time')
220
        return
221