Package sabx10 :: Package osm :: Module osm
[hide private]
[frames] | no frames]

Source Code for Module sabx10.osm.osm

  1  ############################################################################### 
  2  # 
  3  # sabx10 - an SABX file manipulation library 
  4  # Copyright (C) 2009, 2010 Jay Farrimond (jay@sabikerides.com) 
  5  # 
  6  # This file is part of sabx10. 
  7  # 
  8  # sabx10 is free software: you can redistribute it and/or modify it under the 
  9  # terms of the GNU General Public License as published by the Free Software 
 10  # Foundation, either version 3 of the License, or (at your option) any later 
 11  # version. 
 12  # 
 13  # sabx10 is distributed in the hope that it will be useful, but WITHOUT ANY 
 14  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 15  # A PARTICULAR PURPOSE.  See the GNU General Public License for more details. 
 16  # 
 17  # You should have received a copy of the GNU General Public License along with 
 18  # sabx10.  If not, see <http://www.gnu.org/licenses/>. 
 19  # 
 20  ############################################################################### 
 21  """ 
 22  Create the template data and process the templates so that OSM format XML files 
 23  can be generated.  These files will later be processed by Mapnik into PDF files 
 24  containing ride maps. 
 25  """ 
 26  import math 
 27  import sys 
 28   
 29  from sabx10.map import BORDER 
 30  from sabx10.oxm import parse_segments, parse_top_level, process_rides, \ 
 31      parse_no_def_namespaces 
 32  from sabx10.templating import TemplateProcessor 
 33   
 34  from bounds import Bounds, Cluster 
 35  from consts import HEIGHT, WIDTH, PPI, PIX_SCALE_FACTOR 
 36  from legend import generate_legend 
 37  from utils import BaseId, NodeId 
 38  from zoom import find_close_item, get_zoom_items 
 39   
 40  #### SEGMENTS #### 
41 -def _process_segments(ride):
42 """ 43 Go through all the segments for the ride and add way ids. This is necessary 44 because the OSM way elements are generated from SABX segments and must have 45 unique ids. 46 47 @param ride: L{Ride} whose segments will be modified 48 @type ride: L{Ride} 49 """ 50 way_id = BaseId() 51 for seg in ride.segs: 52 seg.way_id = way_id.next()
53 54 #### TURNS ####
55 -def _process_turns(ride, node_id):
56 """ 57 Go through all the turns for the ride and add node ids. This is necessary 58 because some OSM node elements are generated from SABX turns and must have 59 unique ids. Also, add a name to the turn because all nodes are expected to 60 have names. 61 62 @param ride: L{Ride} whose turns will be modified 63 @type ride: L{Ride} 64 @param node_id: L{NodeId} to generate turn node ids from 65 @type node_id: L{NodeId} 66 """ 67 for turn in ride.turns: 68 if turn.index == 0: 69 turn.name = 'P' 70 else: 71 turn.name = turn.index 72 turn.node_id = node_id.next()
73 74 #### TURN CLUSTERS ####
75 -def _find_clustered_turns(turns, dist):
76 """ 77 Find clusters of turns that are within "dist" distance of eachother. 78 Clusters are used to create smaller maps of turns whose icons would overlap 79 on the large map and be hard to distinguish. These are like insets on some 80 maps used to show highly detailed areas of interest. 81 82 @param turns: C{list} of L{Turn}s to process 83 @type turns: C{list} of L{Turn} 84 @param dist: distance threshold for determining clusters 85 @type dist: C{float} 86 87 @return: C{list} of L{Cluster} 88 @rtype: C{list} of L{Cluster} 89 """ 90 dist /= 2.0 91 clusters = [] 92 for turn in turns: 93 old_cluster = find_close_item(clusters, turn, dist) 94 if old_cluster is not None: 95 old_cluster.expand_to_point(turn.lat, turn.lon) 96 else: 97 new_cluster = Cluster(turn.lat, turn.lon) 98 clusters.append(new_cluster) 99 100 clusters = [cluster for cluster in clusters if cluster.point_count > 1] 101 for index, cluster in enumerate(clusters): 102 cluster.index = index 103 104 return clusters
105
106 -def _size_cluster_bounding_boxes(clusters, dist):
107 """ 108 Enlarge and set the pixel size for the L{Cluster} boxes. 109 110 @param clusters: C{list} of L{Cluster}s to process 111 @type clusters: C{list} of L{Cluster} 112 @param dist: distance threshold for determining clusters 113 @type dist: C{float} 114 """ 115 for cluster in clusters: 116 dist *= 1.1 117 cluster.resize(dist, dist) 118 cluster.set_pixel_size()
119
120 -def _get_turn_clusters(turns, dist):
121 """ 122 Generate and size the clusters for a list of turns. 123 124 @param turns: C{list} of L{Turn}s to process 125 @type turns: C{list} of L{Turn} 126 @param dist: distance threshold for determining clusters 127 @type dist: C{float} 128 129 @return: C{list} of L{Cluster} 130 @rtype: C{list} of L{Cluster} 131 """ 132 clusters = _find_clustered_turns(turns, dist) 133 _size_cluster_bounding_boxes(clusters, dist) 134 return clusters
135 136 #### STOPS AND POIS ####
137 -def _process_stops_pois(items, node_id):
138 """ 139 Go through a list of stops or pois, filter out duplicates, and add node ids 140 to them. 141 142 Adding node ids is necessary because some OSM node elements are generated 143 from SABX stops and pois and must have unique ids. 144 145 Filtering out duplicates is necessary because some stops and pois can be 146 visited more than once in a ride if a ride passes through the same place 147 twice (or more). For the map, the stop or poi icon should only be 148 displayed once. 149 150 @param items: C{list} of L{Stop} or L{Poi} items 151 @type items: C{list} of L{Stop} or L{Poi} 152 @param node_id: L{NodeId} to generate stop and poi node ids from 153 @type node_id: L{NodeId} 154 """ 155 item_ids = [] 156 filtered_items = [] 157 for item in items: 158 if item.id not in item_ids: 159 item_ids.append(item.id) 160 item.node_id = node_id.next() 161 filtered_items.append(item) 162 return filtered_items
163 164 #### PARKING ####
165 -def _process_parking(ride, node_id):
166 """ 167 Update the parking for a ride by adding a node id. This is necessary 168 because some OSM node elements are generated from SABX parking elements and 169 must have unique ids. 170 171 @param ride: L{Ride} whose parking will be modified 172 @type ride: L{Ride} 173 @param node_id: L{NodeId} to generate the parking node id from 174 @type node_id: L{NodeId} 175 """ 176 ride.parking.node_id = node_id.next()
177 178 #### ALL ####
179 -def _process_zooms_n_clusters(the_ride, node_id):
180 """ 181 Go through the given ride and generate turn clusters and lists of zoomed-in 182 and zommed-out turns, stops, and pois. 183 184 @param the_ride: L{Ride} to be processed 185 @type the_ride: L{Ride} 186 @param node_id: L{NodeId} to generate the node ids from 187 @type node_id: L{NodeId} 188 """ 189 icon_width = 10 * PIX_SCALE_FACTOR 190 icon_height = 10 * PIX_SCALE_FACTOR 191 192 sep_dist = the_ride.bounds.calc_sep_dist(icon_width, icon_height) 193 the_ride.zoom_out_turns = get_zoom_items(the_ride.turns, sep_dist, node_id) 194 the_ride.zoom_out_stops = get_zoom_items(the_ride.stops, sep_dist, node_id) 195 the_ride.zoom_out_pois = get_zoom_items(the_ride.pois, sep_dist, node_id) 196 197 the_ride.turn_clusters = _get_turn_clusters(the_ride.turns, sep_dist * 2.0) 198 if len(the_ride.turn_clusters) > 0: 199 sep_dist = the_ride.turn_clusters[0].calc_sep_dist(icon_width, 200 icon_height) 201 the_ride.zoom_in_turns = get_zoom_items( 202 the_ride.turns, sep_dist, node_id) 203 the_ride.zoom_in_stops = get_zoom_items( 204 the_ride.stops, sep_dist, node_id) 205 the_ride.zoom_in_pois = get_zoom_items( 206 the_ride.pois, sep_dist, node_id) 207 else: 208 the_ride.zoom_in_turns = [] 209 the_ride.zoom_in_stops = [] 210 the_ride.zoom_in_pois = []
211
212 -def _process_all_rides(xml_tree, title, node_id):
213 """ 214 Go through all the rides in the rideset and generate the data needed by the 215 template to create the PDF map data. 216 217 @param xml_tree: C{ElementTree} representation of a rideset 218 @type xml_tree: C{ElementTree} stuff 219 @param title: title for the map 220 @type title: C{string} 221 @param node_id: L{NodeId} to generate the node ids from 222 @type node_id: L{NodeId} 223 """ 224 ride_data, bounds = process_rides(xml_tree) 225 for ride in ride_data: 226 _process_parking(ride, node_id) 227 _process_segments(ride) 228 _process_turns(ride, node_id) 229 ride.stops = _process_stops_pois(ride.stops, node_id) 230 ride.pois = _process_stops_pois(ride.pois, node_id) 231 232 ride.bounds = Bounds(box=ride.bounds) 233 ride.bounds.set_pixel_size() 234 ride.bounds.expand_to_good_size(0.1) 235 bounds.expand_to_box(ride.bounds) 236 237 _process_zooms_n_clusters(ride, node_id) 238 generate_legend(ride, title, node_id) 239 240 return ride_data, bounds
241
242 -class SabxProcessor(TemplateProcessor):
243 """ 244 Specialized L{TemplateProcessor} to handle SABX files and turn them into 245 map data that Mapnik can handle. 246 """
247 - def __init__(self, template_file=None, man=None):
248 """ 249 Add command-line option for input file. 250 251 @param template_file: name of template file to process 252 @type template_file: C{string} 253 @param man: man page text 254 @type man: C{string} 255 """ 256 TemplateProcessor.__init__(self, template_file, man) 257 258 self.parser.add_option("-i", "--infile", dest="in_file", 259 help="input sabx data from FILE", 260 metavar="FILE")
261
262 - def process_options(self):
263 """ 264 Setup for the input file. 265 """ 266 TemplateProcessor.process_options(self) 267 if self.options.in_file: 268 self.in_file = open(self.options.in_file, "r") 269 else: 270 self.in_file = sys.stdin
271
272 - def get_template_data(self):
273 """ 274 Add all the extra data that Mapnik needs. 275 """ 276 xml_tree = parse_no_def_namespaces(self.in_file) 277 self.template_data.update(parse_top_level(xml_tree)) 278 279 self.template_data['seg_list'], \ 280 self.template_data['seg_dict'] = parse_segments(xml_tree) 281 node_id = NodeId(self.template_data['seg_list']) 282 self.template_data['rides'], self.template_data['bounds'] = \ 283 _process_all_rides(xml_tree, self.template_data['title'], node_id) 284 self.template_data['PIX_SCALE_FACTOR'] = PIX_SCALE_FACTOR 285 self.template_data['PPI'] = PPI 286 self.template_data['WIDTH'] = WIDTH 287 self.template_data['HEIGHT'] = HEIGHT 288 self.template_data['BORDER'] = BORDER
289