2 import sys, json |
2 import sys, json |
3 from misc import * |
3 from misc import * |
4 from geometry import * |
4 from geometry import * |
5 from zipfile import ZipFile |
5 from zipfile import ZipFile |
6 from configparser import ConfigParser |
6 from configparser import ConfigParser |
7 from regions import parse_regions |
|
8 |
|
9 representatives = {} |
|
10 |
|
11 regions = parse_regions(sys.argv[2]) |
|
12 bus_stops = {} |
|
13 block_factor = 0.005 |
|
14 |
7 |
15 class Blockmap: |
8 class Blockmap: |
16 ''' Models a map of blocks. Maps each location to a square area. ''' |
9 ''' |
|
10 Models a map of blocks. Maps each location to a square area. |
|
11 ''' |
|
12 block_factor = 0.005 |
17 def __init__(self, default = None): |
13 def __init__(self, default = None): |
18 from collections import defaultdict |
14 from collections import defaultdict |
19 self.blocks = default or defaultdict(set) |
15 self.blocks = default or defaultdict(set) |
20 def __getitem__(self, blockid): |
16 def __getitem__(self, blockid): |
21 ''' Returns a block for block coordinates. The block is a set that can contain anything. ''' |
17 ''' |
|
18 Returns a block for block coordinates. The block is a set that can |
|
19 contain anything. |
|
20 ''' |
22 return self.blocks[blockid] |
21 return self.blocks[blockid] |
23 def blockpoint(self, point): |
22 def blockpoint(self, point): |
24 ''' Returns a block point for a location. The block point is a coordinate in the blockmap. ''' |
23 ''' |
25 return int(point.x / block_factor), int(point.y / block_factor) |
24 Returns a block point for a location. The block point is a |
|
25 coordinate in the blockmap. |
|
26 ''' |
|
27 block_coordinate = lambda x: int(x / self.block_factor) |
|
28 return block_coordinate(point.x), block_coordinate(point.y) |
|
29 |
|
30 def minmax(data): |
|
31 ''' |
|
32 From: http://code.activestate.com/recipes/577916-fast-minmax-function/ |
|
33 Computes the minimum and maximum values in one-pass using only |
|
34 1.5*len(data) comparisons |
|
35 ''' |
|
36 import itertools |
|
37 it = iter(data) |
|
38 try: |
|
39 lo = hi = next(it) |
|
40 except StopIteration: |
|
41 raise ValueError('minmax() arg is an empty sequence') |
|
42 for x, y in itertools.zip_longest(it, it, fillvalue = lo): |
|
43 if x > y: |
|
44 x, y = y, x |
|
45 lo = min(x, lo) |
|
46 hi = max(y, hi) |
|
47 return lo, hi |
26 |
48 |
27 def blocks_in_shape(blockmap, shape): |
49 def blocks_in_shape(blockmap, shape): |
28 ''' Finds all blocks inside the bounding box of a shape. ''' |
50 ''' |
29 min_x = min(point.x for point in shape.points) |
51 Finds all blocks inside the bounding box of a shape. |
30 min_y = min(point.y for point in shape.points) |
52 ''' |
31 max_x = max(point.x for point in shape.points) |
53 from itertools import product |
32 max_y = max(point.y for point in shape.points) |
54 min_x, max_x = minmax(point.x for point in shape.points) |
|
55 min_y, max_y = minmax(point.y for point in shape.points) |
33 min_blockpoint = blockmap.blockpoint(Location(min_x, min_y)) |
56 min_blockpoint = blockmap.blockpoint(Location(min_x, min_y)) |
34 max_blockpoint = blockmap.blockpoint(Location(max_x, max_y)) |
57 max_blockpoint = blockmap.blockpoint(Location(max_x, max_y)) |
35 for x in range(min_blockpoint[0], max_blockpoint[0] + 1): |
58 range_x = range(min_blockpoint[0], max_blockpoint[0] + 1) |
36 for y in range(min_blockpoint[1], max_blockpoint[1] + 1): |
59 range_y = range(min_blockpoint[1], max_blockpoint[1] + 1) |
37 yield blockmap[x, y] |
60 yield from (blockmap[x, y] for x, y in product(range_x, range_y)) |
38 |
61 |
39 def main(): |
62 def location_from_row(row): |
40 with ZipFile(sys.argv[1]) as archive: |
63 return Location(float(row['stop_lat']), float(row['stop_lon'])) |
|
64 |
|
65 def read_bus_stops(archive_path): |
|
66 bus_stops = dict() |
|
67 with ZipFile(archive_path) as archive: |
41 with archive.open('stops.txt') as file: |
68 with archive.open('stops.txt') as file: |
42 for row in read_csv(map(bytes.decode, file)): |
69 return { |
43 location = Location(float(row['stop_lat']), float(row['stop_lon'])) |
70 row['stop_id']: location_from_row(row) |
44 reference = row['stop_id'] |
71 for row in read_csv(map(bytes.decode, file)) |
45 bus_stops[reference] = location |
72 } |
46 region_shapes = list() |
73 |
47 districts = dict() |
74 def create_blockmap(regions): |
48 bus_stop_regions = dict() |
75 ''' |
|
76 Creates a blockmap of regions |
|
77 ''' |
49 blockmap = Blockmap() |
78 blockmap = Blockmap() |
50 # Find all regions for every block. |
|
51 for region in regions.values(): |
79 for region in regions.values(): |
52 for block in blocks_in_shape(blockmap, region['shape']): |
80 for block in blocks_in_shape(blockmap, region['shape']): |
53 set.add(block, region['name']) |
81 set.add(block, region['name']) |
|
82 return blockmap |
|
83 |
|
84 def get_args(): |
|
85 from argparse import ArgumentParser |
|
86 parser = ArgumentParser() |
|
87 parser.add_argument('gtfs_zip') |
|
88 parser.add_argument('profile') |
|
89 return parser.parse_args() |
|
90 |
|
91 def compute_bus_stop_regions(regions, bus_stops): |
54 # Find the region every node is in |
92 # Find the region every node is in |
|
93 blockmap = create_blockmap(regions) |
|
94 bus_stop_regions = dict() |
55 for stop_id, stop_position in bus_stops.items(): |
95 for stop_id, stop_position in bus_stops.items(): |
56 for region_name in blockmap[blockmap.blockpoint(stop_position)]: |
96 for region_name in blockmap[blockmap.blockpoint(stop_position)]: |
57 region = regions[region_name] |
97 region = regions[region_name] |
58 if region['shape'].contains_point(stop_position): |
98 if region['shape'].contains_point(stop_position): |
59 bus_stop_regions[stop_id] = region['name'] |
99 bus_stop_regions[stop_id] = region['name'] |
60 break |
100 break |
61 else: |
101 else: |
62 bus_stop_regions[stop_id] = None |
102 bus_stop_regions[stop_id] = None |
|
103 return bus_stop_regions |
|
104 |
|
105 def percentage(a): |
|
106 return '%.1f%%' % (a * 100) |
|
107 |
|
108 def compute_route_models(gtfs_path): |
|
109 from buses import load_buses |
|
110 load_buses(gtfs_path) |
|
111 |
|
112 def main(): |
|
113 from regions import parse_regions |
|
114 from misc import profile |
|
115 args = get_args() |
|
116 profile.read(args.profile) |
|
117 regions = parse_regions(profile['regions']['osm-path']) |
|
118 bus_stops = read_bus_stops(args.gtfs_zip) |
|
119 bus_stop_regions = compute_bus_stop_regions(regions, bus_stops) |
63 covered = sum(1 if value else 0 for value in bus_stop_regions.values()) |
120 covered = sum(1 if value else 0 for value in bus_stop_regions.values()) |
64 total = len(bus_stops) |
121 total = len(bus_stops) |
65 print('%.1f%% bus stops covered.' % (covered * 100 / total), file = sys.stderr) |
122 print('%s bus stops covered.' % percentage(covered / total), |
|
123 file = sys.stderr) |
66 json.dump(bus_stop_regions, sys.stdout, indent = 2) |
124 json.dump(bus_stop_regions, sys.stdout, indent = 2) |
|
125 return 0 |
67 |
126 |
68 if __name__ == '__main__': |
127 if __name__ == '__main__': |
69 main() |
128 from sys import exit |
|
129 exit(main()) |