kopia lustrzana https://github.com/hholzgra/ocitysmap
Major refactoring of the main rendering process
The RenderingSession system has disappeared in favor of a much simpler ctor()/render() API for the Renderers. As a consequence, it's the job of each renderer to create the grid, the map_canvas and the index renderer (if any). But this is needed because the renderer is in charge of the page layout, which impacts the way the map_canvas is rendered and which depends on the index. Potentially every renderer need to manage the chicken-and-egg problem in their own way. Of course, now this ctor takes a street_index as parameter, which allows it (by way of an index renderer) to update the global layout depending on the size of the index. For now only the PlainRenderer has been updated. It users an object placeholder to replace the RenderingSession object. This object placeholder allows one to define a set of attributes and to access them through the standard dotted notation. This object has a meaning only internally to the PlainRenderer::render(). We don't define a global structure like RenderingSession in order not to pollute the code with global names. And such an object placeholder is only a private placeholder for render() and the function it calls, to avoid having to write 6235 arguments to pass the helper functions it calls.stable
rodzic
230e2ccda2
commit
81d0afa312
|
|
@ -381,39 +381,32 @@ class OCitySMap:
|
|||
|
||||
# Create a temporary directory for all our shape files
|
||||
tmpdir = tempfile.mkdtemp(prefix='ocitysmap')
|
||||
l.debug('Rendering in temporary directory %s' % tmpdir)
|
||||
|
||||
# Prepare the main renderer
|
||||
renderer_cls = renderers.get_renderer_class_by_name(renderer_name)
|
||||
renderer = renderer_cls(config, tmpdir)
|
||||
renderer.create_map_canvas()
|
||||
|
||||
shade_wkt = self._get_shade_wkt(
|
||||
renderer.canvas.get_actual_bounding_box(),
|
||||
config.polygon_wkt)
|
||||
renderer.add_shade(shade_wkt)
|
||||
|
||||
renderer.canvas.render()
|
||||
|
||||
street_index_renderer = index.StreetIndexRenderer(
|
||||
self._i18n,
|
||||
street_index.categories)
|
||||
|
||||
try:
|
||||
l.debug('Rendering in temporary directory %s' % tmpdir)
|
||||
|
||||
# Prepare the generic renderer
|
||||
renderer_cls = renderers.get_renderer_class_by_name(renderer_name)
|
||||
renderer = renderer_cls(config, tmpdir, street_index)
|
||||
|
||||
# Update the street_index to reflect the grid's actual position
|
||||
if renderer.grid:
|
||||
street_index.apply_grid(renderer.grid)
|
||||
|
||||
# Perform the actual rendering to the Cairo devices
|
||||
for output_format in output_formats:
|
||||
output_filename = '%s.%s' % (file_prefix, output_format)
|
||||
self._render_one(renderer, street_index_renderer,
|
||||
output_filename, output_format)
|
||||
self._render_one(renderer, output_format, output_filename)
|
||||
|
||||
# Also dump the CSV street index
|
||||
street_index.write_to_csv(config.title, '%s.csv' % file_prefix)
|
||||
finally:
|
||||
self._cleanup_tempdir(tmpdir)
|
||||
|
||||
def _render_one(self, renderer, street_index_renderer, filename,
|
||||
output_format):
|
||||
def _render_one(self, renderer, output_format, output_filename):
|
||||
l.info('Rendering to %s format...' % output_format.upper())
|
||||
|
||||
factory = None
|
||||
dpi = renderers.RenderingSession.PT_PER_INCH
|
||||
dpi = renderers.UTILS.PT_PER_INCH
|
||||
|
||||
if output_format == 'png':
|
||||
try:
|
||||
|
|
@ -422,17 +415,17 @@ class OCitySMap:
|
|||
dpi = OCitySMap.DEFAULT_RENDERING_PNG_DPI
|
||||
|
||||
factory = lambda w,h: cairo.ImageSurface(cairo.FORMAT_ARGB32,
|
||||
int(renderers.RenderingSession.pt_to_dots_with_dpi(w, dpi)),
|
||||
int(renderers.RenderingSession.pt_to_dots_with_dpi(h, dpi)))
|
||||
int(renderers.UTILS.convert_pt_to_dots_with_dpi(w, dpi)),
|
||||
int(renderers.UTILS.convert_pt_to_dots_with_dpi(h, dpi)))
|
||||
elif output_format == 'svg':
|
||||
factory = lambda w,h: cairo.SVGSurface(filename, w, h)
|
||||
factory = lambda w,h: cairo.SVGSurface(output_filename, w, h)
|
||||
elif output_format == 'svgz':
|
||||
factory = lambda w,h: cairo.SVGSurface(
|
||||
gzip.GzipFile(filename, 'wb'), w, h)
|
||||
gzip.GzipFile(output_filename, 'wb'), w, h)
|
||||
elif output_format == 'pdf':
|
||||
factory = lambda w,h: cairo.PDFSurface(filename, w, h)
|
||||
factory = lambda w,h: cairo.PDFSurface(output_filename, w, h)
|
||||
elif output_format == 'ps':
|
||||
factory = lambda w,h: cairo.PSSurface(filename, w, h)
|
||||
factory = lambda w,h: cairo.PSSurface(output_filename, w, h)
|
||||
elif output_format == 'csv':
|
||||
# We don't render maps into CSV.
|
||||
return
|
||||
|
|
@ -442,13 +435,11 @@ class OCitySMap:
|
|||
'Unsupported output format: %s!' % output_format.upper()
|
||||
|
||||
surface = factory(renderer.paper_width_pt, renderer.paper_height_pt)
|
||||
rs = renderer.create_rendering_session(surface, street_index_renderer,
|
||||
dpi)
|
||||
renderer.render(rs)
|
||||
renderer.render(surface, dpi)
|
||||
|
||||
l.debug('Writing %s...' % filename)
|
||||
l.debug('Writing %s...' % output_filename)
|
||||
if output_format == 'png':
|
||||
surface.write_to_png(filename)
|
||||
surface.write_to_png(output_filename)
|
||||
|
||||
surface.finish()
|
||||
|
||||
|
|
|
|||
|
|
@ -28,31 +28,36 @@ import logging
|
|||
import mapnik
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import pango
|
||||
import pangocairo
|
||||
|
||||
import grid
|
||||
import map_canvas
|
||||
from map_canvas import MapCanvas
|
||||
from grid import Grid
|
||||
import shapes
|
||||
|
||||
l = logging.getLogger('ocitysmap')
|
||||
|
||||
class RenderingSession:
|
||||
|
||||
class UTILS:
|
||||
PT_PER_INCH = 72.0
|
||||
|
||||
def __init__(self, surface, street_index_renderer,
|
||||
dpi=PT_PER_INCH):
|
||||
self.surface = surface
|
||||
self.street_index_renderer = street_index_renderer
|
||||
self.dpi = dpi
|
||||
|
||||
def pt_to_dots(self, pt):
|
||||
return RenderingSession.pt_to_dots_with_dpi(pt, self.dpi)
|
||||
@staticmethod
|
||||
def convert_pt_to_dots(pt, dpi = PT_PER_INCH):
|
||||
return float(pt * dpi) / UTILS.PT_PER_INCH
|
||||
|
||||
@staticmethod
|
||||
def pt_to_dots_with_dpi(pt, dpi):
|
||||
return float(pt * dpi) / RenderingSession.PT_PER_INCH
|
||||
def convert_mm_to_pt(mm):
|
||||
return ((mm/10.0) / 2.54) * 72
|
||||
|
||||
class _StructPlaceholder:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def create_struct(**kv):
|
||||
s = UTILS._StructPlaceholder()
|
||||
for k,v in kv.iteritems():
|
||||
setattr(s, k, v)
|
||||
return s
|
||||
|
||||
|
||||
class Renderer:
|
||||
|
|
@ -60,7 +65,6 @@ class Renderer:
|
|||
The job of an OCitySMap layout renderer is to lay out the resulting map and
|
||||
render it from a given rendering configuration.
|
||||
"""
|
||||
|
||||
# Portrait paper sizes in milimeters
|
||||
PAPER_SIZES = [('A5', 148, 210),
|
||||
('A4', 210, 297),
|
||||
|
|
@ -94,40 +98,20 @@ class Renderer:
|
|||
# on the rendered map of a kilometer
|
||||
DEFAULT_KM_IN_MM = 100
|
||||
|
||||
def __init__(self, rc, tmpdir):
|
||||
self.rc = rc
|
||||
self.tmpdir = tmpdir
|
||||
self.canvas = None
|
||||
self.grid = None
|
||||
def __init__(self, rc, tmpdir, street_index):
|
||||
# Note: street_index may be None
|
||||
self.rc = rc
|
||||
self.tmpdir = tmpdir
|
||||
self.grid = None # The implementation is in charge of it
|
||||
self.street_index = street_index
|
||||
|
||||
self.paper_width_pt = \
|
||||
Renderer.convert_mm_to_pt(self.rc.paper_width_mm)
|
||||
UTILS.convert_mm_to_pt(self.rc.paper_width_mm)
|
||||
self.paper_height_pt = \
|
||||
Renderer.convert_mm_to_pt(self.rc.paper_height_mm)
|
||||
UTILS.convert_mm_to_pt(self.rc.paper_height_mm)
|
||||
|
||||
@staticmethod
|
||||
def convert_mm_to_pt(mm):
|
||||
return ((mm/10.0) / 2.54) * 72
|
||||
|
||||
def _create_map_canvas(self, graphical_ratio):
|
||||
"""Creates a map canvas of the given graphical ratio and lays out the
|
||||
grid on top of it with the canvas's corrected goegraphical bounding
|
||||
box."""
|
||||
self.canvas = map_canvas.MapCanvas(self.rc.stylesheet,
|
||||
self.rc.bounding_box,
|
||||
graphical_ratio)
|
||||
|
||||
# Add the grid
|
||||
self.grid = grid.Grid(self.canvas.get_actual_bounding_box(),
|
||||
self.rc.rtl)
|
||||
grid_shape = self.grid.generate_shape_file(
|
||||
os.path.join(self.tmpdir, 'grid.shp'))
|
||||
self.canvas.add_shape_file(grid_shape,
|
||||
self.rc.stylesheet.grid_line_color,
|
||||
self.rc.stylesheet.grid_line_alpha,
|
||||
self.rc.stylesheet.grid_line_width)
|
||||
|
||||
def _draw_centered_text(self, ctx, text, x, y):
|
||||
def _draw_centered_text(ctx, text, x, y):
|
||||
ctx.save()
|
||||
xb, yb, tw, th, xa, ya = ctx.text_extents(text)
|
||||
ctx.move_to(x - tw/2.0 - xb, y - yb/2.0)
|
||||
|
|
@ -135,7 +119,8 @@ class Renderer:
|
|||
ctx.stroke()
|
||||
ctx.restore()
|
||||
|
||||
def _adjust_font_size(self, layout, fd, constraint_x, constraint_y):
|
||||
@staticmethod
|
||||
def _adjust_font_size(layout, fd, constraint_x, constraint_y):
|
||||
while (layout.get_size()[0] / pango.SCALE < constraint_x and
|
||||
layout.get_size()[1] / pango.SCALE < constraint_y):
|
||||
fd.set_size(int(fd.get_size()*1.2))
|
||||
|
|
@ -143,7 +128,8 @@ class Renderer:
|
|||
fd.set_size(int(fd.get_size()/1.2))
|
||||
layout.set_font_description(fd)
|
||||
|
||||
def _get_osm_logo(self, ctx, rs, height):
|
||||
@staticmethod
|
||||
def _get_osm_logo(ctx, height):
|
||||
logo_path = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), '..', 'images', 'osm-logo.png'))
|
||||
try:
|
||||
|
|
@ -164,84 +150,112 @@ class Renderer:
|
|||
ctx.restore()
|
||||
return ctx.pop_group(), png.get_width()*factor
|
||||
|
||||
def _draw_labels(self, ctx, map_area_width_dots, map_area_height_dots,
|
||||
@staticmethod
|
||||
def _draw_labels(ctx, map_grid,
|
||||
map_area_width_dots, map_area_height_dots,
|
||||
grid_legend_margin_dots):
|
||||
ctx.save()
|
||||
|
||||
step_horiz = map_area_width_dots / self.grid.horiz_count
|
||||
last_horiz_portion = math.modf(self.grid.horiz_count)[0]
|
||||
step_horiz = map_area_width_dots / map_grid.horiz_count
|
||||
last_horiz_portion = math.modf(map_grid.horiz_count)[0]
|
||||
|
||||
step_vert = map_area_height_dots / self.grid.vert_count
|
||||
last_vert_portion = math.modf(self.grid.vert_count)[0]
|
||||
step_vert = map_area_height_dots / map_grid.vert_count
|
||||
last_vert_portion = math.modf(map_grid.vert_count)[0]
|
||||
|
||||
ctx.set_font_size(min(0.75 * grid_legend_margin_dots,
|
||||
0.5 * step_horiz))
|
||||
|
||||
for i, label in enumerate(self.grid.horizontal_labels):
|
||||
for i, label in enumerate(map_grid.horizontal_labels):
|
||||
x = i * step_horiz
|
||||
|
||||
if i < len(self.grid.horizontal_labels) - 1:
|
||||
if i < len(map_grid.horizontal_labels) - 1:
|
||||
x += step_horiz/2.0
|
||||
elif last_horiz_portion >= 0.3:
|
||||
x += step_horiz * last_horiz_portion/2.0
|
||||
else:
|
||||
continue
|
||||
|
||||
self._draw_centered_text(ctx, label,
|
||||
x, grid_legend_margin_dots/2.0)
|
||||
self._draw_centered_text(ctx, label,
|
||||
x, map_area_height_dots -
|
||||
grid_legend_margin_dots/2.0)
|
||||
Renderer._draw_centered_text(ctx, label,
|
||||
x, grid_legend_margin_dots/2.0)
|
||||
Renderer._draw_centered_text(ctx, label,
|
||||
x, map_area_height_dots -
|
||||
grid_legend_margin_dots/2.0)
|
||||
|
||||
for i, label in enumerate(self.grid.vertical_labels):
|
||||
for i, label in enumerate(map_grid.vertical_labels):
|
||||
y = i * step_vert
|
||||
|
||||
if i < len(self.grid.vertical_labels) - 1:
|
||||
if i < len(map_grid.vertical_labels) - 1:
|
||||
y += step_vert/2.0
|
||||
elif last_vert_portion >= 0.3:
|
||||
y += step_vert * last_vert_portion/2.0
|
||||
else:
|
||||
continue
|
||||
|
||||
self._draw_centered_text(ctx, label,
|
||||
grid_legend_margin_dots/2.0, y)
|
||||
self._draw_centered_text(ctx, label,
|
||||
map_area_width_dots -
|
||||
grid_legend_margin_dots/2.0, y)
|
||||
Renderer._draw_centered_text(ctx, label,
|
||||
grid_legend_margin_dots/2.0, y)
|
||||
Renderer._draw_centered_text(ctx, label,
|
||||
map_area_width_dots -
|
||||
grid_legend_margin_dots/2.0, y)
|
||||
|
||||
ctx.restore()
|
||||
|
||||
def add_shade(self, shade_wkt):
|
||||
# Add the grey shade
|
||||
shade_shape = shapes.PolyShapeFile(
|
||||
self.canvas.get_actual_bounding_box(),
|
||||
def _create_map_canvas(self, graphical_ratio,
|
||||
draw_contour_shade = True):
|
||||
# Prepare the map canvas
|
||||
canvas = MapCanvas(self.rc.stylesheet,
|
||||
self.rc.bounding_box,
|
||||
graphical_ratio)
|
||||
|
||||
if draw_contour_shade:
|
||||
# Determine the shade WKT
|
||||
regexp_polygon = re.compile('^POLYGON\(\(([^)]*)\)\)$')
|
||||
matches = regexp_polygon.match(self.rc.polygon_wkt)
|
||||
if not matches:
|
||||
l.error('Administrative boundary looks invalid!')
|
||||
return None
|
||||
inside = matches.groups()[0]
|
||||
|
||||
bounding_box \
|
||||
= canvas.get_actual_bounding_box().create_expanded(0.05, 0.05)
|
||||
shade_wkt = "MULTIPOLYGON(((%s)),((%s)))" % \
|
||||
(bounding_box.as_wkt(with_polygon_statement = False), inside)
|
||||
|
||||
# Prepare the shade SHP
|
||||
shade_shape = shapes.PolyShapeFile(
|
||||
canvas.get_actual_bounding_box(),
|
||||
os.path.join(self.tmpdir, 'shade.shp'),
|
||||
'shade')
|
||||
shade_shape.add_shade_from_wkt(shade_wkt)
|
||||
self.canvas.add_shape_file(shade_shape, self.rc.stylesheet.shade_color,
|
||||
self.rc.stylesheet.shade_alpha)
|
||||
shade_shape.add_shade_from_wkt(shade_wkt)
|
||||
|
||||
# The next three methods are to be overloaded by the actual renderer.
|
||||
# Add the shade SHP to the map
|
||||
canvas.add_shape_file(shade_shape,
|
||||
self.rc.stylesheet.shade_color,
|
||||
self.rc.stylesheet.shade_alpha)
|
||||
|
||||
def create_map_canvas(self):
|
||||
"""Creates the Mapnik map canvas to the appropriate graphical ratio so
|
||||
it can be scaled and fitted into the zone on the page dedicated to the
|
||||
map."""
|
||||
raise NotImplementedError
|
||||
return canvas
|
||||
|
||||
def create_rendering_session(self, surface, street_index_renderer,
|
||||
dpi=RenderingSession.PT_PER_INCH):
|
||||
"""
|
||||
"""
|
||||
return RenderingSession(surface, street_index_renderer, dpi)
|
||||
def _create_grid(self, canvas):
|
||||
# Prepare the grid SHP
|
||||
map_grid = Grid(canvas.get_actual_bounding_box(), self.rc.rtl)
|
||||
grid_shape = map_grid.generate_shape_file(
|
||||
os.path.join(self.tmpdir, 'grid.shp'))
|
||||
|
||||
def render(self, rs):
|
||||
# Add the grid SHP to the map
|
||||
canvas.add_shape_file(grid_shape,
|
||||
self.rc.stylesheet.grid_line_color,
|
||||
self.rc.stylesheet.grid_line_alpha,
|
||||
self.rc.stylesheet.grid_line_width)
|
||||
|
||||
return map_grid
|
||||
|
||||
# The next two methods are to be overloaded by the actual renderer.
|
||||
def render(self, cairo_surface, dpi):
|
||||
"""Renders the map, the index and all other visual map features on the
|
||||
given Cairo surface.
|
||||
|
||||
Args:
|
||||
rs (RenderingSession): the rendering session (containing the
|
||||
surface, street index renderer, etc.)
|
||||
cairo_surface (Cairo.Surface): the destination Cairo device
|
||||
dpi (int): dots per inch of the device
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -275,8 +289,8 @@ class PlainRenderer(Renderer):
|
|||
name = 'plain'
|
||||
description = 'A basic, full-page layout for the map.'
|
||||
|
||||
def __init__(self, rc, tmpdir):
|
||||
Renderer.__init__(self, rc, tmpdir)
|
||||
def __init__(self, rc, tmpdir, street_index = None):
|
||||
Renderer.__init__(self, rc, tmpdir, street_index)
|
||||
|
||||
self.grid_legend_margin_pt = \
|
||||
min(Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_width_pt,
|
||||
|
|
@ -291,28 +305,17 @@ class PlainRenderer(Renderer):
|
|||
self.title_margin_pt +
|
||||
self.copyright_margin_pt))
|
||||
|
||||
def create_rendering_session(self, surface, street_index_renderer,
|
||||
dpi=RenderingSession.PT_PER_INCH):
|
||||
rs = Renderer.create_rendering_session(self, surface,
|
||||
street_index_renderer,
|
||||
dpi)
|
||||
rs.paper_width_dots = rs.pt_to_dots(self.paper_width_pt)
|
||||
rs.paper_height_dots = rs.pt_to_dots(self.paper_height_pt)
|
||||
# Prepare the map, shade and grid
|
||||
self._map_canvas = self._create_map_canvas(
|
||||
float(self.map_area_width_pt) /
|
||||
float(self.map_area_height_pt))
|
||||
self.grid = self._create_grid(self._map_canvas)
|
||||
|
||||
rs.safe_margin_dots = rs.pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT)
|
||||
rs.grid_legend_margin_dots = rs.pt_to_dots(self.grid_legend_margin_pt)
|
||||
rs.title_margin_dots = rs.pt_to_dots(self.title_margin_pt)
|
||||
rs.copyright_margin_dots = rs.pt_to_dots(self.copyright_margin_pt)
|
||||
# Prepare canvas
|
||||
self._map_canvas.render()
|
||||
|
||||
rs.map_area_width_dots = rs.pt_to_dots(self.map_area_width_pt)
|
||||
rs.map_area_height_dots = rs.pt_to_dots(self.map_area_height_pt)
|
||||
return rs
|
||||
|
||||
def create_map_canvas(self):
|
||||
self._create_map_canvas(float(self.map_area_width_pt) /
|
||||
float(self.map_area_height_pt))
|
||||
|
||||
def _draw_copyright_notice(self, ctx, rs, notice=None):
|
||||
def _draw_copyright_notice(self, ctx, page_layout, notice=None):
|
||||
today = datetime.date.today()
|
||||
notice = notice or \
|
||||
_(u'Copyright © %(year)d MapOSMatic/OCitySMap developers. '
|
||||
|
|
@ -323,12 +326,14 @@ class PlainRenderer(Renderer):
|
|||
u'You can contribute to improve this map. '
|
||||
u'See http://wiki.openstreetmap.org')
|
||||
|
||||
notice = notice % {'year': today.year, 'date': today.strftime("%d %B %Y")}
|
||||
notice = notice % {'year': today.year,
|
||||
'date': today.strftime("%d %B %Y")}
|
||||
|
||||
ctx.save()
|
||||
ctx.translate(0, rs.map_area_height_dots + 0.005 * rs.paper_height_dots)
|
||||
ctx.translate(0, (page_layout.map_area_height_dots
|
||||
+ 0.005 * page_layout.paper_height_dots))
|
||||
|
||||
width = rs.paper_width_dots - 2*rs.safe_margin_dots
|
||||
width = page_layout.paper_width_dots - 2*page_layout.safe_margin_dots
|
||||
|
||||
pc = pangocairo.CairoContext(ctx)
|
||||
fd = pango.FontDescription('DejaVu')
|
||||
|
|
@ -336,35 +341,41 @@ class PlainRenderer(Renderer):
|
|||
layout = pc.create_layout()
|
||||
layout.set_font_description(fd)
|
||||
layout.set_text(notice)
|
||||
self._adjust_font_size(layout, fd, width, rs.copyright_margin_dots)
|
||||
self._adjust_font_size(layout, fd, width,
|
||||
page_layout.copyright_margin_dots)
|
||||
pc.show_layout(layout)
|
||||
ctx.restore()
|
||||
|
||||
def _draw_title(self, ctx, rs, font_face):
|
||||
|
||||
def _draw_title(self, ctx, page_layout, font_face):
|
||||
# Title background
|
||||
ctx.save()
|
||||
ctx.set_source_rgb(0.8, 0.9, 0.96)
|
||||
ctx.rectangle(0, 0, rs.paper_width_dots - 2*rs.safe_margin_dots,
|
||||
rs.title_margin_dots)
|
||||
ctx.rectangle(0, 0,
|
||||
page_layout.paper_width_dots
|
||||
- 2*page_layout.safe_margin_dots,
|
||||
page_layout.title_margin_dots)
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
|
||||
# Retrieve and paint the OSM logo
|
||||
ctx.save()
|
||||
grp, logo_width = self._get_osm_logo(ctx, rs, 0.8*rs.title_margin_dots)
|
||||
ctx.translate(rs.paper_width_dots -
|
||||
2.0 * rs.safe_margin_dots - \
|
||||
logo_width - 0.1 * rs.title_margin_dots,
|
||||
0.1 * rs.title_margin_dots)
|
||||
grp, logo_width = self._get_osm_logo(
|
||||
ctx, 0.8*page_layout.title_margin_dots)
|
||||
|
||||
ctx.translate(page_layout.paper_width_dots -
|
||||
2.0 * page_layout.safe_margin_dots -
|
||||
logo_width - 0.1 * page_layout.title_margin_dots,
|
||||
0.1 * page_layout.title_margin_dots)
|
||||
ctx.set_source(grp)
|
||||
ctx.paint_with_alpha(0.5)
|
||||
ctx.restore()
|
||||
|
||||
pc = pangocairo.CairoContext(ctx)
|
||||
layout = pc.create_layout()
|
||||
layout.set_width(int((rs.paper_width_dots -
|
||||
2.0 * rs.safe_margin_dots -
|
||||
0.1 * rs.title_margin_dots -
|
||||
layout.set_width(int((page_layout.paper_width_dots -
|
||||
2.0 * page_layout.safe_margin_dots -
|
||||
0.1 * page_layout.title_margin_dots -
|
||||
logo_width) * pango.SCALE))
|
||||
if not self.rc.rtl: layout.set_alignment(pango.ALIGN_LEFT)
|
||||
else: layout.set_alignment(pango.ALIGN_RIGHT)
|
||||
|
|
@ -373,61 +384,87 @@ class PlainRenderer(Renderer):
|
|||
layout.set_font_description(fd)
|
||||
layout.set_text(self.rc.title)
|
||||
self._adjust_font_size(layout, fd, layout.get_width(),
|
||||
0.8 * rs.title_margin_dots)
|
||||
0.8 * page_layout.title_margin_dots)
|
||||
|
||||
ctx.save()
|
||||
ctx.rectangle(0, 0, rs.paper_width_dots - 2.0 * rs.safe_margin_dots,
|
||||
rs.title_margin_dots)
|
||||
ctx.rectangle(0, 0,
|
||||
page_layout.paper_width_dots
|
||||
- 2.0 * page_layout.safe_margin_dots,
|
||||
page_layout.title_margin_dots)
|
||||
ctx.stroke()
|
||||
ctx.translate(0.1 * rs.title_margin_dots,
|
||||
(rs.title_margin_dots -
|
||||
ctx.translate(0.1 * page_layout.title_margin_dots,
|
||||
(page_layout.title_margin_dots -
|
||||
(layout.get_size()[1] / pango.SCALE)) / 2.0)
|
||||
pc.show_layout(layout)
|
||||
ctx.restore()
|
||||
|
||||
def render(self, rs):
|
||||
def render(self, cairo_surface, dpi):
|
||||
"""
|
||||
Args:
|
||||
rs (RenderingSession): ...
|
||||
...
|
||||
"""
|
||||
|
||||
l.info('PlainRenderer rendering on %dx%dmm paper at %d dpi.' %
|
||||
(self.rc.paper_width_mm, self.rc.paper_height_mm, rs.dpi))
|
||||
(self.rc.paper_width_mm, self.rc.paper_height_mm, dpi))
|
||||
|
||||
ctx = cairo.Context(rs.surface)
|
||||
rendered_map = self.canvas.get_rendered_map()
|
||||
# First determine some useful drawing parameters
|
||||
page_layout = UTILS.create_struct(
|
||||
paper_width_dots \
|
||||
= UTILS.convert_pt_to_dots(self.paper_width_pt, dpi),
|
||||
paper_height_dots \
|
||||
= UTILS.convert_pt_to_dots(self.paper_height_pt, dpi),
|
||||
safe_margin_dots \
|
||||
= UTILS.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT, dpi),
|
||||
grid_legend_margin_dots \
|
||||
= UTILS.convert_pt_to_dots(self.grid_legend_margin_pt, dpi),
|
||||
title_margin_dots \
|
||||
= UTILS.convert_pt_to_dots(self.title_margin_pt, dpi),
|
||||
copyright_margin_dots \
|
||||
= UTILS.convert_pt_to_dots(self.copyright_margin_pt, dpi),
|
||||
map_area_width_dots \
|
||||
= UTILS.convert_pt_to_dots(self.map_area_width_pt, dpi),
|
||||
map_area_height_dots \
|
||||
= UTILS.convert_pt_to_dots(self.map_area_height_pt, dpi)
|
||||
)
|
||||
|
||||
ctx = cairo.Context(cairo_surface)
|
||||
rendered_map = self._map_canvas.get_rendered_map()
|
||||
|
||||
# Print margin
|
||||
ctx.translate(rs.safe_margin_dots, rs.safe_margin_dots)
|
||||
ctx.translate(page_layout.safe_margin_dots,
|
||||
page_layout.safe_margin_dots)
|
||||
|
||||
self._draw_title(ctx, rs, 'Georgia Bold')
|
||||
self._draw_title(ctx, page_layout, 'Georgia Bold')
|
||||
|
||||
# Skip title height
|
||||
ctx.translate(0, rs.title_margin_dots)
|
||||
ctx.translate(0, page_layout.title_margin_dots)
|
||||
|
||||
# Draw the map, scaled to fit the designated area
|
||||
ctx.save()
|
||||
ctx.scale(rs.map_area_width_dots / rendered_map.width,
|
||||
rs.map_area_height_dots / rendered_map.height)
|
||||
ctx.scale(page_layout.map_area_width_dots
|
||||
/ rendered_map.width,
|
||||
page_layout.map_area_height_dots
|
||||
/ rendered_map.height)
|
||||
mapnik.render(rendered_map, ctx)
|
||||
ctx.restore()
|
||||
|
||||
# Draw a rectangle around the map
|
||||
ctx.rectangle(0, 0, rs.map_area_width_dots, rs.map_area_height_dots)
|
||||
ctx.rectangle(0, 0, page_layout.map_area_width_dots,
|
||||
page_layout.map_area_height_dots)
|
||||
ctx.stroke()
|
||||
|
||||
# Place the vertical and horizontal square labels
|
||||
self._draw_labels(ctx,
|
||||
rs.map_area_width_dots,
|
||||
rs.map_area_height_dots,
|
||||
rs.grid_legend_margin_dots)
|
||||
self._draw_labels(ctx, self.grid,
|
||||
page_layout.map_area_width_dots,
|
||||
page_layout.map_area_height_dots,
|
||||
page_layout.grid_legend_margin_dots)
|
||||
|
||||
self._draw_copyright_notice(ctx, rs)
|
||||
self._draw_copyright_notice(ctx, page_layout)
|
||||
|
||||
# TODO: map scale
|
||||
# TODO: compass rose
|
||||
|
||||
rs.surface.flush()
|
||||
cairo_surface.flush()
|
||||
|
||||
@staticmethod
|
||||
def get_compatible_paper_sizes(bounding_box, zoom_level,
|
||||
|
|
@ -446,7 +483,7 @@ class PlainRenderer(Renderer):
|
|||
|
||||
l.debug('Map represents %dx%dm, needs at least %.1fx%.1fcm '
|
||||
'on paper.' % (geo_width_m, geo_height_m,
|
||||
paper_width_mm/10, paper_height_mm/10))
|
||||
paper_width_mm/10., paper_height_mm/10.))
|
||||
|
||||
# Test both portrait and landscape orientations when checking for paper
|
||||
# sizes.
|
||||
|
|
@ -490,7 +527,6 @@ def get_paper_sizes():
|
|||
|
||||
if __name__ == '__main__':
|
||||
import coords
|
||||
import cairo
|
||||
|
||||
# Hack to fake gettext
|
||||
try:
|
||||
|
|
@ -519,6 +555,8 @@ if __name__ == '__main__':
|
|||
self.grid_line_alpha = 0.9
|
||||
self.grid_line_width = 2
|
||||
self.zoom_level = 16
|
||||
self.shade_color = 'black'
|
||||
self.shade_alpha = 0.7
|
||||
|
||||
class RenderingConfigurationMock:
|
||||
def __init__(self):
|
||||
|
|
@ -528,17 +566,16 @@ if __name__ == '__main__':
|
|||
self.paper_height_mm = papers[0][2]
|
||||
self.rtl = False
|
||||
self.title = 'Au Kremlin-Bycêtre'
|
||||
self.polygon_wkt = bbox.as_wkt()
|
||||
|
||||
config = RenderingConfigurationMock()
|
||||
|
||||
plain = renderer_cls(config, '/tmp')
|
||||
plain = renderer_cls(config, '/tmp', None)
|
||||
surface = cairo.PDFSurface('/tmp/plain.pdf',
|
||||
Renderer.convert_mm_to_pt(config.paper_width_mm),
|
||||
Renderer.convert_mm_to_pt(config.paper_height_mm))
|
||||
UTILS.convert_mm_to_pt(config.paper_width_mm),
|
||||
UTILS.convert_mm_to_pt(config.paper_height_mm))
|
||||
|
||||
plain.create_map_canvas()
|
||||
plain.canvas.render()
|
||||
plain.render(plain.create_rendering_session(surface, None))
|
||||
plain.render(surface, UTILS.PT_PER_INCH)
|
||||
surface.finish()
|
||||
|
||||
print "Generated /tmp/plain.pdf"
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue