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

Source Code for Module sabx10.map.map

  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  Main code for generating interactive Google Maps representations of SABX data 
 23  files.  It takes an SABX file and parses it and processes its rides using the 
 24  L{sabx10.oxm} package.  It then adds additional data to the processed rides. 
 25   
 26  @var zoom_levels: distances between 32x32 map icons in Google Maps for each zoom  
 27  level, with the zoom level being the index of the distance in the list 
 28  @type zoom_levels: C{list} 
 29  """ 
 30  import os.path 
 31  import sys 
 32   
 33  import sabx10.oxm as oxm 
 34  from sabx10.templating import TemplateProcessor 
 35   
 36  from consts import BORDER 
 37  from elevations import process_elevations 
 38  import glineenc 
 39   
 40  #### ZOOM LEVEL #### 
 41  zoom_levels = [2690, 1354, 680, 342, 168, 86, 42, 21, 11,  
 42                 5.3, 2.6, 1.32, 0.66, 0.33, 0.17, 0.083,  
 43                 0.041, 0.021, 0.01, 0.005] 
 44   
45 -def _min_distance(points, pt):
46 """ 47 Determine how far away, in statute miles, the closest point to C{pt} is in 48 C{points}. 49 50 @param points: C{list} of points to check against 51 @type points: C{list} of L{Point} objects 52 @param pt: L{Point} object to check for 53 @type pt: L{Point} object 54 55 @return: distance to closest point 56 @rtype: C{float} 57 """ 58 min_dist = 3000 59 for point in points: 60 min_dist = min(min_dist, pt.calculate_distance(point)) 61 return min_dist
62
63 -def _min_zoom_level(points, pt):
64 """ 65 Determine the minimum Google maps zoom level to show C{pt} so that it won't 66 overlap with other points in the C{points} list at higher zooms. This 67 assumes lower zoom levels show bigger areas, such that the highest zoom 68 level would show a tick on a rat's back, while the lowest zoom level would 69 show the continents of the world. Lowest is 0, while highest is 19. These 70 are determined so that 32x32 icons won't overlap in the area of the map 71 surrounding San Antonio, TX. For other parts of the world (closer or 72 farther from the equator) these values may not be all that good. 73 74 @param points: C{list} of points to check against 75 @type points: C{list} of L{Point} objects 76 @param pt: L{Point} object to check for 77 @type pt: L{Point} object 78 79 @return: zoom level 80 @rtype: C{int} 81 """ 82 dist = _min_distance(points, pt) 83 index = 0 84 for zoom in zoom_levels: 85 if dist > zoom: 86 return index 87 index += 1 88 return index
89 90 #### SEGMENTS & TURNS ####
91 -def _process_segments(ride_segs, graph_filebase, ride_index):
92 """ 93 For each segment in a ride, give it a type, Google Map encode its points, 94 and add filenames for its profiles. 95 96 Set the type to "seg", so that Jinja2 can tell what type it is, since 97 Jinja2 doesn't natively support object introspection. 98 99 Add encoded points, meaning points that have been encoded into Google Maps' 100 compact format for storing segment points. 101 102 Add filenames for the profiles for the segments. 103 104 @param ride_segs: list of segments in the ride 105 @type ride_segs: C{list} of L{Segment} objects 106 @param graph_filebase: base name for profile files 107 @type graph_filebase: C{string} 108 @param ride_index: index of ride being processed 109 @type ride_index: C{string} 110 """ 111 for seg in ride_segs: 112 # encode points 113 seg.pts = [(pt.lat, pt.lon) for pt in seg.waypoints] 114 115 # type of 'seg' 116 seg.item_type = 'seg' 117 118 # create filenames 119 seg.profile_small = '%s_prof_small_%s_%s.png' % \ 120 (graph_filebase, ride_index, seg.index) 121 seg.profile_large = '%s_prof_large_%s_%s.png' % \ 122 (graph_filebase, ride_index, seg.index)
123
124 -def _process_turns(ride_turns):
125 """ 126 For each turn in a ride, set its type and calculate a zoom level. 127 128 Calculate the minimum Google Maps zoom level to display this turn. 129 130 Set the type to "turn" so that Jinja2 can tell what type it is, since 131 Jinja2 doesn't natively support object introspection. 132 133 @param ride_turns: list of turns for this ride 134 @type ride_turns: C{list} of L{Turn} objects 135 """ 136 turn_points = [] 137 for turn in ride_turns: 138 # handle zoom level 139 turn_point = oxm.Point(turn.lat, turn.lon) 140 turn.min_zoom = _min_zoom_level(turn_points, turn_point) 141 turn_points.append(turn_point) 142 143 # type of 'turn' 144 turn.item_type = 'turn'
145 146 #### STOPs & POIs ####
147 -def _process_stops(ride_stops):
148 """ 149 For each stop in a ride, set its type. 150 151 Set the type to "stop" so that Jinja2 can tell what type it is, since 152 Jinja2 doesn't natively support object introspection. 153 154 @param ride_stops: list of stops to process 155 @type ride_stops: C{list} of L{Stop} objects 156 """ 157 for stop in ride_stops: 158 stop.item_type = 'stop'
159
160 -def _process_pois(ride_pois):
161 """ 162 For each poi in a ride, set its type. 163 164 Set all the pois to have type "poi" so that Jinja2 can tell what type it 165 is, since Jinja2 doesn't natively support object introspection. 166 167 @param ride_pois: list of pois to process 168 @type ride_pois: C{list} of L{Poi} objects 169 """ 170 for poi in ride_pois: 171 poi.item_type = 'poi'
172
173 -def _process_stop_poi_dists(item_list):
174 """ 175 Calculate the ride distance and off-course distance for each poi or stop in 176 a ride. This function works on stop or poi lists, and expects one of these 177 to be passed in. 178 179 Go through the list of stops/pois for a ride and correlate these with the 180 stops/pois defined for the rideset. For each occurrence of a stop/poi in a 181 ride, save the distance into the ride and the distance off course for the 182 stop/poi. You want to do this because a single stop/poi can be used several 183 times in a ride. When summarizing the stop/poi, you want to be able to 184 list all the distances for the stop/poi. This is used when generating the 185 stop/poi info dialog when clicking on the stop/poi icon in the Google Map. 186 187 @param item_list: list of items to process 188 @type item_list: C{list} of L{Poi} or L{Stop} objects 189 190 @return: distances dictionary 191 @rtype: C{dictionary} 192 """ 193 item_dists = {} 194 for item in item_list: 195 if item.id in item_dists: 196 item_dists[item.id].append((item.distance,item.off_route)) 197 else: 198 item_dists[item.id] = [(item.distance,item.off_route)] 199 return item_dists
200 201 #### MAIN ####
202 -def _process_all_rides(xml_tree, filebase):
203 """ 204 Go through all the rides in the rideset and update them with the extra 205 information necessary to correctly render maps and their data from the 206 templates. 207 208 @param xml_tree: C{ElementTree} representation of a rideset 209 @type xml_tree: C{ElementTree} stuff 210 @param filebase: base upon which to build file names 211 @type filebase: C{string} 212 213 @return: modified ride data 214 @rtype: C{list} of L{Ride} 215 """ 216 ride_data, bounds = oxm.process_rides(xml_tree) 217 for ride in ride_data: 218 # basic processing 219 _process_segments(ride.segs, filebase, ride.index) 220 _process_turns(ride.turns) 221 _process_stops(ride.stops) 222 _process_pois(ride.pois) 223 224 # stop/poi distances 225 ride.stop_dists = _process_stop_poi_dists(ride.stops) 226 ride.poi_dists = _process_stop_poi_dists(ride.pois) 227 228 # elevations 229 ride.ele, ride.ele_segs = process_elevations(ride) 230 231 # file names 232 ride.profile_small = \ 233 '%s_prof_small_%s_all.png' % (filebase, ride.index) 234 ride.profile_large = \ 235 '%s_prof_large_%s_all.png' % (filebase, ride.index) 236 ride.map_large = '%s_map_%s.png' % (filebase, ride.index) 237 ride.map_small_base = '%s_map_%s_' % (filebase, ride.index) 238 239 return ride_data
240
241 -class SabxProcessor(TemplateProcessor):
242 """ 243 Specialized L{TemplateProcessor} to handle SABX files and turn them into 244 Google Maps displayable maps. 245 """ 246
247 - def __init__(self, template_file=None, man=None):
248 """ 249 Add command-line options to set the input file and various directories 250 the templates need to know about. 251 252 @param template_file: name of template file to process 253 @type template_file: C{string} 254 @param man: man page text 255 @type man: C{string} 256 """ 257 TemplateProcessor.__init__(self, template_file, man) 258 259 # input file 260 self.parser.add_option("-i", "--infile", dest="in_file", 261 help="input sabx data from FILE", 262 metavar="FILE") 263 264 # directories 265 self.parser.add_option("-r", "--marker-dir", dest="marker_dir", 266 help="relative web directory for marker files", 267 metavar="MARKERDIR", default=".") 268 self.parser.add_option("-g", "--image-dir", dest="image_dir", 269 help="relative web directory for image files", 270 metavar="IMAGEDIR", default=".") 271 self.parser.add_option("-c", "--css-dir", dest="css_dir", 272 help="relative web directory for css files", 273 metavar="CSSDIR", default=".") 274 self.parser.add_option("-j", "--js-dir", dest="js_dir", 275 help="relative web directory for javascript files", 276 metavar="JSDIR", default=".") 277 self.parser.add_option("-b", "--base-dir", dest="base_dir", 278 help="relative base web directory", 279 metavar="BASEDIR", default=".") 280 self.parser.add_option("-a", "--analytics-key", dest="analytics", 281 help="Google analytics key", 282 metavar="ANALYTICS", default="")
283
284 - def process_options(self):
285 """ 286 Save the dirs and setup for the input and output files. 287 """ 288 def _save_options(self): 289 self.template_data['marker_dir'] = self.options.marker_dir 290 self.template_data['image_dir'] = self.options.image_dir 291 self.template_data['css_dir'] = self.options.css_dir 292 self.template_data['js_dir'] = self.options.js_dir 293 self.template_data['base_dir'] = self.options.base_dir 294 self.template_data['analytics'] = self.options.analytics
295 296 def _setup_input_file(self): 297 if self.options.in_file: 298 self.in_file = open(self.options.in_file, "r") 299 else: 300 self.in_file = sys.stdin
301 302 def _setup_output_file(self): 303 if self.options.out_file: 304 self.filebase = \ 305 os.path.splitext(os.path.basename(self.options.out_file))[0] 306 self.filebase = self.filebase.replace("-print", "") 307 else: 308 self.filebase = "stdout" 309 310 TemplateProcessor.process_options(self) 311 _save_options(self) 312 _setup_input_file(self) 313 _setup_output_file(self) 314
315 - def get_template_data(self):
316 """ 317 Add all the extra-special goodness to the SABX data for our templates. 318 """ 319 xml_tree = oxm.parse_no_def_namespaces(self.in_file) 320 self.template_data.update(oxm.parse_top_level(xml_tree)) 321 self.template_data['filebase'] = self.filebase 322 self.template_data['photos'] = oxm.parse_photos(xml_tree) 323 self.template_data['rides'] = \ 324 _process_all_rides(xml_tree, self.filebase) 325 self.template_data['zoom_factor'] = glineenc.zoom_factor 326 self.template_data['zoom_levels'] = glineenc.num_levels 327 self.template_data['BORDER'] = BORDER
328