ocitysmap/ocitysmap2/layoutlib/multi_page_renderer.py

753 wiersze
31 KiB
Python

# -*- coding: utf-8 -*-
# ocitysmap, city map and street index generator from OpenStreetMap data
# Copyright (C) 2012 David Mentré
# Copyright (C) 2012 Thomas Petazzoni
# Copyright (C) 2012 Gaël Utard
# Copyright (C) 2012 Étienne Loks
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import logging
import tempfile
import math
import sys
import cairo
try:
import mapnik2 as mapnik
except ImportError:
import mapnik
import coords
import locale
import pangocairo
import pango
import datetime
from itertools import groupby
from abstract_renderer import Renderer
from ocitysmap2.maplib.map_canvas import MapCanvas
from ocitysmap2.maplib.grid import Grid
from ocitysmap2.maplib.overview_grid import OverviewGrid
from indexlib.indexer import StreetIndex
from indexlib.multi_page_renderer import MultiPageStreetIndexRenderer
import ocitysmap2
import commons
import shapely.wkt
from ocitysmap2 import maplib
from indexlib.commons import IndexCategory
LOG = logging.getLogger('ocitysmap')
PAGE_STR = " - Page %(page_number)d"
class MultiPageRenderer(Renderer):
"""
This Renderer creates a multi-pages map, with all the classic overlayed
features and no index page.
"""
name = 'multi_page'
description = 'A multi-page layout.'
multipages = True
def __init__(self, db, rc, tmpdir, dpi, street_index):
Renderer.__init__(self, db, rc, tmpdir, dpi, street_index)
self._grid_legend_margin_pt = \
min(Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_width_pt,
Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_height_pt)
# Compute the usable area per page
self._usable_area_width_pt = (self.paper_width_pt -
(2 * Renderer.PRINT_SAFE_MARGIN_PT))
self._usable_area_height_pt = (self.paper_height_pt -
(2 * Renderer.PRINT_SAFE_MARGIN_PT))
scale_denom = 10000
GRAYED_MARGIN_MM = 10
OVERLAP_MARGIN_MM = 20
# Debug: show original bounding box as JS code
# print self.rc.bounding_box.as_javascript("original", "#00ff00")
# Convert the original Bounding box into Mercator meters
self._proj = mapnik.Projection(coords._MAPNIK_PROJECTION)
orig_envelope = self._project_envelope(self.rc.bounding_box)
# Extend the bounding box to take into account the lost outter
# margin
off_x = orig_envelope.minx - (GRAYED_MARGIN_MM * scale_denom) / 1000
off_y = orig_envelope.miny - (GRAYED_MARGIN_MM * scale_denom) / 1000
width = orig_envelope.width() + (2 * GRAYED_MARGIN_MM * scale_denom) / 1000
height = orig_envelope.height() + (2 * GRAYED_MARGIN_MM * scale_denom) / 1000
# Calculate the total width and height of paper needed to
# render the geographical area at the current scale.
total_width_pt = commons.convert_mm_to_pt(float(width) * 1000 / scale_denom)
total_height_pt = commons.convert_mm_to_pt(float(height) * 1000 / scale_denom)
self.grayed_margin_pt = commons.convert_mm_to_pt(GRAYED_MARGIN_MM)
overlap_margin_pt = commons.convert_mm_to_pt(OVERLAP_MARGIN_MM)
# Calculate the number of pages needed in both directions
if total_width_pt < self._usable_area_width_pt:
nb_pages_width = 1
else:
nb_pages_width = \
(float(total_width_pt - self._usable_area_width_pt) / \
(self._usable_area_width_pt - overlap_margin_pt)) + 1
if total_height_pt < self._usable_area_height_pt:
nb_pages_height = 1
else:
nb_pages_height = \
(float(total_height_pt - self._usable_area_height_pt) / \
(self._usable_area_height_pt - overlap_margin_pt)) + 1
# Round up the number of pages needed so that we have integer
# number of pages
self.nb_pages_width = int(math.ceil(nb_pages_width))
self.nb_pages_height = int(math.ceil(nb_pages_height))
# Calculate the entire paper area available
total_width_pt_after_extension = self._usable_area_width_pt + \
(self._usable_area_width_pt - overlap_margin_pt) * (self.nb_pages_width - 1)
total_height_pt_after_extension = self._usable_area_height_pt + \
(self._usable_area_height_pt - overlap_margin_pt) * (self.nb_pages_height - 1)
# Convert this paper area available in the number of Mercator
# meters that can be rendered on the map
total_width_merc = \
commons.convert_pt_to_mm(total_width_pt_after_extension) * scale_denom / 1000
total_height_merc = \
commons.convert_pt_to_mm(total_height_pt_after_extension) * scale_denom / 1000
# Extend the geographical boundaries so that we completely
# fill the available paper size. We are careful to extend the
# boundaries evenly on all directions (so the center of the
# previous boundaries remain the same as the new one)
off_x -= (total_width_merc - width) / 2
width = total_width_merc
off_y -= (total_height_merc - height) / 2
height = total_height_merc
# Calculate what is the final global bounding box that we will render
envelope = mapnik.Box2d(off_x, off_y, off_x + width, off_y + height)
self._geo_bbox = self._inverse_envelope(envelope)
# Debug: show transformed bounding box as JS code
# print self._geo_bbox.as_javascript("extended", "#0f0f0f")
# Convert the usable area on each sheet of paper into the
# amount of Mercator meters we can render in this area.
usable_area_merc_m_width = commons.convert_pt_to_mm(self._usable_area_width_pt) * scale_denom / 1000
usable_area_merc_m_height = commons.convert_pt_to_mm(self._usable_area_height_pt) * scale_denom / 1000
grayed_margin_merc_m = (GRAYED_MARGIN_MM * scale_denom) / 1000
overlap_margin_merc_m = (OVERLAP_MARGIN_MM * scale_denom) / 1000
# Calculate all the bounding boxes that correspond to the
# geographical area that will be rendered on each sheet of
# paper.
area_polygon = shapely.wkt.loads(self.rc.polygon_wkt)
bboxes = []
for j in reversed(range(0, self.nb_pages_height)):
for i in range(0, self.nb_pages_width):
cur_x = off_x + i * (usable_area_merc_m_width - overlap_margin_merc_m)
cur_y = off_y + j * (usable_area_merc_m_height - overlap_margin_merc_m)
envelope = mapnik.Box2d(cur_x, cur_y,
cur_x+usable_area_merc_m_width,
cur_y+usable_area_merc_m_height)
envelope_inner = mapnik.Box2d(cur_x + grayed_margin_merc_m,
cur_y + grayed_margin_merc_m,
cur_x + usable_area_merc_m_width - grayed_margin_merc_m,
cur_y + usable_area_merc_m_height - grayed_margin_merc_m)
inner_bb = self._inverse_envelope(envelope_inner)
if not area_polygon.disjoint(shapely.wkt.loads(
inner_bb.as_wkt())):
bboxes.append((self._inverse_envelope(envelope),
inner_bb))
# Debug: show per-page bounding boxes as JS code
# for i, (bb, bb_inner) in enumerate(bboxes):
# print bb.as_javascript(name="p%d" % i)
self.pages = []
# Create an overview map
overview_bb = self._geo_bbox.create_expanded(0.001, 0.001)
# Create the overview grid
self.overview_grid = OverviewGrid(overview_bb,
[bb_inner for bb, bb_inner in bboxes], self.rc.i18n.isrtl())
grid_shape = self.overview_grid.generate_shape_file(
os.path.join(self.tmpdir, 'grid_overview.shp'))
# Create a canvas for the overview page
self.overview_canvas = MapCanvas(self.rc.stylesheet,
overview_bb, self._usable_area_width_pt,
self._usable_area_height_pt, dpi,
extend_bbox_to_ratio=True)
# Create the gray shape around the overview map
exterior = shapely.wkt.loads(self.overview_canvas.get_actual_bounding_box()\
.as_wkt())
interior = shapely.wkt.loads(self.rc.polygon_wkt)
shade_wkt = exterior.difference(interior).wkt
shade = maplib.shapes.PolyShapeFile(self.rc.bounding_box,
os.path.join(self.tmpdir, 'shape_overview.shp'),
'shade-overview')
shade.add_shade_from_wkt(shade_wkt)
self.overview_canvas.add_shape_file(shade)
self.overview_canvas.add_shape_file(grid_shape,
self.rc.stylesheet.grid_line_color, 1,
self.rc.stylesheet.grid_line_width)
self.overview_canvas.render()
# Create the map canvas for each page
indexes = []
for i, (bb, bb_inner) in enumerate(bboxes):
# Create the gray shape around the map
exterior = shapely.wkt.loads(bb.as_wkt())
interior = shapely.wkt.loads(bb_inner.as_wkt())
shade_wkt = exterior.difference(interior).wkt
shade = maplib.shapes.PolyShapeFile(
bb, os.path.join(self.tmpdir, 'shade%d.shp' % i),
'shade%d' % i)
shade.add_shade_from_wkt(shade_wkt)
# Create the contour shade
# Area to keep visible
interior_contour = shapely.wkt.loads(self.rc.polygon_wkt)
# Determine the shade WKT
shade_contour_wkt = interior.difference(interior_contour).wkt
# Prepare the shade SHP
shade_contour = maplib.shapes.PolyShapeFile(bb,
os.path.join(self.tmpdir, 'shade_contour%d.shp' % i),
'shade_contour%d' % i)
shade_contour.add_shade_from_wkt(shade_contour_wkt)
# Create the grid
map_grid = Grid(bb_inner, self.rc.i18n.isrtl())
grid_shape = map_grid.generate_shape_file(
os.path.join(self.tmpdir, 'grid%d.shp' % i))
# Create one canvas for the current page
map_canvas = MapCanvas(self.rc.stylesheet,
bb, self._usable_area_width_pt,
self._usable_area_height_pt, dpi,
extend_bbox_to_ratio=False)
map_canvas.add_shape_file(shade)
map_canvas.add_shape_file(shade_contour,
self.rc.stylesheet.shade_color_2,
self.rc.stylesheet.shade_alpha_2)
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)
map_canvas.render()
self.pages.append((map_canvas, map_grid))
# Create the index for the current page
inside_contour_wkt = interior_contour.intersection(interior).wkt
index = StreetIndex(self.db,
inside_contour_wkt,
self.rc.i18n, page_number=(i + 4))
index.apply_grid(map_grid)
indexes.append(index)
# Merge all indexes
self.index_categories = self._merge_page_indexes(indexes)
# Prepare the small map for the front page
self._front_page_map = self._prepare_front_page_map(dpi)
def _merge_page_indexes(self, indexes):
# First, we split street categories and "other" categories,
# because we sort them and we don't want to have the "other"
# categories intermixed with the street categories. This
# sorting is required for the groupby Python operator to work
# properly.
all_categories_streets = []
all_categories_others = []
for page_number, idx in enumerate(indexes):
for cat in idx.categories:
# Split in two lists depending on the category type
# (street or other)
if cat.is_street:
all_categories_streets.append(cat)
else:
all_categories_others.append(cat)
all_categories_streets_merged = \
self._merge_index_same_categories(all_categories_streets, is_street=True)
all_categories_others_merged = \
self._merge_index_same_categories(all_categories_others, is_street=False)
all_categories_merged = \
all_categories_streets_merged + all_categories_others_merged
return all_categories_merged
def _merge_index_same_categories(self, categories, is_street=True):
# Sort by categories. Now we may have several consecutive
# categories with the same name (i.e category for letter 'A'
# from page 1, category for letter 'A' from page 3).
categories.sort(key=lambda s:s.name)
categories_merged = []
for category_name,grouped_categories in groupby(categories,
key=lambda s:s.name):
# Group the different IndexItem from categories having the
# same name. The groupby() function guarantees us that
# categories with the same name are grouped together in
# grouped_categories[].
grouped_items = []
for cat in grouped_categories:
grouped_items.extend(cat.items)
# Re-sort alphabetically all the IndexItem according to
# the street name.
prev_locale = locale.getlocale(locale.LC_COLLATE)
locale.setlocale(locale.LC_COLLATE, self.rc.i18n.language_code())
try:
grouped_items_sorted = \
sorted(grouped_items,
lambda x,y: locale.strcoll(x.label, y.label))
finally:
locale.setlocale(locale.LC_COLLATE, prev_locale)
self._blank_duplicated_names(grouped_items_sorted)
# Rebuild a IndexCategory object with the list of merged
# and sorted IndexItem
categories_merged.append(
IndexCategory(category_name, grouped_items_sorted, is_street))
return categories_merged
# We set the label to empty string in case of duplicated item. In
# multi-page renderer we won't draw the dots in that case
def _blank_duplicated_names(self, grouped_items_sorted):
prev_label = ''
for item in grouped_items_sorted:
if prev_label == item.label:
item.label = ''
else:
prev_label = item.label
def _project_envelope(self, bbox):
"""Project the given bounding box into the rendering projection."""
envelope = mapnik.Box2d(bbox.get_top_left()[1],
bbox.get_top_left()[0],
bbox.get_bottom_right()[1],
bbox.get_bottom_right()[0])
c0 = self._proj.forward(mapnik.Coord(envelope.minx, envelope.miny))
c1 = self._proj.forward(mapnik.Coord(envelope.maxx, envelope.maxy))
return mapnik.Box2d(c0.x, c0.y, c1.x, c1.y)
def _inverse_envelope(self, envelope):
"""Inverse the given cartesian envelope (in 900913) back to a 4002
bounding box."""
c0 = self._proj.inverse(mapnik.Coord(envelope.minx, envelope.miny))
c1 = self._proj.inverse(mapnik.Coord(envelope.maxx, envelope.maxy))
return coords.BoundingBox(c0.y, c0.x, c1.y, c1.x)
def _prepare_front_page_map(self, dpi):
front_page_map_w = \
self._usable_area_width_pt - 2 * Renderer.PRINT_SAFE_MARGIN_PT
front_page_map_h = \
(self._usable_area_height_pt - 2 * Renderer.PRINT_SAFE_MARGIN_PT) / 2
# Create the nice small map
front_page_map = \
MapCanvas(self.rc.stylesheet,
self.rc.bounding_box,
front_page_map_w,
front_page_map_h,
dpi,
extend_bbox_to_ratio=True)
# Add the shape that greys out everything that is outside of
# the administrative boundary.
exterior = shapely.wkt.loads(front_page_map.get_actual_bounding_box().as_wkt())
interior = shapely.wkt.loads(self.rc.polygon_wkt)
shade_wkt = exterior.difference(interior).wkt
shade = maplib.shapes.PolyShapeFile(self.rc.bounding_box,
os.path.join(self.tmpdir, 'shape_overview_cover.shp'),
'shade-overview-cover')
shade.add_shade_from_wkt(shade_wkt)
front_page_map.add_shape_file(shade)
front_page_map.render()
return front_page_map
def _render_front_page_header(self, ctx, w, h):
# Draw a light blue block which will contain the name of the
# city being rendered.
blue_w = w
blue_h = 0.3 * h
ctx.set_source_rgb(.80,.80,.80)
ctx.rectangle(0, 0, blue_w, blue_h)
ctx.fill()
# Prepare the title text layout
pc = pangocairo.CairoContext(ctx)
layout = pc.create_layout()
layout.set_width(int(0.7 * w * pango.SCALE))
layout.set_alignment(pango.ALIGN_CENTER)
fd = pango.FontDescription("Georgia Bold")
fd.set_size(pango.SCALE)
layout.set_font_description(fd)
layout.set_text(self.rc.title)
self._adjust_font_size(layout, fd, 0.7 * blue_w, 0.8 * blue_h)
# Draw the title
text_x, text_y, text_w, text_h = layout.get_extents()[1]
ctx.save()
ctx.set_source_rgb(0, 0, 0)
ctx.translate((blue_w / 2) - (text_w / 2.0) / pango.SCALE - text_x / pango.SCALE,
(blue_h / 2) - (text_h / 2.0) / pango.SCALE - text_y / pango.SCALE)
pc.show_layout(layout)
ctx.restore()
def _render_front_page_map(self, ctx, dpi, w, h):
# We will render the map slightly below the title
ctx.save()
ctx.translate(0, 0.3 * h + Renderer.PRINT_SAFE_MARGIN_PT)
# Render the map !
mapnik.render(self._front_page_map.get_rendered_map(), ctx)
ctx.restore()
def _render_front_page_footer(self, ctx, w, h, osm_date):
ctx.save()
# Draw the footer
ctx.translate(0, 0.8 * h + 2 * Renderer.PRINT_SAFE_MARGIN_PT)
# Display a nice grey rectangle as the background of the
# footer
footer_w = w
footer_h = 0.2 * h - 2 * Renderer.PRINT_SAFE_MARGIN_PT
ctx.set_source_rgb(.80,.80,.80)
ctx.rectangle(0, 0, footer_w, footer_h)
ctx.fill()
# Draw the OpenStreetMap logo to the right of the footer
logo_height = footer_h / 2
grp, logo_width = self._get_osm_logo(ctx, logo_height)
if grp:
ctx.save()
ctx.translate(w - logo_width - Renderer.PRINT_SAFE_MARGIN_PT,
logo_height / 2)
ctx.set_source(grp)
ctx.paint_with_alpha(0.8)
ctx.restore()
# Prepare the text for the left of the footer
today = datetime.date.today()
notice = \
_(u'Copyright © %(year)d MapOSMatic/OCitySMap developers.\n'
u'http://www.maposmatic.org\n\n'
u'Map data © %(year)d OpenStreetMap.org '
u'and contributors (cc-by-sa).\n'
u'http://www.openstreetmap.org\n\n'
u'Map rendered on: %(date)s. OSM data updated on: %(osmdate)s.\n'
u'The map may be incomplete or inaccurate. '
u'You can contribute to improve this map.\n'
u'See http://wiki.openstreetmap.org')
# We need the correct locale to be set for strftime().
prev_locale = locale.getlocale(locale.LC_TIME)
locale.setlocale(locale.LC_TIME, self.rc.i18n.language_code())
try:
if osm_date is None:
osm_date_str = _(u'unknown')
else:
osm_date_str = osm_date.strftime("%d %B %Y %H:%M")
notice = notice % {'year': today.year,
'date': today.strftime("%d %B %Y"),
'osmdate': osm_date_str}
finally:
locale.setlocale(locale.LC_TIME, prev_locale)
# Render the text
pc = pangocairo.CairoContext(ctx)
layout = pc.create_layout()
layout.set_width(int(footer_w * 0.7) * pango.SCALE)
layout.set_alignment(pango.ALIGN_LEFT)
fd = pango.FontDescription("Georgia Bold")
fd.set_size(pango.SCALE)
layout.set_font_description(fd)
layout.set_text(notice)
self._adjust_font_size(layout, fd, footer_w * 0.7, footer_h * 0.8)
text_x, text_y, text_w, text_h = layout.get_extents()[1]
ctx.save()
ctx.set_source_rgb(0,0,0)
ctx.translate(Renderer.PRINT_SAFE_MARGIN_PT,
(footer_h / 2) - (text_h / 2.0 / pango.SCALE))
# Hack to workaround what appears to be a Cairo bug: without
# drawing a rectangle here, the translation above is not taken
# into account for rendering the text.
ctx.rectangle(0, 0, 0, 0)
pc.show_layout(layout)
ctx.restore()
ctx.restore()
def _render_front_page(self, ctx, cairo_surface, dpi, osm_date):
# Draw a nice grey rectangle covering the whole page
ctx.save()
ctx.set_source_rgb(.95,.95,.95)
ctx.rectangle(Renderer.PRINT_SAFE_MARGIN_PT,
Renderer.PRINT_SAFE_MARGIN_PT,
self._usable_area_width_pt,
self._usable_area_height_pt)
ctx.fill()
ctx.restore()
# Translate into the working area, taking another
# PRINT_SAFE_MARGIN_PT inside the grey area.
ctx.save()
ctx.translate(2 * Renderer.PRINT_SAFE_MARGIN_PT,
2 * Renderer.PRINT_SAFE_MARGIN_PT)
w = self._usable_area_width_pt - 2 * Renderer.PRINT_SAFE_MARGIN_PT
h = self._usable_area_height_pt - 2 * Renderer.PRINT_SAFE_MARGIN_PT
self._render_front_page_header(ctx, w, h)
self._render_front_page_map(ctx, dpi, w, h)
self._render_front_page_footer(ctx, w, h, osm_date)
ctx.restore()
cairo_surface.show_page()
def _render_blank_page(self, ctx, cairo_surface, dpi):
"""
Render a blank page with a nice "intentionally blank" notice
"""
ctx.save()
ctx.translate(
commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT),
commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT))
# footer notice
w = self._usable_area_width_pt
h = self._usable_area_height_pt
ctx.set_source_rgb(.6,.6,.6)
Renderer._draw_centered_text(ctx, _('This page is intentionally left '\
'blank.'), w/2.0, 0.95*h)
self._render_page_number(ctx, 2)
cairo_surface.show_page()
ctx.restore()
def _render_page_number(self, ctx, page_number):
"""
Render page number
"""
ctx.save()
x_offset = 0
if page_number % 2:
x_offset += commons.convert_pt_to_dots(self._usable_area_width_pt)\
- commons.convert_pt_to_dots(self.grayed_margin_pt)
y_offset = commons.convert_pt_to_dots(self._usable_area_height_pt)\
- commons.convert_pt_to_dots(self.grayed_margin_pt)
ctx.translate(x_offset, y_offset)
ctx.set_source_rgba(1, 1, 1, 0.6)
ctx.rectangle(0, 0, commons.convert_pt_to_dots(self.grayed_margin_pt),
commons.convert_pt_to_dots(self.grayed_margin_pt))
ctx.fill()
ctx.set_source_rgba(0, 0, 0, 1)
x_offset = commons.convert_pt_to_dots(self.grayed_margin_pt)/2
y_offset = commons.convert_pt_to_dots(self.grayed_margin_pt)/2
ctx.translate(x_offset, y_offset)
Renderer._draw_centered_text(ctx, unicode(page_number), 0, 0)
ctx.restore()
def _render_overview_page(self, ctx, cairo_surface, dpi):
rendered_map = self.overview_canvas.get_rendered_map()
mapnik.render(rendered_map, ctx)
# draw pages numbers
self._draw_overview_labels(ctx, self.overview_canvas, self.overview_grid,
commons.convert_pt_to_dots(self._usable_area_width_pt),
commons.convert_pt_to_dots(self._usable_area_height_pt))
# Render the page number
self._render_page_number(ctx, 3)
cairo_surface.show_page()
def render(self, cairo_surface, dpi, osm_date):
ctx = cairo.Context(cairo_surface)
self._render_front_page(ctx, cairo_surface, dpi, osm_date)
self._render_blank_page(ctx, cairo_surface, dpi)
ctx.save()
# Prepare to draw the map at the right location
ctx.translate(
commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT),
commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT))
self._render_overview_page(ctx, cairo_surface, dpi)
for i, (canvas, grid) in enumerate(self.pages):
rendered_map = canvas.get_rendered_map()
mapnik.render(rendered_map, ctx)
# Place the vertical and horizontal square labels
ctx.save()
ctx.translate(commons.convert_pt_to_dots(self.grayed_margin_pt),
commons.convert_pt_to_dots(self.grayed_margin_pt))
self._draw_labels(ctx, grid,
commons.convert_pt_to_dots(self._usable_area_width_pt) \
- 2 * commons.convert_pt_to_dots(self.grayed_margin_pt),
commons.convert_pt_to_dots(self._usable_area_height_pt) \
- 2 * commons.convert_pt_to_dots(self.grayed_margin_pt),
commons.convert_pt_to_dots(self._grid_legend_margin_pt))
ctx.restore()
# Render the page number
self._render_page_number(ctx, i+4)
#self._render_neighbour_arrows()
cairo_surface.show_page()
ctx.restore()
mpsir = MultiPageStreetIndexRenderer(self.rc.i18n,
ctx, cairo_surface,
self.index_categories,
(Renderer.PRINT_SAFE_MARGIN_PT,
Renderer.PRINT_SAFE_MARGIN_PT,
self._usable_area_width_pt,
self._usable_area_height_pt))
mpsir.render()
cairo_surface.flush()
# Convert a length in geometric meters (in the real life) into a
# length in paper millimiters (as drawn on the map).
def _geo_m_to_paper_mm(self, geo_m):
return geo_m / 1000.0 * Renderer.DEFAULT_KM_IN_MM * 2
def _paper_mm_to_geo_m(self, paper_mm):
return paper_mm * 1000.0 / (Renderer.DEFAULT_KM_IN_MM * 2)
def _paper_pt_to_geo_m(self, paper_pt):
return self._paper_mm_to_geo_m(commons.convert_pt_to_mm(paper_pt))
# In multi-page mode, we only accept A4, A5 and US letter as paper
# sizes. The goal is to render booklets, not posters.
@staticmethod
def get_compatible_paper_sizes(bounding_box, zoom_level,
resolution_km_in_mm=Renderer.DEFAULT_KM_IN_MM,
index_position=None, hsplit=1, vsplit=1):
valid_sizes = []
acceptable_formats = [ 'A5', 'A4', 'US letter' ]
for sz in ocitysmap2.layoutlib.PAPER_SIZES:
# Skip unsupported paper formats
if sz[0] not in acceptable_formats:
continue
valid_sizes.append((sz[0], sz[1], sz[2], True, True))
return valid_sizes
@classmethod
def _draw_overview_labels(cls, ctx, map_canvas, overview_grid,
area_width_dots, area_height_dots):
"""
Draw the page numbers for the overview grid.
Args:
ctx (cairo.Context): The cairo context to use to draw.
overview_grid (OverViewGrid): the overview grid object
area_width_dots/area_height_dots (numbers): size of the
drawing area (cairo units).
"""
ctx.save()
ctx.set_font_size(14)
bbox = map_canvas.get_actual_bounding_box()
bottom_right, bottom_left, top_left, top_right = bbox.to_mercator()
bottom, left = bottom_right.y, top_left.x
coord_delta_y = top_left.y - bottom_right.y
coord_delta_x = bottom_right.x - top_left.x
w, h = None, None
for idx, page_bb in enumerate(overview_grid._pages_bbox):
p_bottom_right, p_bottom_left, p_top_left, p_top_right = \
page_bb.to_mercator()
center_x = p_top_left.x+(p_top_right.x-p_top_left.x)/2
center_y = p_bottom_left.y+(p_top_right.y-p_bottom_right.y)/2
y_percent = 100 - 100.0*(center_y - bottom)/coord_delta_y
y = int(area_height_dots*y_percent/100)
x_percent = 100.0*(center_x - left)/coord_delta_x
x = int(area_width_dots*x_percent/100)
if not w or not h:
w = area_width_dots*(p_bottom_right.x - p_bottom_left.x
)/coord_delta_x
h = area_height_dots*(p_top_right.y - p_bottom_right.y
)/coord_delta_y
# Prepare the number text layout
pc = pangocairo.CairoContext(ctx)
layout = pc.create_layout()
layout.set_width(int(0.7 * w * pango.SCALE))
layout.set_alignment(pango.ALIGN_CENTER)
fd = pango.FontDescription("Georgia Bold")
fd.set_size(pango.SCALE)
layout.set_font_description(fd)
# adjust size with the last page number
layout.set_text('0'*len(unicode(len(overview_grid._pages_bbox)+3)))
cls._adjust_font_size(layout, fd, 0.65 * w, 0.8 * h)
# set the real text
layout.set_text(unicode(idx+4))
# draw
text_x, text_y, text_w, text_h = layout.get_extents()[1]
ctx.save()
ctx.set_source_rgba(0, 0, 0, 0.6)
ctx.translate(x - (text_w/2.0)/pango.SCALE - text_x/pango.SCALE,
y - (text_h/2.0)/pango.SCALE - text_y/pango.SCALE)
pc.show_layout(layout)
ctx.restore()
ctx.restore()