kopia lustrzana https://github.com/hholzgra/ocitysmap
693 wiersze
26 KiB
Python
693 wiersze
26 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# ocitysmap, city map and street index generator from OpenStreetMap data
|
|
# Copyright (C) 2010 David Decotigny
|
|
# Copyright (C) 2010 Frédéric Lehobey
|
|
# Copyright (C) 2010 Pierre Mauduit
|
|
# Copyright (C) 2010 David Mentré
|
|
# Copyright (C) 2010 Maxime Petazzoni
|
|
# Copyright (C) 2010 Thomas Petazzoni
|
|
# Copyright (C) 2010 Gaël Utard
|
|
|
|
# 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 math
|
|
import datetime
|
|
import cairo
|
|
import locale
|
|
import mapnik
|
|
assert mapnik.mapnik_version >= 200100, \
|
|
"Mapnik module version %s is too old, see ocitysmap's INSTALL " \
|
|
"for more details." % mapnik.mapnik_version_string()
|
|
import pango
|
|
import pangocairo
|
|
|
|
import commons
|
|
import ocitysmap2
|
|
from abstract_renderer import Renderer
|
|
from ocitysmap2.indexlib.renderer import StreetIndexRenderer
|
|
|
|
import logging
|
|
|
|
from indexlib.indexer import StreetIndex
|
|
from indexlib.commons import IndexDoesNotFitError, IndexEmptyError
|
|
import draw_utils
|
|
|
|
LOG = logging.getLogger('ocitysmap')
|
|
|
|
|
|
class SinglePageRenderer(Renderer):
|
|
"""
|
|
This Renderer creates a full-page map, with the overlayed features
|
|
like the grid, grid labels, scale and compass rose and can draw an
|
|
index.
|
|
"""
|
|
|
|
name = 'generic_single_page'
|
|
description = 'A generic full-page layout with or without index.'
|
|
|
|
MAX_INDEX_OCCUPATION_RATIO = 1/3.
|
|
|
|
def __init__(self, db, rc, tmpdir, dpi, file_prefix,
|
|
index_position = 'side'):
|
|
"""
|
|
Create the renderer.
|
|
|
|
Args:
|
|
rc (RenderingConfiguration): rendering parameters.
|
|
tmpdir (os.path): Path to a temp dir that can hold temp files.
|
|
index_position (str): None or 'side' (index on side),
|
|
'bottom' (index at bottom).
|
|
"""
|
|
Renderer.__init__(self, db, rc, tmpdir, dpi)
|
|
|
|
# Prepare the index
|
|
self.street_index = StreetIndex(db,
|
|
rc.polygon_wkt,
|
|
rc.i18n)
|
|
if not self.street_index.categories:
|
|
LOG.warning("Designated area leads to an empty index")
|
|
self.street_index = None
|
|
|
|
self._grid_legend_margin_pt = \
|
|
min(Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_width_pt,
|
|
Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_height_pt)
|
|
self._title_margin_pt = 0.05 * self.paper_height_pt
|
|
self._copyright_margin_pt = 0.02 * self.paper_height_pt
|
|
|
|
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 +
|
|
self._title_margin_pt +
|
|
self._copyright_margin_pt))
|
|
|
|
# Prepare the Index (may raise a IndexDoesNotFitError)
|
|
if ( index_position and self.street_index
|
|
and self.street_index.categories ):
|
|
self._index_renderer, self._index_area \
|
|
= self._create_index_rendering(index_position == "side")
|
|
else:
|
|
self._index_renderer, self._index_area = None, None
|
|
|
|
# Prepare the layout of the whole page
|
|
if not self._index_area:
|
|
# No index displayed
|
|
self._map_coords = ( Renderer.PRINT_SAFE_MARGIN_PT,
|
|
( Renderer.PRINT_SAFE_MARGIN_PT
|
|
+ self._title_margin_pt ),
|
|
self._usable_area_width_pt,
|
|
self._usable_area_height_pt )
|
|
elif index_position == 'side':
|
|
# Index present, displayed on the side
|
|
if self._index_area.x > Renderer.PRINT_SAFE_MARGIN_PT:
|
|
# Index on the right -> map on the left
|
|
self._map_coords = ( Renderer.PRINT_SAFE_MARGIN_PT,
|
|
( Renderer.PRINT_SAFE_MARGIN_PT
|
|
+ self._title_margin_pt ),
|
|
( self._usable_area_width_pt
|
|
- self._index_area.w ),
|
|
self._usable_area_height_pt )
|
|
else:
|
|
# Index on the left -> map on the right
|
|
self._map_coords = ( self._index_area.x + self._index_area.w,
|
|
( Renderer.PRINT_SAFE_MARGIN_PT
|
|
+ self._title_margin_pt ),
|
|
( self._usable_area_width_pt
|
|
- self._index_area.w ),
|
|
self._usable_area_height_pt )
|
|
elif index_position == 'bottom':
|
|
# Index present, displayed at the bottom -> map on top
|
|
self._map_coords = ( Renderer.PRINT_SAFE_MARGIN_PT,
|
|
( Renderer.PRINT_SAFE_MARGIN_PT
|
|
+ self._title_margin_pt ),
|
|
self._usable_area_width_pt,
|
|
( self._usable_area_height_pt
|
|
- self._index_area.h ) )
|
|
else:
|
|
raise AssertionError("Invalid index position %s"
|
|
% repr(index_position))
|
|
|
|
# Prepare the map
|
|
self._map_canvas = self._create_map_canvas(
|
|
float(self._map_coords[2]), # W
|
|
float(self._map_coords[3]), # H
|
|
dpi )
|
|
|
|
# Prepare the grid
|
|
self.grid = self._create_grid(self._map_canvas)
|
|
|
|
# Update the street_index to reflect the grid's actual position
|
|
if self.grid and self.street_index:
|
|
self.street_index.apply_grid(self.grid)
|
|
|
|
# Dump the CSV street index
|
|
if self.street_index:
|
|
self.street_index.write_to_csv(rc.title, '%s.csv' % file_prefix)
|
|
|
|
# Commit the internal rendering stack of the map
|
|
self._map_canvas.render()
|
|
|
|
|
|
def _create_index_rendering(self, on_the_side):
|
|
"""
|
|
Prepare to render the Street index.
|
|
|
|
Args:
|
|
on_the_side (bool): True=index on the side, False=at bottom.
|
|
|
|
Return a couple (StreetIndexRenderer, StreetIndexRenderingArea).
|
|
"""
|
|
# Now we determine the actual occupation of the index
|
|
index_renderer = StreetIndexRenderer(self.rc.i18n,
|
|
self.street_index.categories)
|
|
|
|
# We use a fake vector device to determine the actual
|
|
# rendering characteristics
|
|
fake_surface = cairo.PDFSurface(None,
|
|
self.paper_width_pt,
|
|
self.paper_height_pt)
|
|
|
|
if on_the_side:
|
|
index_max_width_pt \
|
|
= self.MAX_INDEX_OCCUPATION_RATIO * self._usable_area_width_pt
|
|
|
|
if not self.rc.i18n.isrtl():
|
|
# non-RTL: Index is on the right
|
|
index_area = index_renderer.precompute_occupation_area(
|
|
fake_surface,
|
|
( self.paper_width_pt - Renderer.PRINT_SAFE_MARGIN_PT
|
|
- index_max_width_pt ),
|
|
( Renderer.PRINT_SAFE_MARGIN_PT + self._title_margin_pt ),
|
|
index_max_width_pt,
|
|
self._usable_area_height_pt,
|
|
'width', 'right')
|
|
else:
|
|
# RTL: Index is on the left
|
|
index_area = index_renderer.precompute_occupation_area(
|
|
fake_surface,
|
|
Renderer.PRINT_SAFE_MARGIN_PT,
|
|
( Renderer.PRINT_SAFE_MARGIN_PT + self._title_margin_pt ),
|
|
index_max_width_pt,
|
|
self._usable_area_height_pt,
|
|
'width', 'left')
|
|
else:
|
|
# Index at the bottom of the page
|
|
index_max_height_pt \
|
|
= self.MAX_INDEX_OCCUPATION_RATIO * self._usable_area_height_pt
|
|
|
|
index_area = index_renderer.precompute_occupation_area(
|
|
fake_surface,
|
|
Renderer.PRINT_SAFE_MARGIN_PT,
|
|
( self.paper_height_pt
|
|
- Renderer.PRINT_SAFE_MARGIN_PT
|
|
- self._copyright_margin_pt
|
|
- index_max_height_pt ),
|
|
self._usable_area_width_pt,
|
|
index_max_height_pt,
|
|
'height', 'bottom')
|
|
|
|
return index_renderer, index_area
|
|
|
|
|
|
def _draw_title(self, ctx, w_dots, h_dots, font_face):
|
|
"""
|
|
Draw the title at the current position inside a
|
|
w_dots*h_dots rectangle.
|
|
|
|
Args:
|
|
ctx (cairo.Context): The Cairo context to use to draw.
|
|
w_dots,h_dots (number): Rectangle dimension (ciaro units)
|
|
font_face (str): Pango font specification.
|
|
"""
|
|
|
|
# Title background
|
|
ctx.save()
|
|
ctx.set_source_rgb(0.8, 0.9, 0.96)
|
|
ctx.rectangle(0, 0, w_dots, h_dots)
|
|
ctx.fill()
|
|
ctx.restore()
|
|
|
|
# Retrieve and paint the OSM logo
|
|
ctx.save()
|
|
grp, logo_width = self._get_osm_logo(ctx, 0.8*h_dots)
|
|
if grp:
|
|
ctx.translate(w_dots - logo_width - 0.1*h_dots, 0.1*h_dots)
|
|
ctx.set_source(grp)
|
|
ctx.paint_with_alpha(0.5)
|
|
else:
|
|
LOG.warning("OSM Logo not available.")
|
|
logo_width = 0
|
|
ctx.restore()
|
|
|
|
# Prepare the title
|
|
pc = pangocairo.CairoContext(ctx)
|
|
layout = pc.create_layout()
|
|
layout.set_width(int((w_dots - 0.1*w_dots - logo_width) * pango.SCALE))
|
|
if not self.rc.i18n.isrtl(): layout.set_alignment(pango.ALIGN_LEFT)
|
|
else: layout.set_alignment(pango.ALIGN_RIGHT)
|
|
fd = pango.FontDescription(font_face)
|
|
fd.set_size(pango.SCALE)
|
|
layout.set_font_description(fd)
|
|
layout.set_text(self.rc.title)
|
|
draw_utils.adjust_font_size(layout, fd, layout.get_width(), 0.8*h_dots)
|
|
|
|
# Draw the title
|
|
ctx.save()
|
|
ctx.rectangle(0, 0, w_dots, h_dots)
|
|
ctx.stroke()
|
|
ctx.translate(0.1*h_dots,
|
|
(h_dots -
|
|
(layout.get_size()[1] / pango.SCALE)) / 2.0)
|
|
pc.show_layout(layout)
|
|
ctx.restore()
|
|
|
|
|
|
def _draw_copyright_notice(self, ctx, w_dots, h_dots, notice=None,
|
|
osm_date=None):
|
|
"""
|
|
Draw a copyright notice at current location and within the
|
|
given w_dots*h_dots rectangle.
|
|
|
|
Args:
|
|
ctx (cairo.Context): The Cairo context to use to draw.
|
|
w_dots,h_dots (number): Rectangle dimension (ciaro units).
|
|
font_face (str): Pango font specification.
|
|
notice (str): Optional notice to replace the default.
|
|
"""
|
|
|
|
today = datetime.date.today()
|
|
notice = notice or \
|
|
_(u'Copyright © %(year)d MapOSMatic/OCitySMap developers. '
|
|
u'Map data © %(year)d OpenStreetMap.org '
|
|
u'and contributors (cc-by-sa).\n'
|
|
u'Map rendered on: %(date)s. OSM data updated on: %(osmdate)s. '
|
|
u'The map may be incomplete or inaccurate. '
|
|
u'You can contribute to improve this map. '
|
|
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)
|
|
|
|
ctx.save()
|
|
pc = pangocairo.CairoContext(ctx)
|
|
fd = pango.FontDescription('DejaVu')
|
|
fd.set_size(pango.SCALE)
|
|
layout = pc.create_layout()
|
|
layout.set_font_description(fd)
|
|
layout.set_text(notice)
|
|
draw_utils.adjust_font_size(layout, fd, w_dots, h_dots)
|
|
pc.show_layout(layout)
|
|
ctx.restore()
|
|
|
|
|
|
def render(self, cairo_surface, dpi, osm_date):
|
|
"""Renders the map, the index and all other visual map features on the
|
|
given Cairo surface.
|
|
|
|
Args:
|
|
cairo_surface (Cairo.Surface): the destination Cairo device.
|
|
dpi (int): dots per inch of the device.
|
|
"""
|
|
LOG.info('SinglePageRenderer rendering on %dx%dmm paper at %d dpi.' %
|
|
(self.rc.paper_width_mm, self.rc.paper_height_mm, dpi))
|
|
|
|
# First determine some useful drawing parameters
|
|
safe_margin_dots \
|
|
= commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT, dpi)
|
|
usable_area_width_dots \
|
|
= commons.convert_pt_to_dots(self._usable_area_width_pt, dpi)
|
|
usable_area_height_dots \
|
|
= commons.convert_pt_to_dots(self._usable_area_height_pt, dpi)
|
|
|
|
title_margin_dots \
|
|
= commons.convert_pt_to_dots(self._title_margin_pt, dpi)
|
|
|
|
copyright_margin_dots \
|
|
= commons.convert_pt_to_dots(self._copyright_margin_pt, dpi)
|
|
|
|
map_coords_dots = map(lambda l: commons.convert_pt_to_dots(l, dpi),
|
|
self._map_coords)
|
|
|
|
ctx = cairo.Context(cairo_surface)
|
|
|
|
# Set a white background
|
|
ctx.save()
|
|
ctx.set_source_rgb(1, 1, 1)
|
|
ctx.rectangle(0, 0, commons.convert_pt_to_dots(self.paper_width_pt, dpi),
|
|
commons.convert_pt_to_dots(self.paper_height_pt, dpi))
|
|
ctx.fill()
|
|
ctx.restore()
|
|
|
|
##
|
|
## Draw the index, when applicable
|
|
##
|
|
if self._index_renderer and self._index_area:
|
|
ctx.save()
|
|
|
|
# NEVER use ctx.scale() here because otherwise pango will
|
|
# choose different dont metrics which may be incompatible
|
|
# with what has been computed by __init__(), which may
|
|
# require more columns than expected ! Instead, we have
|
|
# to trick pangocairo into believing it is rendering to a
|
|
# device with the same default resolution, but with a
|
|
# cairo resolution matching the 'dpi' specified
|
|
# resolution. See
|
|
# index::render::StreetIndexRenederer::render() and
|
|
# comments within.
|
|
|
|
self._index_renderer.render(ctx, self._index_area, dpi)
|
|
|
|
ctx.restore()
|
|
|
|
# Also draw a rectangle
|
|
ctx.save()
|
|
ctx.rectangle(commons.convert_pt_to_dots(self._index_area.x, dpi),
|
|
commons.convert_pt_to_dots(self._index_area.y, dpi),
|
|
commons.convert_pt_to_dots(self._index_area.w, dpi),
|
|
commons.convert_pt_to_dots(self._index_area.h, dpi))
|
|
ctx.stroke()
|
|
ctx.restore()
|
|
|
|
|
|
##
|
|
## Draw the map, scaled to fit the designated area
|
|
##
|
|
ctx.save()
|
|
|
|
# Prepare to draw the map at the right location
|
|
ctx.translate(map_coords_dots[0], map_coords_dots[1])
|
|
|
|
# Draw the rescaled Map
|
|
ctx.save()
|
|
rendered_map = self._map_canvas.get_rendered_map()
|
|
LOG.debug('Mapnik scale: 1/%f' % rendered_map.scale_denominator())
|
|
LOG.debug('Actual scale: 1/%f' % self._map_canvas.get_actual_scale())
|
|
mapnik.render(rendered_map, ctx)
|
|
ctx.restore()
|
|
|
|
# Draw a rectangle around the map
|
|
ctx.rectangle(0, 0, map_coords_dots[2], map_coords_dots[3])
|
|
ctx.stroke()
|
|
|
|
# Place the vertical and horizontal square labels
|
|
self._draw_labels(ctx, self.grid,
|
|
map_coords_dots[2],
|
|
map_coords_dots[3],
|
|
commons.convert_pt_to_dots(self._grid_legend_margin_pt,
|
|
dpi))
|
|
ctx.restore()
|
|
|
|
##
|
|
## Draw the title
|
|
##
|
|
ctx.save()
|
|
ctx.translate(safe_margin_dots, safe_margin_dots)
|
|
self._draw_title(ctx, usable_area_width_dots,
|
|
title_margin_dots, 'Georgia Bold')
|
|
ctx.restore()
|
|
|
|
##
|
|
## Draw the copyright notice
|
|
##
|
|
ctx.save()
|
|
|
|
# Move to the right position
|
|
ctx.translate(safe_margin_dots,
|
|
( safe_margin_dots + title_margin_dots
|
|
+ usable_area_height_dots
|
|
+ copyright_margin_dots/4. ) )
|
|
|
|
# Draw the copyright notice
|
|
self._draw_copyright_notice(ctx, usable_area_width_dots,
|
|
copyright_margin_dots,
|
|
osm_date=osm_date)
|
|
ctx.restore()
|
|
|
|
# TODO: map scale
|
|
# TODO: compass rose
|
|
|
|
cairo_surface.flush()
|
|
|
|
@staticmethod
|
|
def _generic_get_compatible_paper_sizes(bounding_box,
|
|
scale=Renderer.DEFAULT_SCALE, index_position = None):
|
|
"""Returns a list of the compatible paper sizes for the given bounding
|
|
box. The list is sorted, smaller papers first, and a "custom" paper
|
|
matching the dimensions of the bounding box is added at the end.
|
|
|
|
Args:
|
|
bounding_box (coords.BoundingBox): the map geographic bounding box.
|
|
scale (int): minimum mapnik scale of the map.
|
|
index_position (str): None or 'side' (index on side),
|
|
'bottom' (index at bottom).
|
|
|
|
Returns a list of tuples (paper name, width in mm, height in
|
|
mm, portrait_ok, landscape_ok, is_default). Paper sizes are
|
|
represented in portrait mode.
|
|
"""
|
|
|
|
# the mapnik scale depends on the latitude
|
|
lat = bounding_box.get_top_left()[0]
|
|
scale *= math.cos(math.radians(lat))
|
|
|
|
# by convention, mapnik uses 90 ppi whereas cairo uses 72 ppi
|
|
scale *= float(72) / 90
|
|
|
|
geo_height_m, geo_width_m = bounding_box.spheric_sizes()
|
|
paper_width_mm = geo_width_m * 1000 / scale
|
|
paper_height_mm = geo_height_m * 1000 / scale
|
|
|
|
LOG.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.))
|
|
|
|
# Take index into account, when applicable
|
|
if index_position == 'side':
|
|
paper_width_mm /= (1. -
|
|
SinglePageRenderer.MAX_INDEX_OCCUPATION_RATIO)
|
|
elif index_position == 'bottom':
|
|
paper_height_mm /= (1. -
|
|
SinglePageRenderer.MAX_INDEX_OCCUPATION_RATIO)
|
|
|
|
# Take margins into account
|
|
paper_width_mm += 2 * commons.convert_pt_to_mm(Renderer.PRINT_SAFE_MARGIN_PT)
|
|
paper_height_mm += 2 * commons.convert_pt_to_mm(Renderer.PRINT_SAFE_MARGIN_PT)
|
|
|
|
# Take grid legend, title and copyright into account
|
|
paper_width_mm /= 1 - Renderer.GRID_LEGEND_MARGIN_RATIO
|
|
paper_height_mm /= 1 - (Renderer.GRID_LEGEND_MARGIN_RATIO + 0.05 + 0.02)
|
|
|
|
# Transform the values into integers
|
|
paper_width_mm = int(math.ceil(paper_width_mm))
|
|
paper_height_mm = int(math.ceil(paper_height_mm))
|
|
|
|
LOG.debug('Best fit is %.1fx%.1fcm.' % (paper_width_mm/10., paper_height_mm/10.))
|
|
|
|
# Test both portrait and landscape orientations when checking for paper
|
|
# sizes.
|
|
valid_sizes = []
|
|
for name, w, h in ocitysmap2.layoutlib.PAPER_SIZES:
|
|
portrait_ok = paper_width_mm <= w and paper_height_mm <= h
|
|
landscape_ok = paper_width_mm <= h and paper_height_mm <= w
|
|
|
|
if portrait_ok or landscape_ok:
|
|
valid_sizes.append([name, w, h, portrait_ok, landscape_ok, False])
|
|
|
|
# Add a 'Custom' paper format to the list that perfectly matches the
|
|
# bounding box.
|
|
valid_sizes.append(['Best fit',
|
|
min(paper_width_mm, paper_height_mm),
|
|
max(paper_width_mm, paper_height_mm),
|
|
paper_width_mm < paper_height_mm,
|
|
paper_width_mm > paper_height_mm,
|
|
False])
|
|
|
|
# select the first one as default
|
|
valid_sizes[0][5] = True
|
|
|
|
return valid_sizes
|
|
|
|
|
|
class SinglePageRendererNoIndex(SinglePageRenderer):
|
|
|
|
name = 'plain'
|
|
description = 'Full-page layout without index.'
|
|
|
|
def __init__(self, db, rc, tmpdir, dpi, file_prefix):
|
|
"""
|
|
Create the renderer.
|
|
|
|
Args:
|
|
rc (RenderingConfiguration): rendering parameters.
|
|
tmpdir (os.path): Path to a temp dir that can hold temp files.
|
|
"""
|
|
SinglePageRenderer.__init__(self, db, rc, tmpdir, dpi, file_prefix, None)
|
|
|
|
|
|
@staticmethod
|
|
def get_compatible_paper_sizes(bounding_box,
|
|
scale=Renderer.DEFAULT_SCALE):
|
|
"""Returns a list of the compatible paper sizes for the given bounding
|
|
box. The list is sorted, smaller papers first, and a "custom" paper
|
|
matching the dimensions of the bounding box is added at the end.
|
|
|
|
Args:
|
|
bounding_box (coords.BoundingBox): the map geographic bounding box.
|
|
scale (int): minimum mapnik scale of the map.
|
|
|
|
Returns a list of tuples (paper name, width in mm, height in
|
|
mm, portrait_ok, landscape_ok). Paper sizes are represented in
|
|
portrait mode.
|
|
"""
|
|
return SinglePageRenderer._generic_get_compatible_paper_sizes(
|
|
bounding_box, scale, None)
|
|
|
|
|
|
class SinglePageRendererIndexOnSide(SinglePageRenderer):
|
|
|
|
name = 'single_page_index_side'
|
|
description = 'Full-page layout with the index on the side.'
|
|
|
|
def __init__(self, db, rc, tmpdir, dpi, file_prefix):
|
|
"""
|
|
Create the renderer.
|
|
|
|
Args:
|
|
rc (RenderingConfiguration): rendering parameters.
|
|
tmpdir (os.path): Path to a temp dir that can hold temp files.
|
|
"""
|
|
SinglePageRenderer.__init__(self, db, rc, tmpdir, dpi, file_prefix, 'side')
|
|
|
|
@staticmethod
|
|
def get_compatible_paper_sizes(bounding_box,
|
|
scale=Renderer.DEFAULT_SCALE):
|
|
"""Returns a list of the compatible paper sizes for the given bounding
|
|
box. The list is sorted, smaller papers first, and a "custom" paper
|
|
matching the dimensions of the bounding box is added at the end.
|
|
|
|
Args:
|
|
bounding_box (coords.BoundingBox): the map geographic bounding box.
|
|
scale (int): minimum mapnik scale of the map.
|
|
|
|
Returns a list of tuples (paper name, width in mm, height in
|
|
mm, portrait_ok, landscape_ok). Paper sizes are represented in
|
|
portrait mode.
|
|
"""
|
|
return SinglePageRenderer._generic_get_compatible_paper_sizes(
|
|
bounding_box, scale, 'side')
|
|
|
|
|
|
class SinglePageRendererIndexBottom(SinglePageRenderer):
|
|
|
|
name = 'single_page_index_bottom'
|
|
description = 'Full-page layout with the index at the bottom.'
|
|
|
|
def __init__(self, db, rc, tmpdir, dpi, file_prefix):
|
|
"""
|
|
Create the renderer.
|
|
|
|
Args:
|
|
rc (RenderingConfiguration): rendering parameters.
|
|
tmpdir (os.path): Path to a temp dir that can hold temp files.
|
|
"""
|
|
SinglePageRenderer.__init__(self, db, rc, tmpdir, dpi, file_prefix, 'bottom')
|
|
|
|
@staticmethod
|
|
def get_compatible_paper_sizes(bounding_box,
|
|
scale=Renderer.DEFAULT_SCALE):
|
|
"""Returns a list of the compatible paper sizes for the given bounding
|
|
box. The list is sorted, smaller papers first, and a "custom" paper
|
|
matching the dimensions of the bounding box is added at the end.
|
|
|
|
Args:
|
|
bounding_box (coords.BoundingBox): the map geographic bounding box.
|
|
scale (int): minimum mapnik scale of the map.
|
|
|
|
Returns a list of tuples (paper name, width in mm, height in
|
|
mm, portrait_ok, landscape_ok). Paper sizes are represented in
|
|
portrait mode.
|
|
"""
|
|
return SinglePageRenderer._generic_get_compatible_paper_sizes(
|
|
bounding_box, scale, 'bottom')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import renderers
|
|
import coords
|
|
from ocitysmap2 import i18n
|
|
|
|
# Hack to fake gettext
|
|
try:
|
|
_(u"Test gettext")
|
|
except NameError:
|
|
__builtins__.__dict__["_"] = lambda x: x
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
|
|
bbox = coords.BoundingBox(48.8162, 2.3417, 48.8063, 2.3699)
|
|
zoom = 16
|
|
|
|
renderer_cls = renderers.get_renderer_class_by_name('plain')
|
|
papers = renderer_cls.get_compatible_paper_sizes(bbox, zoom)
|
|
|
|
print 'Compatible paper sizes:'
|
|
for p in papers:
|
|
print ' * %s (%.1fx%.1fcm)' % (p[0], p[1]/10.0, p[2]/10.0)
|
|
print 'Using first available:', papers[0]
|
|
|
|
class StylesheetMock:
|
|
def __init__(self):
|
|
# self.path = '/home/sam/src/python/maposmatic/mapnik-osm/osm.xml'
|
|
self.path = '/mnt/data1/common/home/d2/Downloads/svn/mapnik-osm/osm.xml'
|
|
self.grid_line_color = 'black'
|
|
self.grid_line_alpha = 0.9
|
|
self.grid_line_width = 2
|
|
self.shade_color = 'black'
|
|
self.shade_alpha = 0.7
|
|
|
|
class RenderingConfigurationMock:
|
|
def __init__(self):
|
|
self.stylesheet = StylesheetMock()
|
|
self.bounding_box = bbox
|
|
self.paper_width_mm = papers[0][1]
|
|
self.paper_height_mm = papers[0][2]
|
|
self.i18n = i18n.i18n()
|
|
self.title = 'Au Kremlin-Bycêtre'
|
|
self.polygon_wkt = bbox.as_wkt()
|
|
|
|
config = RenderingConfigurationMock()
|
|
|
|
plain = renderer_cls(config, '/tmp', None)
|
|
surface = cairo.PDFSurface('/tmp/plain.pdf',
|
|
commons.convert_mm_to_pt(config.paper_width_mm),
|
|
commons.convert_mm_to_pt(config.paper_height_mm))
|
|
|
|
plain.render(surface, commons.PT_PER_INCH)
|
|
surface.finish()
|
|
|
|
print "Generated /tmp/plain.pdf"
|