コード例 #1
0
    def __init__(self, db, rc, tmpdir, dpi, file_prefix):
        Renderer.__init__(self, db, rc, tmpdir, dpi)

        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))

        self._map_coords = ( Renderer.PRINT_SAFE_MARGIN_PT,
                             Renderer.PRINT_SAFE_MARGIN_PT,
                             self._usable_area_width_pt,
                             self._usable_area_height_pt ) 

        scale_denom = Renderer.DEFAULT_MULTIPAGE_SCALE

        # offset to the first map page number
        # there are currently three header pages
        # making the first actual map detail page number 4
        self._first_map_page_number = 4

        # the mapnik scale depends on the latitude. However we are
        # always using Mapnik conversion functions (lat,lon <->
        # mercator_meters) so we don't need to take into account
        # latitude in following computations

        # by convention, mapnik uses 90 ppi whereas cairo uses 72 ppi
        scale_denom *= float(72) / 90

        GRAYED_MARGIN_MM  = 10
        OVERLAP_MARGIN_MM = 20

        # Convert the original Bounding box into Mercator meters
        self._proj = mapnik.Projection(coords._MAPNIK_PROJECTION)
        orig_envelope = self._project_envelope(self.rc.bounding_box)


        while True:
            # Extend the bounding box to take into account the lost outer
            # margin
            off_x  = orig_envelope.minx - GRAYED_MARGIN_MM * 9.6
            off_y  = orig_envelope.miny - GRAYED_MARGIN_MM * 9.6
            width  = orig_envelope.width() + (2 * GRAYED_MARGIN_MM) * 9.6
            height = orig_envelope.height() + (2 * GRAYED_MARGIN_MM) * 9.6

            # 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))

            total_pages = self.nb_pages_width * self.nb_pages_height

            if Renderer.MAX_MULTIPAGE_MAPPAGES and \
               total_pages < Renderer.MAX_MULTIPAGE_MAPPAGES:
                break

            new_scale_denom = scale_denom * 1.41

            if new_scale_denom > Renderer.DEFAULT_SCALE:
                break

            scale_denom = new_scale_denom


        # 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)

        # 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 = []
        self.page_disposition, map_number = {}, 0
        for j in reversed(range(0, self.nb_pages_height)):
            col = self.nb_pages_height - j - 1
            self.page_disposition[col] = []
            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())):
                    self.page_disposition[col].append(map_number)
                    map_number += 1
                    bboxes.append((self._inverse_envelope(envelope),
                                   inner_bb))
                else:
                    self.page_disposition[col].append(None)

        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)

        if self.rc.osmid != None:
            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()

        self._overlays = copy(self.rc.overlays)
        
        # generate style file for GPX file
        if self.rc.gpx_file:
            try:
                gpx_style = GpxStylesheet(self.rc.gpx_file, self.tmpdir)
            except Exception as e:
                LOG.warning("GPX stylesheet error: %s" % e)
            else:
                self._overlays.append(gpx_style)

        # denormalize UMAP json to geojson, then create style for it
        if self.rc.umap_file:
            try:
                umap_style = UmapStylesheet(self.rc.umap_file, self.tmpdir)
            except Exception as e:
                LOG.warning("Umap_stylesheet error: %s" % e)
            else:
                self._overlays.append(umap_style)

        self.overview_overlay_canvases = []
        self.overview_overlay_effects  = {}
        
        for overlay in self._overlays:
            path = overlay.path.strip()
            if path.startswith('internal:'):
                plugin_name = path.lstrip('internal:')
                self.overview_overlay_effects[plugin_name] = self.get_plugin(plugin_name)
            else:
                ov_canvas = MapCanvas(overlay,
                                      overview_bb,
                                      self._usable_area_width_pt,
                                      self._usable_area_height_pt,
                                      dpi,
                                      extend_bbox_to_ratio=True)
                ov_canvas.render()
                self.overview_overlay_canvases.append(ov_canvas)

        # 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 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)

            # Create canvas for overlay on current page
            overlay_canvases = []
            overlay_effects  = {}
            for overlay in self._overlays:
                path = overlay.path.strip()
                plugin_name = path.lstrip('internal:')
                if path.startswith('internal:'):
                    overlay_effects[plugin_name] = self.get_plugin(plugin_name)
                else:
                    overlay_canvases.append(MapCanvas(overlay,
                                               bb, self._usable_area_width_pt,
                                               self._usable_area_height_pt, dpi,
                                               extend_bbox_to_ratio=False))

            # Create the grid
            map_grid = Grid(bb_inner, map_canvas.get_actual_scale(), self.rc.i18n.isrtl())
            grid_shape = map_grid.generate_shape_file(
                os.path.join(self.tmpdir, 'grid%d.shp' % i))

            map_canvas.add_shape_file(shade)
            if self.rc.osmid != None:
                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()

            for overlay_canvas in overlay_canvases:
                overlay_canvas.render()

            self.pages.append((map_canvas, map_grid, overlay_canvases, overlay_effects))

            # 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 + self._first_map_page_number))

            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._prepare_front_page_map(dpi)
コード例 #2
0
ファイル: multi_page_renderer.py プロジェクト: Nimn/ocitysmap
    def __init__(self, db, rc, tmpdir, dpi, file_prefix):
        Renderer.__init__(self, db, rc, tmpdir, dpi)

        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 = Renderer.DEFAULT_SCALE

        # the mapnik scale depends on the latitude. However we are
        # always using Mapnik conversion functions (lat,lon <->
        # mercator_meters) so we don't need to take into account
        # latitude in following computations

        # by convention, mapnik uses 90 ppi whereas cairo uses 72 ppi
        scale_denom *= float(72) / 90

        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 = []
        self.page_disposition, map_number = {}, 0
        for j in reversed(range(0, self.nb_pages_height)):
            col = self.nb_pages_height - j - 1
            self.page_disposition[col] = []
            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())):
                    self.page_disposition[col].append(map_number)
                    map_number += 1
                    bboxes.append((self._inverse_envelope(envelope), inner_bb))
                else:
                    self.page_disposition[col].append(None)
        # 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 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)

            # Create canvas for overlay on current page
            overla_canvas = None
            if self.rc.overlay:
                overlay_canvas = MapCanvas(self.rc.overlay,
                                           bb,
                                           self._usable_area_width_pt,
                                           self._usable_area_height_pt,
                                           dpi,
                                           extend_bbox_to_ratio=False)

            # Create the grid
            map_grid = Grid(bb_inner, map_canvas.get_actual_scale(),
                            self.rc.i18n.isrtl())
            grid_shape = map_grid.generate_shape_file(
                os.path.join(self.tmpdir, 'grid%d.shp' % i))

            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()

            if overlay_canvas:
                overlay_canvas.render()

            self.pages.append((map_canvas, map_grid, overlay_canvas))

            # 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)
コード例 #3
0
    def __init__(self, db, rc, tmpdir, dpi, file_prefix):
        Renderer.__init__(self, db, rc, tmpdir, dpi)

        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 = Renderer.DEFAULT_SCALE

        # the mapnik scale depends on the latitude. However we are
        # always using Mapnik conversion functions (lat,lon <->
        # mercator_meters) so we don't need to take into account
        # latitude in following computations

        # by convention, mapnik uses 90 ppi whereas cairo uses 72 ppi
        scale_denom *= float(72) / 90

        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 = []
        self.page_disposition, map_number = {}, 0
        for j in reversed(range(0, self.nb_pages_height)):
            col = self.nb_pages_height - j - 1
            self.page_disposition[col] = []
            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())):
                    self.page_disposition[col].append(map_number)
                    map_number += 1
                    bboxes.append((self._inverse_envelope(envelope),
                                   inner_bb))
                else:
                    self.page_disposition[col].append(None)
        # 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 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)

            # Create the grid
            map_grid = Grid(bb_inner, map_canvas.get_actual_scale(), self.rc.i18n.isrtl())
            grid_shape = map_grid.generate_shape_file(
                os.path.join(self.tmpdir, 'grid%d.shp' % i))

            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)