1717# Used for Mercator projection and tile space
1818OSM = ModestMaps .OpenStreetMap .Provider ()
1919
20+ UPSTREAM_SHST_REQUEST_TIMEOUT_S = 20
21+
2022class Tile :
2123 ''' Container for dicts of SharedStreets geometries, intersections, references, and metadata.
2224 '''
@@ -39,7 +41,8 @@ def round_coord(float):
3941def iter_objects (url , DataClass ):
4042 ''' Generate a stream of objects from the protobuf URL.
4143 '''
42- response , position = requests .get (url ), 0
44+ response = requests .get (url , timeout = UPSTREAM_SHST_REQUEST_TIMEOUT_S )
45+ position = 0
4346 logger .debug ('Got {} bytes: {}' .format (len (response .content ), repr (response .content [:32 ])))
4447
4548 if response .status_code not in range (200 , 299 ):
@@ -66,70 +69,70 @@ def is_inside(southwest, northeast, geometry):
6669 '''
6770 lons = [geometry .lonlats [i ] for i in range (0 , len (geometry .lonlats ), 2 )]
6871 lats = [geometry .lonlats [i ] for i in range (1 , len (geometry .lonlats ), 2 )]
69-
72+
7073 if max (lons ) < southwest .lon or northeast .lon < min (lons ):
7174 return False
72-
75+
7376 elif max (lats ) < southwest .lat or northeast .lat < min (lats ):
7477 return False
75-
78+
7679 return True
7780
7881def get_tile (zoom , x , y , data_url_template = None ):
7982 ''' Get a single Tile instance.
80-
83+
8184 zoom, x, y: Web mercator tile coordinates using OpenStreetMap convention.
82-
85+
8386 https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Zoom_levels
84-
87+
8588 data_url_template: RFC 6570 URI template for upstream protobuf tiles
8689 with z, x, y, and layer expressions. Default to DATA_URL_TEMPLATE.
87-
90+
8891 https://tools.ietf.org/html/rfc6570#section-2.2)
8992 '''
9093 if data_url_template is None :
9194 data_url_template = DATA_URL_TEMPLATE
92-
95+
9396 # Define lat/lon for filtered area
9497 tile_coord = ModestMaps .Core .Coordinate (y , x , zoom )
9598 data_coord = tile_coord .zoomTo (DATA_ZOOM ).container ()
9699 tile_sw = OSM .coordinateLocation (tile_coord .down ())
97100 tile_ne = OSM .coordinateLocation (tile_coord .right ())
98101 data_zxy = dict (z = int (data_coord .zoom ), x = int (data_coord .column ), y = int (data_coord .row ))
99-
102+
100103 logger .debug ((tile_coord , data_coord , tile_sw , tile_ne ))
101-
104+
102105 # Filter geometries within the selected tile
103106 geom_data_url = uritemplate .expand (data_url_template , layer = 'geometry' , ** data_zxy )
104107 geometries = {geom .id : geom for geom in iter_objects (geom_data_url ,
105108 data_classes ['geometry' ]) if is_inside (tile_sw , tile_ne , geom )}
106-
109+
107110 logger .debug ('{} geometries' .format (len (geometries )))
108-
111+
109112 # Get intersections attached to one of the filtered geometries
110113 inter_data_url = uritemplate .expand (data_url_template , layer = 'intersection' , ** data_zxy )
111114
112115 intersection_ids = {id for id in itertools .chain (* [(geom .fromIntersectionId ,
113116 geom .toIntersectionId ) for geom in geometries .values ()])}
114117 intersections = {inter .id : inter for inter in iter_objects (inter_data_url ,
115118 data_classes ['intersection' ]) if inter .id in intersection_ids }
116-
119+
117120 logger .debug ('{} intersections' .format (len (intersections )))
118-
121+
119122 # Get references attached to one of the filtered geometries
120123 ref_data_url = uritemplate .expand (data_url_template , layer = 'reference' , ** data_zxy )
121124 references = {ref .id : ref for ref in iter_objects (ref_data_url ,
122125 data_classes ['reference' ]) if ref .geometryId in geometries }
123-
126+
124127 logger .debug ('{} references' .format (len (references )))
125-
128+
126129 # Get metadata attached to one of the filtered geometries
127130 md_data_url = uritemplate .expand (data_url_template , layer = 'metadata' , ** data_zxy )
128131 metadata = {md .geometryId : md for md in iter_objects (md_data_url ,
129132 data_classes ['metadata' ]) if md .geometryId in geometries }
130-
133+
131134 logger .debug ('{} metadata' .format (len (metadata )))
132-
135+
133136 return Tile (geometries , intersections , references , metadata )
134137
135138def geometry_feature (geometry , metadata , id_length ):
@@ -181,7 +184,7 @@ def reference_feature(reference, id_length):
181184 '''
182185 '''
183186 LR0 , LR1 = reference .locationReferences
184-
187+
185188 return {
186189 'role' : 'SharedStreets:Reference' ,
187190 'id' : reference .id [:id_length ],
@@ -211,26 +214,26 @@ def reference_feature(reference, id_length):
211214
212215def make_geojson (tile , id_length = 32 ):
213216 ''' Get a GeoJSON dictionary for a geographic tile.
214-
217+
215218 tile: Tile instance with lists of SharedStreets entities.
216-
219+
217220 id_length: Desired length of SharedStreets ID strings. Normally 32-char
218221 MD5 hashes, these can be truncated to conserve storage. Default 12.
219222 '''
220223 geojson = dict (type = 'FeatureCollection' , features = [], references = [])
221-
224+
222225 for geometry in tile .geometries .values ():
223226 geojson ['features' ].append (geometry_feature (geometry , tile .metadata [geometry .id ], id_length ))
224227 #break
225-
228+
226229 for intersection in tile .intersections .values ():
227230 geojson ['features' ].append (intersection_feature (intersection , id_length ))
228231 #break
229-
232+
230233 for reference in tile .references .values ():
231234 geojson ['references' ].append (reference_feature (reference , id_length ))
232235 #break
233-
236+
234237 return geojson
235238
236239parser = argparse .ArgumentParser (description = 'Download a tile of SharedStreets data' )
0 commit comments