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 Computes the minimum and maximum values in one-pass using only |
|
33 1.5*len(data) comparisons |
|
34 ''' |
|
35 import itertools |
|
36 it = iter(data) |
|
37 try: |
|
38 lo = hi = next(it) |
|
39 except StopIteration: |
|
40 raise ValueError('minmax() arg is an empty sequence') |
|
41 for x, y in itertools.zip_longest(it, it, fillvalue = lo): |
|
42 if x > y: |
|
43 x, y = y, x |
|
44 lo = min(x, lo) |
|
45 hi = max(y, hi) |
|
46 return lo, hi |
26 |
47 |
27 def blocks_in_shape(blockmap, shape): |
48 def blocks_in_shape(blockmap, shape): |
28 ''' Finds all blocks inside the bounding box of a shape. ''' |
49 ''' |
29 min_x = min(point.x for point in shape.points) |
50 Finds all blocks inside the bounding box of a shape. |
30 min_y = min(point.y for point in shape.points) |
51 ''' |
31 max_x = max(point.x for point in shape.points) |
52 from itertools import product |
32 max_y = max(point.y for point in shape.points) |
53 min_x, max_x = minmax(point.x for point in shape.points) |
|
54 min_y, max_y = minmax(point.y for point in shape.points) |
33 min_blockpoint = blockmap.blockpoint(Location(min_x, min_y)) |
55 min_blockpoint = blockmap.blockpoint(Location(min_x, min_y)) |
34 max_blockpoint = blockmap.blockpoint(Location(max_x, max_y)) |
56 max_blockpoint = blockmap.blockpoint(Location(max_x, max_y)) |
35 for x in range(min_blockpoint[0], max_blockpoint[0] + 1): |
57 range_x = range(min_blockpoint[0], max_blockpoint[0] + 1) |
36 for y in range(min_blockpoint[1], max_blockpoint[1] + 1): |
58 range_y = range(min_blockpoint[1], max_blockpoint[1] + 1) |
37 yield blockmap[x, y] |
59 yield from (blockmap[x, y] for x, y in product(range_x, range_y)) |
38 |
60 |
39 def main(): |
61 def location_from_row(row): |
40 with ZipFile(sys.argv[1]) as archive: |
62 return Location(float(row['stop_lat']), float(row['stop_lon'])) |
|
63 |
|
64 def read_bus_stops(archive_path): |
|
65 bus_stops = dict() |
|
66 with ZipFile(archive_path) as archive: |
41 with archive.open('stops.txt') as file: |
67 with archive.open('stops.txt') as file: |
42 for row in read_csv(map(bytes.decode, file)): |
68 return { |
43 location = Location(float(row['stop_lat']), float(row['stop_lon'])) |
69 row['stop_id']: location_from_row(row) |
44 reference = row['stop_id'] |
70 for row in read_csv(map(bytes.decode, file)) |
45 bus_stops[reference] = location |
71 } |
46 region_shapes = list() |
72 |
47 districts = dict() |
73 def create_blockmap(regions): |
48 bus_stop_regions = dict() |
74 ''' |
|
75 Creates a blockmap of regions |
|
76 ''' |
49 blockmap = Blockmap() |
77 blockmap = Blockmap() |
50 # Find all regions for every block. |
|
51 for region in regions.values(): |
78 for region in regions.values(): |
52 for block in blocks_in_shape(blockmap, region['shape']): |
79 for block in blocks_in_shape(blockmap, region['shape']): |
53 set.add(block, region['name']) |
80 set.add(block, region['name']) |
|
81 return blockmap |
|
82 |
|
83 def get_args(): |
|
84 from argparse import ArgumentParser |
|
85 parser = ArgumentParser() |
|
86 parser.add_argument('gtfs_zip') |
|
87 parser.add_argument('mapfile') |
|
88 return parser.parse_args() |
|
89 |
|
90 def compute_bus_stop_regions(regions, bus_stops): |
54 # Find the region every node is in |
91 # Find the region every node is in |
|
92 blockmap = create_blockmap(regions) |
|
93 bus_stop_regions = dict() |
55 for stop_id, stop_position in bus_stops.items(): |
94 for stop_id, stop_position in bus_stops.items(): |
56 for region_name in blockmap[blockmap.blockpoint(stop_position)]: |
95 for region_name in blockmap[blockmap.blockpoint(stop_position)]: |
57 region = regions[region_name] |
96 region = regions[region_name] |
58 if region['shape'].contains_point(stop_position): |
97 if region['shape'].contains_point(stop_position): |
59 bus_stop_regions[stop_id] = region['name'] |
98 bus_stop_regions[stop_id] = region['name'] |
60 break |
99 break |
61 else: |
100 else: |
62 bus_stop_regions[stop_id] = None |
101 bus_stop_regions[stop_id] = None |
|
102 return bus_stop_regions |
|
103 |
|
104 def percentage(a): |
|
105 return '%.1f%%' % (a * 100) |
|
106 |
|
107 def main(): |
|
108 from regions import parse_regions |
|
109 args = get_args() |
|
110 regions = parse_regions(args.mapfile) |
|
111 bus_stops = read_bus_stops(args.gtfs_zip) |
|
112 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()) |
113 covered = sum(1 if value else 0 for value in bus_stop_regions.values()) |
64 total = len(bus_stops) |
114 total = len(bus_stops) |
65 print('%.1f%% bus stops covered.' % (covered * 100 / total), file = sys.stderr) |
115 print('%s bus stops covered.' % percentage(covered / total), |
|
116 file = sys.stderr) |
66 json.dump(bus_stop_regions, sys.stdout, indent = 2) |
117 json.dump(bus_stop_regions, sys.stdout, indent = 2) |
67 |
118 |
68 if __name__ == '__main__': |
119 if __name__ == '__main__': |
69 main() |
120 main() |