5757DPI_300 = 300
5858DPI_600 = 600
5959
60+ L = logging .getLogger ("mapnik.printing" )
61+
6062
6163class PDFPrinter (object ):
6264
@@ -82,7 +84,8 @@ def __init__(self,
8284 preserve_aspect = True ,
8385 centering = CENTERING_CONSTRAINED_AXIS ,
8486 is_latlon = False ,
85- use_ocg_layers = False ):
87+ use_ocg_layers = False ,
88+ font_name = "DejaVu Sans" ):
8689 """
8790 Args:
8891 pagesize: tuple of page size in meters, see predefined sizes in mapnik.formats module
@@ -102,6 +105,7 @@ def __init__(self,
102105 axis. Typically this will be horizontal for portrait pages and vertical for landscape pages.
103106 is_latlon: whether the map is in lat lon degrees or not.
104107 use_ocg_layers: create OCG layers in the PDF, requires PyPDF2
108+ font_name: the font name used each time text is written (e.g., legend titles, representative fraction, etc.)
105109 """
106110 self ._pagesize = pagesize
107111 self ._margin = margin
@@ -127,15 +131,10 @@ def __init__(self,
127131 self ._box = Box2d (percent_box [0 ] * pagesize [0 ], percent_box [1 ] * pagesize [1 ],
128132 percent_box [2 ] * pagesize [0 ], percent_box [3 ] * pagesize [1 ])
129133
130- self .font_name = "DejaVu Sans"
134+ self .font_name = font_name
131135
132136 def render_map (self , m , filename ):
133137 """Renders the given map to filename."""
134-
135- # FIXME: bug. map-background rendering is not correctly supported
136- # it gets added to the legend layer on top of the map, should have its own layer and be below the map
137- # to reproduce render python-mapnik/test/data/good_maps/agg_poly_gamma_map.xml
138-
139138 self ._surface = cairo .PDFSurface (filename , m2pt (self ._pagesize [0 ]), m2pt (self ._pagesize [1 ]))
140139 ctx = cairo .Context (self ._surface )
141140
@@ -345,16 +344,21 @@ def map_spans_antimeridian(self, m):
345344 else :
346345 return False
347346
348- def render_on_map_scale (self , m , grid_layer_name = "Coordinates Grid Overlay" ):
349- """Adds a grid overlay on the map."""
347+ def render_grid_on_map (self , m , grid_layer_name = "Coordinates Grid Overlay" ):
348+ """
349+ Adds a grid overlay on the map, i.e., horizontal and vertical axes plus boxes around the map.
350+
351+ Axes are drawn as 0.5px gray lines.
352+ Boxes alternate between black fill / white stroke and white fill / black stroke. Font is DejaVu Sans.
353+ """
350354 (div_size , page_div_size ) = self ._get_sensible_scalebar_size (m )
351355
352356 # render horizontal axes
353357 (first_value_x , first_value_x_percent ) = self ._get_scale_axes_first_values (
354358 div_size ,
355359 m .envelope ().minx ,
356360 m .envelope ().width ())
357- self ._render_scale_axes (
361+ self ._render_grid_axes_and_boxes_on_map (
358362 first_value_x ,
359363 first_value_x_percent ,
360364 page_div_size ,
@@ -366,7 +370,7 @@ def render_on_map_scale(self, m, grid_layer_name="Coordinates Grid Overlay"):
366370 div_size ,
367371 m .envelope ().miny ,
368372 m .envelope ().height ())
369- self ._render_scale_axes (
373+ self ._render_grid_axes_and_boxes_on_map (
370374 first_value_y ,
371375 first_value_y_percent ,
372376 page_div_size ,
@@ -387,8 +391,8 @@ def _get_sensible_scalebar_size(self, m, num_divisions=8, width=-1):
387391 # ensures we can fit the bar within page area width if specified
388392 page_div_size = self .map_box .width () * div_size / m .envelope ().width ()
389393 while width > 0 and page_div_size > width :
390- div_size /= 2
391- page_div_size /= 2
394+ div_size /= 2.0
395+ page_div_size /= 2.0
392396
393397 return (div_size , page_div_size )
394398
@@ -402,8 +406,14 @@ def _get_scale_axes_first_values(self, div_size, map_envelope_start, map_envelop
402406
403407 return (first_value , first_value_percent )
404408
405- def _render_scale_axes (self , first , first_percent , page_div_size , div_size , is_x_axis ):
406- """Renders the horizontal or vertical axes on the map depending on the is_x_axis parameter."""
409+ def _render_grid_axes_and_boxes_on_map (self , first , first_percent , page_div_size , div_size , is_x_axis ):
410+ """
411+ Renders the horizontal or vertical axes and corresponding boxes on the map depending on the is_x_axis
412+ parameter.
413+
414+ Axes are drawn as 0.5px gray lines.
415+ Boxes alternate between black fill / white stroke and white fill / black stroke. Font is DejaVu Sans.
416+ """
407417 ctx = cairo .Context (self ._surface )
408418
409419 if is_x_axis :
@@ -427,7 +437,7 @@ def _render_scale_axes(self, first, first_percent, page_div_size, div_size, is_x
427437
428438 while value < end :
429439 self ._draw_line (ctx , m2pt (value ), m2pt (boundary_start ), m2pt (value ), m2pt (boundary_end ), line_width = 0.5 )
430- self ._render_scale_boxes (ctx , boundary_start , boundary_end , prev , value , text = text , fill_color = fill_color )
440+ self ._render_grid_boxes (ctx , boundary_start , boundary_end , prev , value , text = text , fill_color = fill_color )
431441
432442 prev = value
433443 value += page_div_size
@@ -438,10 +448,13 @@ def _render_scale_axes(self, first, first_percent, page_div_size, div_size, is_x
438448 text = "%d" % label_value
439449 else :
440450 # ensure that the last box gets drawn
441- self ._render_scale_boxes (ctx , boundary_start , boundary_end , prev , end , fill_color = fill_color )
451+ self ._render_grid_boxes (ctx , boundary_start , boundary_end , prev , end , fill_color = fill_color )
442452
443453 def _draw_line (self , ctx , start_x , start_y , end_x , end_y , line_width = 1 , stroke_color = (0.5 , 0.5 , 0.5 )):
444- """Draws a line from (start_x, start_y) to (end_x, end_y) on the specified cairo context."""
454+ """
455+ Draws a line from (start_x, start_y) to (end_x, end_y) on the specified cairo context.
456+ By default, the line drawn is 1px wide and gray.
457+ """
445458 ctx .save ()
446459
447460 ctx .move_to (start_x , start_y )
@@ -452,14 +465,17 @@ def _draw_line(self, ctx, start_x, start_y, end_x, end_y, line_width=1, stroke_c
452465
453466 ctx .restore ()
454467
455- def _render_scale_boxes (self , ctx , boundary_start , boundary_end , prev , value , text = None , border_size = 8 , fill_color = (0.0 , 0.0 , 0.0 )):
468+ def _render_grid_boxes (self , ctx , boundary_start , boundary_end , prev , value , text = None , border_size = 8 , fill_color = (0.0 , 0.0 , 0.0 )):
456469 """Renders the scale boxes at each end of the grid overlay."""
457470 for bar in (m2pt (boundary_start ) - border_size , m2pt (boundary_end )):
458471 rectangle = Rectangle (m2pt (prev ), bar , m2pt (value - prev ), border_size )
459472 self ._render_box (ctx , rectangle , text , fill_color = fill_color )
460473
461- def _render_box (self , ctx , rectangle , text = None , stroke_color = (0.0 , 0.0 , 0.0 ), fill_color = (0.0 , 0.0 , 0.0 )):
462- """Renders a box with top left corner positioned at (x,y)."""
474+ def _render_box (self , ctx , rectangle , text = None , stroke_color = (0.0 , 0.0 , 0.0 ), fill_color = (1.0 , 1.0 , 1.0 )):
475+ """
476+ Renders a box with top left corner positioned at (x,y).
477+ Default design is white fill and black stroke.
478+ """
463479 ctx .save ()
464480
465481 line_width = 1
@@ -559,8 +575,8 @@ def render_scale(self, m, ctx=None, width=0.05, num_divisions=3, bar_size=8.0, w
559575
560576 Notes:
561577 Does not render if lat lon maps or if the aspect ratio is not preserved.
578+ The scale bar divisions alternate between black fill / white stroke and white fill / black stroke.
562579 """
563-
564580 (w , h ) = (0 , 0 )
565581
566582 # don't render scale text if we are in lat lon
@@ -593,15 +609,14 @@ def _render_scale_bar(self, m, ctx, width=0.05, w=0, h=0, num_divisions=3, bar_s
593609 Returns:
594610 The width and height of the scale bar rendered
595611 """
596-
597612 # FIXME: bug. the scale bar divisions does not scale properly when the map envelope is huge
598613 # to reproduce render python-mapnik/test/data/good_maps/agg_poly_gamma_map.xml and call render_scale
599614
600615 scale_bar_extra_space_factor = 1.2
601616 div_width = width / num_divisions * scale_bar_extra_space_factor
602617 (div_size , page_div_size ) = self ._get_sensible_scalebar_size (m , num_divisions = num_divisions , width = div_width )
603618
604- div_unit = self ._get_div_unit (div_size )
619+ div_unit = self .get_div_unit (div_size )
605620
606621 text = "0{}" .format (div_unit )
607622
@@ -621,16 +636,24 @@ def _render_scale_bar(self, m, ctx, width=0.05, w=0, h=0, num_divisions=3, bar_s
621636
622637 return (w , h )
623638
624- def _get_div_unit (self , div_size ):
625- """Returns the appropriate division unit based on the division size."""
639+ def get_div_unit (self , div_size , div_unit_short = "m" , div_unit_long = "km" , div_unit_divisor = 1000.0 ):
640+ """
641+ Returns the appropriate division unit based on the division size.
626642
627- # TODO: we're assuming the coordinate system units are meters
628- # Is there a way to encapsulate this so miles/meters/feet/whatever can be customised via subclassing?
643+ Args:
644+ div_size: the size of the division
645+ div_unit_short: the default string for the division unit
646+ div_unit_long: the string for the division unit if div_size is large enough to be converted
647+ from div_unit_short to div_unit_long while keeping div_size greater than 1
648+ div_unit_divisor: the divisor applied to convert from div_unit_short to div_unit_long
629649
630- div_unit = "m"
631- if div_size > 1000 :
632- div_size /= 1000
633- div_unit = "km"
650+ Note:
651+ Default values use the metric system
652+ """
653+ div_unit = div_unit_short
654+ if div_size > div_unit_divisor :
655+ div_size /= div_unit_divisor
656+ div_unit = div_unit_long
634657
635658 return div_unit
636659
@@ -670,10 +693,16 @@ def _get_meta_info_corner(self, render_size, m):
670693
671694 return (x , y )
672695
673- def render_on_map_lat_lon_grid (self , m , dec_degrees = True , grid_layer_name = "Latitude Longitude Grid Overlay" ):
674- # FIXME: buggy. does not get the top and right lines. see _render_lat_lon_axis also
696+ def render_graticule_on_map (self , m , dec_degrees = True , grid_layer_name = "Graticule" ):
697+ # FIXME: buggy. does not get the top and right lines and other issues. see _render_graticule_axes_and_text also
698+
699+ """
700+ Renders the graticule on the map.
701+
702+ Lines are drawn as 0.5px wide and gray.
703+ Text font is DejaVu Sans and gray.
704+ """
675705
676- """Renders a lat lon grid on the map."""
677706 # don't render lat_lon grid if we are already in latlon
678707 if self ._is_latlon :
679708 return
@@ -695,8 +724,8 @@ def render_on_map_lat_lon_grid(self, m, dec_degrees=True, grid_layer_name="Latit
695724 latlon_divsize = deg_min_sec_scale (latlon_mapwidth / 7.0 )
696725 latlon_interpsize = latlon_mapwidth / m .width
697726
698- # renders the horizontal lat lon axes
699- self ._render_lat_lon_axes (
727+ # renders the horizontal graticule axes
728+ self ._render_graticule_axes_and_text (
700729 m ,
701730 p2 ,
702731 latlon_bounds ,
@@ -706,8 +735,8 @@ def render_on_map_lat_lon_grid(self, m, dec_degrees=True, grid_layer_name="Latit
706735 dec_degrees ,
707736 True )
708737
709- # renders the vertical lat lon axes
710- self ._render_lat_lon_axes (
738+ # renders the vertical graticule axes
739+ self ._render_graticule_axes_and_text (
711740 m ,
712741 p2 ,
713742 latlon_bounds ,
@@ -744,10 +773,17 @@ def _adjust_latlon_bounds(self, m, proj, latlon_bounds):
744773
745774 return latlon_bounds
746775
747- def _render_lat_lon_axes (self , m , p2 , latlon_bounds , latlon_buffer ,
776+ def _render_graticule_axes_and_text (self , m , p2 , latlon_bounds , latlon_buffer ,
748777 latlon_interpsize , latlon_divsize , dec_degrees , is_x_axis , stroke_color = (0.5 , 0.5 , 0.5 )):
749- # FIXME: buggy. does not get the top and right lines. see render_on_map_lat_lon_grid also
750- """Renders the horizontal or vertical axes on the map depending on the is_x_axis parameter."""
778+ # FIXME: buggy. does not get the top and right lines and other issues. see render_graticule_on_map also
779+ """
780+ Renders the horizontal or vertical axes on the map - depending on the is_x_axis parameter - along with
781+ the latitude or longitude text.
782+
783+ Lines are drawn as 0.5px gray.
784+ Text font is DejaVu Sans gray.
785+ """
786+
751787 ctx = cairo .Context (self ._surface )
752788 ctx .set_source_rgb (* stroke_color )
753789 ctx .set_line_width (1 )
@@ -987,7 +1023,7 @@ def _get_layer_style_valid_rules(self, m, layer_style):
9871023 try :
9881024 sym .avoid_edges = False
9891025 except AttributeError :
990- logging .warning ("Could not set avoid_edges for rule {}" . format ( r .name ) )
1026+ L .warning ("Could not set avoid_edges for rule %s" , r .name )
9911027 if r .min_scale <= m .scale_denominator () and m .scale_denominator () < r .max_scale :
9921028 legend_rule = r
9931029 legend_rule .min_scale = 0
0 commit comments