Skip to content

Commit 423075c

Browse files
author
mbonnefoy
committed
Refactor printing module - step 8
Change the grid and graticule rendering methods names Give some flexibility around the div unit for the scale bar Replace a few integer divisions by float divisions Add comments on hardcoded design IMPORTANT: This commit changes the API
1 parent 748c0bb commit 423075c

1 file changed

Lines changed: 79 additions & 43 deletions

File tree

mapnik/printing/__init__.py

Lines changed: 79 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
DPI_300 = 300
5858
DPI_600 = 600
5959

60+
L = logging.getLogger("mapnik.printing")
61+
6062

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

Comments
 (0)