3333except ImportError :
3434 HAS_PYPDF2 = False
3535
36-
37- class Centering (object ):
38-
39- """
40- Style of centering to use with the map.
41-
42- none: map will be placed in the top left corner
43- constrained_axis: map will be centered on the most constrained axis (e.g. vertical for a portrait page); a square
44- map will be constrained horizontally
45- unconstrained_axis: map will be centered on the unconstrained axis
46- vertical: map will be centered vertically
47- horizontal: map will be centered horizontally
48- both: map will be centered vertically and horizontally
49- """
50-
51- none = 0
52- constrained_axis = 1
53- unconstrained_axis = 2
54- vertical = 3
55- horizontal = 4
56- both = 5
57-
58-
59- class Resolutions (object ):
60-
61- """Some predefined resolutions in DPI"""
62-
63- dpi72 = 72
64- dpi150 = 150
65- dpi300 = 300
66- dpi600 = 600
36+ """
37+ Style of centering to use with the map.
38+
39+ CENTERING_NONE: map will be placed in the top left corner
40+ CENTERING_CONSTRAINED_AXIS: map will be centered on the most constrained axis (e.g. vertical for a portrait page); a square
41+ map will be constrained horizontally
42+ CENTERING_UNCONSTRAINED_AXIS: map will be centered on the unconstrained axis
43+ CENTERING_VERTICAL: map will be centered vertically
44+ CENTERING_HORIZONTAL: map will be centered horizontally
45+ CENTERING_BOTH: map will be centered vertically and horizontally
46+ """
47+ CENTERING_NONE = 0
48+ CENTERING_CONSTRAINED_AXIS = 1
49+ CENTERING_UNCONSTRAINED_AXIS = 2
50+ CENTERING_VERTICAL = 3
51+ CENTERING_HORIZONTAL = 4
52+ CENTERING_BOTH = 5
53+
54+ # some predefined resolutions in DPI
55+ DPI_72 = 72
56+ DPI_150 = 150
57+ DPI_300 = 300
58+ DPI_600 = 600
6759
6860
6961class PDFPrinter (object ):
@@ -86,9 +78,9 @@ def __init__(self,
8678 box = None ,
8779 percent_box = None ,
8880 scale_function = default_scale ,
89- resolution = Resolutions . dpi72 ,
81+ resolution = DPI_72 ,
9082 preserve_aspect = True ,
91- centering = Centering . constrained_axis ,
83+ centering = CENTERING_CONSTRAINED_AXIS ,
9284 is_latlon = False ,
9385 use_ocg_layers = False ):
9486 """
@@ -139,6 +131,11 @@ def __init__(self,
139131
140132 def render_map (self , m , filename ):
141133 """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+
142139 self ._surface = cairo .PDFSurface (filename , m2pt (self ._pagesize [0 ]), m2pt (self ._pagesize [1 ]))
143140 ctx = cairo .Context (self ._surface )
144141
@@ -152,6 +149,8 @@ def render_map(self, m, filename):
152149 m .resize (* self ._get_map_pixel_size (mapw , maph ))
153150
154151 (tx , ty ) = self ._get_render_corner ((mapw , maph ), m )
152+
153+ self ._render_map_background (m , ctx , tx , ty )
155154 self ._render_layers_maps (m , ctx , tx , ty )
156155
157156 self .map_box = Box2d (tx , ty , tx + mapw , ty + maph )
@@ -234,10 +233,10 @@ def _has_horizontal_centering(self, m):
234233 """Returns whether the map has an horizontal centering or not."""
235234 is_map_size_constrained = self ._is_map_size_constrained (m )
236235
237- if (self ._centering == Centering . both or
238- self ._centering == Centering . horizontal or
239- (self ._centering == Centering . constrained_axis and is_map_size_constrained ) or
240- (self ._centering == Centering . unconstrained_axis and not is_map_size_constrained )):
236+ if (self ._centering == CENTERING_BOTH or
237+ self ._centering == CENTERING_HORIZONTAL or
238+ (self ._centering == CENTERING_CONSTRAINED_AXIS and is_map_size_constrained ) or
239+ (self ._centering == CENTERING_UNCONSTRAINED_AXIS and not is_map_size_constrained )):
241240 return True
242241 else :
243242 return False
@@ -246,10 +245,10 @@ def _has_vertical_centering(self, m):
246245 """Returns whether the map has a vertical centering or not."""
247246 is_map_size_constrained = self ._is_map_size_constrained (m )
248247
249- if (self ._centering == Centering . both or
250- self ._centering == Centering . vertical or
251- (self ._centering == Centering . constrained_axis and not is_map_size_constrained ) or
252- (self ._centering == Centering . unconstrained_axis and is_map_size_constrained )):
248+ if (self ._centering == CENTERING_BOTH or
249+ self ._centering == CENTERING_VERTICAL or
250+ (self ._centering == CENTERING_CONSTRAINED_AXIS and not is_map_size_constrained ) or
251+ (self ._centering == CENTERING_UNCONSTRAINED_AXIS and is_map_size_constrained )):
253252 return True
254253 else :
255254 return False
@@ -262,6 +261,27 @@ def _is_map_size_constrained(self, m):
262261
263262 return map_aspect > page_aspect
264263
264+ def _render_map_background (self , m , ctx , tx , ty ):
265+ """
266+ Renders the map background if there is one. If the user set use_ocg_layers to True, we put
267+ the background in a separate layer.
268+ """
269+ if m .background or m .background_image or m .background_color :
270+ background_map = Map (m .width ,m .height ,m .srs )
271+ if m .background :
272+ background_map .background = m .background
273+ if m .background_image :
274+ background_map .background_image = m .background_image
275+ if m .background_color :
276+ background_map .background_color = m .background_color
277+
278+ background_map .zoom_to_box (m .envelope ())
279+ self ._render_layer_map (background_map , ctx , tx , ty )
280+
281+ if self ._use_ocg_layers :
282+ self ._surface .show_page ()
283+ self ._layer_names .append ("Map Background" )
284+
265285 def _render_layers_maps (self , m , ctx , tx , ty ):
266286 """Renders a layer as an individual map within a parent Map object."""
267287 for layer in m .layers :
@@ -310,6 +330,11 @@ def _render_layer_map(self, layer_map, ctx, tx, ty):
310330 ctx .translate (m2pt (tx ), m2pt (ty ))
311331 # cairo defaults to 72dpi
312332 ctx .scale (72.0 / self ._resolution , 72.0 / self ._resolution )
333+
334+ # we clip the context to the map rectangle in order to restrict the background to that area
335+ ctx .rectangle (0 , 0 , layer_map .width , layer_map .height )
336+ ctx .clip ()
337+
313338 render (layer_map , ctx )
314339 ctx .restore ()
315340
@@ -568,14 +593,16 @@ def _render_scale_bar(self, m, ctx, width=0.05, w=0, h=0, num_divisions=3, bar_s
568593 Returns:
569594 The width and height of the scale bar rendered
570595 """
596+
597+ # FIXME: bug. the scale bar divisions does not scale properly when the map envelope is huge
598+ # to reproduce render python-mapnik/test/data/good_maps/agg_poly_gamma_map.xml and call render_scale
599+
571600 scale_bar_extra_space_factor = 1.2
572- dwidth = width / num_divisions * scale_bar_extra_space_factor
573- (div_size , page_div_size ) = self ._get_sensible_scalebar_size (m , num_divisions = num_divisions , width = dwidth )
601+ div_width = width / num_divisions * scale_bar_extra_space_factor
602+ (div_size , page_div_size ) = self ._get_sensible_scalebar_size (m , num_divisions = num_divisions , width = div_width )
603+
604+ div_unit = self ._get_div_unit (div_size )
574605
575- div_unit = "m"
576- if div_size > 1000 :
577- div_size /= 1000
578- div_unit = "km"
579606 text = "0{}" .format (div_unit )
580607
581608 ctx .save ()
@@ -594,6 +621,19 @@ def _render_scale_bar(self, m, ctx, width=0.05, w=0, h=0, num_divisions=3, bar_s
594621
595622 return (w , h )
596623
624+ def _get_div_unit (self , div_size ):
625+ """Returns the appropriate division unit based on the division size."""
626+
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?
629+
630+ div_unit = "m"
631+ if div_size > 1000 :
632+ div_size /= 1000
633+ div_unit = "km"
634+
635+ return div_unit
636+
597637 def _render_scale_representative_fraction (self , ctx , box_width , box_width_padding = 2 , font_size = 6 ):
598638 """
599639 Renders the scale text, i.e.
@@ -606,7 +646,7 @@ def _render_scale_representative_fraction(self, ctx, box_width, box_width_paddin
606646 else :
607647 alignment = None
608648
609- text = "Scale 1:{}" .format (self .rounded_mapscale )
649+ text = "Scale 1:{}" .format (int ( self .rounded_mapscale ) )
610650 text_extent = self .write_text (ctx , text , box_width = box_width , size = font_size , alignment = alignment )
611651
612652 text_extent_width = text_extent [3 ]
@@ -812,7 +852,6 @@ def render_legend(self, m, ctx=None, columns=2, width=None, height=None, attribu
812852 column_width = width / columns
813853 render_box .width = m2pt (width )
814854
815- # TODO: refactor that to reduce the number of arguments?
816855 (render_box .width , render_box .height ) = self ._render_legend_items (m , ctx , render_box , column_width , height ,
817856 columns = columns , attribution = attribution , legend_item_box_size = legend_item_box_size )
818857
@@ -906,8 +945,7 @@ def _is_rule_within_map_scale_limits(self, m, feature, rule):
906945 def _create_legend_item_map (self , m , layer , f , legend_map_size ):
907946 """Creates the legend map, i.e., a minified version of the layer map, and returns it."""
908947 legend_map = Map (legend_map_size [0 ], legend_map_size [1 ], srs = m .srs )
909- if m .background :
910- legend_map .background = m .background
948+
911949 # the buffer is needed to ensure that text labels that overflow the edge of the
912950 # map still render for the legend
913951 legend_map .buffer_size = 1000
@@ -1266,7 +1304,8 @@ def _get_pdf_vp(self, measure):
12661304
12671305 bbox = ArrayObject ()
12681306 for x in self .map_box :
1269- # FIXME: this should be converted from meters to points
1307+ # this should be converted from meters to points
1308+ # fix submitted in https://github.com/mapnik/python-mapnik/pull/115
12701309 bbox .append (FloatObject (str (x )))
12711310
12721311 viewport [NameObject ('/BBox' )] = bbox
0 commit comments