Skip to content

Commit 748c0bb

Browse files
author
mbonnefoy
committed
Refactor printing module - step 7
Render the map background as a separate layer Remove Centering and Resolutions classes, use constants instead Update a few comments and todos
1 parent 6bb878e commit 748c0bb

2 files changed

Lines changed: 95 additions & 53 deletions

File tree

mapnik/printing/__init__.py

Lines changed: 91 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -33,37 +33,29 @@
3333
except 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

6961
class 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

test/python_tests/pdf_printing_test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ def make_pdf(m, output_pdf, esri_wkt):
3232

3333
def test_pdf_printing():
3434
# TODO: make this a proper test once refactoring is over
35-
source_xml = '../data/good_maps/marker-text-line.xml'.encode('utf-8')
35+
# source_xml = '../data/good_maps/marker-text-line.xml'.encode('utf-8')
36+
source_xml = "../data/good_maps/agg_poly_gamma_map.xml".encode("utf-8")
3637
m = make_map_from_xml(source_xml)
3738

3839
output_pdf = "/tmp/pdf_printing_test-test_pdf_printing.pdf"
3940
esri_wkt = 'GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'
4041
make_pdf(m, output_pdf, esri_wkt)
4142

4243
# TODO: compare against expected PDF once finished
44+
# TODO: test with and without pangocairo
45+
# TODO: test legend with attibution
4346

4447
if __name__ == "__main__":
4548
setup()

0 commit comments

Comments
 (0)