Fri, 05 Feb 2021 12:16:29 +0200
update
1 | 1 | #!/usr/bin/env python3 |
2 | import sys, json | |
3 | from misc import * | |
4 | from geometry import * | |
5 | from configparser import ConfigParser | |
6 | ||
7 | class Blockmap: | |
8 | ''' | |
9 | The blockmap is a grid of latitude and longitude lines and models | |
10 | a block -> set relation. A block is a latitude and longitude square. | |
11 | ''' | |
12 | block_size = 200.0 # How big are blocks? | |
13 | def __init__(self, blocks = None): | |
14 | from collections import defaultdict | |
15 | self.blocks = blocks or defaultdict(set) | |
16 | def __getitem__(self, blockid): | |
17 | ''' | |
18 | Returns a block for block coordinates. The block is a set that can | |
19 | contain anything. | |
20 | ''' | |
21 | return self.blocks[blockid] | |
22 | def blockpoint(self, point): | |
23 | ''' | |
24 | Returns blockmap coordinates for geographical coordinates. | |
25 | The blockmap coordinates refer to a block in the blockmap. | |
26 | ''' | |
27 | block_coordinate = lambda x: int(x * self.block_size) | |
28 | return block_coordinate(point.x), block_coordinate(point.y) | |
29 | ||
30 | def blocks_in_shape(blockmap, shape): | |
31 | ''' | |
32 | Finds all blocks inside the bounding box of a shape. | |
33 | ''' | |
34 | from itertools import product | |
35 | min_x, max_x = minmax(point.x for point in shape.points) | |
36 | min_y, max_y = minmax(point.y for point in shape.points) | |
37 | min_blockpoint = blockmap.blockpoint(Location(min_x, min_y)) | |
38 | max_blockpoint = blockmap.blockpoint(Location(max_x, max_y)) | |
39 | range_x = range(min_blockpoint[0], max_blockpoint[0] + 1) | |
40 | range_y = range(min_blockpoint[1], max_blockpoint[1] + 1) | |
41 | yield from (blockmap[x, y] for x, y in product(range_x, range_y)) | |
42 | ||
43 | def create_blockmap(regions): | |
44 | ''' | |
45 | Creates a blockmap of regions | |
46 | ''' | |
47 | blockmap = Blockmap() | |
48 | for region in regions.values(): | |
49 | # Minor shapes contain major shapes, so just use those | |
50 | for shape in (region['minor_shapes'] or region['major_shapes']): | |
51 | for block in blocks_in_shape(blockmap, shape): | |
2 | 52 | set.add(block, region['ref']) |
1 | 53 | return blockmap |
54 | ||
55 | def get_args(): | |
56 | from argparse import ArgumentParser | |
57 | parser = ArgumentParser() | |
58 | parser.add_argument('gtfs_zip') | |
59 | parser.add_argument('profile') | |
60 | return parser.parse_args() | |
61 | ||
62 | def test_shapes(shapes, point): | |
63 | return any(shape.contains_point(point) for shape in shapes) | |
64 | ||
65 | class RegionalLocation: | |
66 | def __init__(self, *, region, region_class): | |
67 | self.region, self.region_class = region, region_class | |
68 | def __repr__(self): | |
69 | return str.format( | |
70 | 'RegionalLocation(region = {region}, region_class = {region_class})', | |
71 | region = repr(self.region), | |
72 | region_class = repr(self.region_class), | |
73 | ) | |
74 | ||
75 | def locate_regionally(position, region): | |
76 | if test_shapes(region['major_shapes'], position): | |
2 | 77 | return RegionalLocation(region = region['ref'], region_class = 'major') |
1 | 78 | elif test_shapes(region['minor_shapes'], position): |
2 | 79 | return RegionalLocation(region = region['ref'], region_class = 'minor') |
1 | 80 | else: |
81 | return None | |
82 | ||
83 | def find_region_for_point(position, regions, blockmap): | |
84 | for region_name in blockmap[blockmap.blockpoint(position)]: | |
85 | region = regions[region_name] | |
86 | classification = locate_regionally(position, region) | |
87 | if classification: | |
88 | return classification | |
89 | ||
90 | class RegionTester: | |
91 | def __init__(self, regions): | |
92 | self.regions = regions | |
93 | self.blockmap = create_blockmap(regions) | |
94 | def __call__(self, latitude, longitude): | |
95 | return find_region_for_point( | |
96 | position = Location(float(latitude), float(longitude)), | |
97 | regions = self.regions, | |
98 | blockmap = self.blockmap, | |
99 | ) |