Пример #1
0
    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
Пример #2
0
    def _create_map_canvas(self, width, height, dpi,
                           draw_contour_shade = True):
        """
        Create a new MapCanvas object.

        Args:
           graphical_ratio (float): ratio W/H of the area to render into.
           draw_contour_shade (bool): whether to draw a shade around
               the area of interest or not.

        Return the MapCanvas object or raise ValueError.
        """

        # Prepare the map canvas
        canvas = MapCanvas(self.rc.stylesheet,
                           self.rc.bounding_box,
                           width, height, dpi)

        if draw_contour_shade:
            # Area to keep visible
            interior = shapely.wkt.loads(self.rc.polygon_wkt)

            # Surroundings to gray-out
            bounding_box \
                = canvas.get_actual_bounding_box().create_expanded(0.05, 0.05)
            exterior = shapely.wkt.loads(bounding_box.as_wkt())

            # Determine the shade WKT
            shade_wkt = exterior.difference(interior).wkt

            # Prepare the shade SHP
            shade_shape = maplib.shapes.PolyShapeFile(
                canvas.get_actual_bounding_box(),
                os.path.join(self.tmpdir, 'shade.shp'),
                'shade')
            shade_shape.add_shade_from_wkt(shade_wkt)

            # Add the shade SHP to the map
            canvas.add_shape_file(shade_shape,
                                  self.rc.stylesheet.shade_color,
                                  self.rc.stylesheet.shade_alpha,
                                  self.rc.stylesheet.grid_line_width)

        return canvas
Пример #3
0
    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
Пример #4
0
    def _create_map_canvas(self, width, height, dpi,
                           draw_contour_shade = True):
        """
        Create a new MapCanvas object.

        Args:
           graphical_ratio (float): ratio W/H of the area to render into.
           draw_contour_shade (bool): whether to draw a shade around
               the area of interest or not.

        Return the MapCanvas object or raise ValueError.
        """

        # Prepare the map canvas
        canvas = MapCanvas(self.rc.stylesheet,
                           self.rc.bounding_box,
                           width, height, dpi)

        if draw_contour_shade:
            # Area to keep visible
            interior = shapely.wkt.loads(self.rc.polygon_wkt)

            # Surroundings to gray-out
            bounding_box \
                = canvas.get_actual_bounding_box().create_expanded(0.05, 0.05)
            exterior = shapely.wkt.loads(bounding_box.as_wkt())

            # Determine the shade WKT
            shade_wkt = exterior.difference(interior).wkt

            # Prepare the shade SHP
            shade_shape = maplib.shapes.PolyShapeFile(
                canvas.get_actual_bounding_box(),
                os.path.join(self.tmpdir, 'shade.shp'),
                'shade')
            shade_shape.add_shade_from_wkt(shade_wkt)

            # Add the shade SHP to the map
            canvas.add_shape_file(shade_shape,
                                  self.rc.stylesheet.shade_color,
                                  self.rc.stylesheet.shade_alpha,
                                  self.rc.stylesheet.grid_line_width)

        return canvas
Пример #5
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 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)
Пример #6
0
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, 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)

    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()
        draw_utils.draw_text_adjusted(ctx, self.rc.title, blue_w / 2,
                                      blue_h / 2, blue_w, blue_h)

    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)

        # prevent map background from filling the full canvas
        ctx.rectangle(0, 0, w, h / 2)
        ctx.clip()

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

        draw_utils.draw_text_adjusted(ctx,
                                      notice,
                                      Renderer.PRINT_SAFE_MARGIN_PT,
                                      footer_h / 2,
                                      footer_w,
                                      footer_h,
                                      align=pango.ALIGN_LEFT)
        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)
        draw_utils.draw_simpletext_center(ctx, _('This page is intentionally left '\
                                            'blank.'), w/2.0, 0.95*h)
        draw_utils.render_page_number(ctx,
                                      2,
                                      self._usable_area_width_pt,
                                      self._usable_area_height_pt,
                                      self.grayed_margin_pt,
                                      transparent_background=False)
        cairo_surface.show_page()
        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
        draw_utils.render_page_number(ctx,
                                      3,
                                      self._usable_area_width_pt,
                                      self._usable_area_height_pt,
                                      self.grayed_margin_pt,
                                      transparent_background=True)

        cairo_surface.show_page()

    def _draw_arrow(self,
                    ctx,
                    cairo_surface,
                    number,
                    max_digit_number,
                    reverse_text=False):
        arrow_edge = self.grayed_margin_pt * .6
        ctx.save()
        ctx.set_source_rgb(0, 0, 0)
        ctx.translate(-arrow_edge / 2, -arrow_edge * 0.45)
        ctx.line_to(0, 0)
        ctx.line_to(0, arrow_edge)
        ctx.line_to(arrow_edge, arrow_edge)
        ctx.line_to(arrow_edge, 0)
        ctx.line_to(arrow_edge / 2, -arrow_edge * .25)
        ctx.close_path()
        ctx.fill()
        ctx.restore()

        ctx.save()
        if reverse_text:
            ctx.rotate(math.pi)
        draw_utils.draw_text_adjusted(ctx,
                                      unicode(number),
                                      0,
                                      0,
                                      arrow_edge,
                                      arrow_edge,
                                      max_char_number=max_digit_number,
                                      text_color=(1, 1, 1, 1),
                                      width_adjust=0.85,
                                      height_adjust=0.9)
        ctx.restore()

    def _render_neighbour_arrows(self, ctx, cairo_surface, map_number,
                                 max_digit_number):
        nb_previous_pages = 4
        current_line, current_col = None, None
        for line_nb in xrange(self.nb_pages_height):
            if map_number in self.page_disposition[line_nb]:
                current_line = line_nb
                current_col = self.page_disposition[line_nb].index(map_number)
                break
        if current_line == None:
            # page not referenced
            return

        # north arrow
        for line_nb in reversed(xrange(current_line)):
            if self.page_disposition[line_nb][current_col] != None:
                north_arrow = self.page_disposition[line_nb][current_col]
                ctx.save()
                ctx.translate(
                    self._usable_area_width_pt / 2,
                    commons.convert_pt_to_dots(self.grayed_margin_pt) / 2)
                self._draw_arrow(ctx, cairo_surface,
                                 north_arrow + nb_previous_pages,
                                 max_digit_number)
                ctx.restore()
                break

        # south arrow
        for line_nb in xrange(current_line + 1, self.nb_pages_height):
            if self.page_disposition[line_nb][current_col] != None:
                south_arrow = self.page_disposition[line_nb][current_col]
                ctx.save()
                ctx.translate(self._usable_area_width_pt/2,
                     self._usable_area_height_pt \
                      - commons.convert_pt_to_dots(self.grayed_margin_pt)/2)
                ctx.rotate(math.pi)
                self._draw_arrow(ctx,
                                 cairo_surface,
                                 south_arrow + nb_previous_pages,
                                 max_digit_number,
                                 reverse_text=True)
                ctx.restore()
                break

        # west arrow
        for col_nb in reversed(xrange(0, current_col)):
            if self.page_disposition[current_line][col_nb] != None:
                west_arrow = self.page_disposition[current_line][col_nb]
                ctx.save()
                ctx.translate(
                    commons.convert_pt_to_dots(self.grayed_margin_pt) / 2,
                    self._usable_area_height_pt / 2)
                ctx.rotate(-math.pi / 2)
                self._draw_arrow(ctx, cairo_surface,
                                 west_arrow + nb_previous_pages,
                                 max_digit_number)
                ctx.restore()
                break

        # east arrow
        for col_nb in xrange(current_col + 1, self.nb_pages_width):
            if self.page_disposition[current_line][col_nb] != None:
                east_arrow = self.page_disposition[current_line][col_nb]
                ctx.save()
                ctx.translate(
                    self._usable_area_width_pt \
                     - commons.convert_pt_to_dots(self.grayed_margin_pt)/2,
                    self._usable_area_height_pt/2)
                ctx.rotate(math.pi / 2)
                self._draw_arrow(ctx, cairo_surface,
                                 east_arrow + nb_previous_pages,
                                 max_digit_number)
                ctx.restore()
                break

    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 map_number, (canvas, grid, overlay) in enumerate(self.pages):

            rendered_map = canvas.get_rendered_map()
            LOG.debug('Mapnik scale: 1/%f' % rendered_map.scale_denominator())
            LOG.debug('Actual scale: 1/%f' % canvas.get_actual_scale())
            mapnik.render(rendered_map, ctx)

            if overlay:
                rendered_overlay = overlay.get_rendered_map()
                mapnik.render(rendered_overlay, 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
            draw_utils.render_page_number(ctx,
                                          map_number + 4,
                                          self._usable_area_width_pt,
                                          self._usable_area_height_pt,
                                          self.grayed_margin_pt,
                                          transparent_background=True)
            self._render_neighbour_arrows(ctx, cairo_surface, map_number,
                                          len(unicode(len(self.pages) + 4)))

            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),
            map_number + 5)

        mpsir.render()

        cairo_surface.flush()

    # In multi-page mode, we only render pdf format
    @staticmethod
    def get_compatible_output_formats():
        return ["pdf"]

    # In multi-page mode, we only accept A4, A5 and US letter as paper
    # sizes. The goal is to render booklets, not posters.
    # The default paper size is A4 portrait
    @staticmethod
    def get_compatible_paper_sizes(bounding_box,
                                   scale=Renderer.DEFAULT_SCALE,
                                   index_position=None,
                                   hsplit=1,
                                   vsplit=1):
        valid_sizes = []
        acceptable_formats = ['A5', 'A4', 'US letter']
        for sz in ocitysmap.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, sz[0] == 'A4'))
        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
            draw_utils.draw_text_adjusted(
                ctx,
                unicode(idx + 4),
                x,
                y,
                w,
                h,
                max_char_number=len(
                    unicode(len(overview_grid._pages_bbox) + 3)),
                text_color=(0, 0, 0, 0.6))

        ctx.restore()
Пример #7
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)
Пример #8
0
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, 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)

    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 _my_cmp(self, x, y):
        return locale.strcoll(x.label, y.label)

    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)
            try:
                locale.setlocale(locale.LC_COLLATE, self.rc.i18n.language_code())
            except Exception:
                l.warning('error while setting LC_COLLATE to "%s"' % self._i18n.language_code())

            try:
                grouped_items_sorted = \
                    sorted(grouped_items, key = cmp_to_key(self._my_cmp))        
            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(
                StreetIndexCategory(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 3587) back to a 4326
        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()
        self._front_page_map = front_page_map

        self._frontpage_overlay_canvases = []
        self._frontpage_overlay_effects  = {}
        for overlay in self._overlays:
            path = overlay.path.strip()
            plugin_name = path.lstrip('internal:')
            if path.startswith('internal:'):
                self._frontpage_overlay_effects[plugin_name] = self.get_plugin(plugin_name)
            else:
                ov_canvas = MapCanvas(overlay,
                                      self.rc.bounding_box,
                                      front_page_map_w,
                                      front_page_map_h,
                                      dpi,
                                      extend_bbox_to_ratio=True)
                ov_canvas.render()
                self._frontpage_overlay_canvases.append(ov_canvas)

    def _render_front_page_header(self, ctx, w, h):
        # Draw a grey blue block which will contain the name of the
        # city being rendered.
        ctx.save()
        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()
        draw_utils.draw_text_adjusted(ctx, self.rc.title, blue_w/2, blue_h/2,
                 blue_w, blue_h)
        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)

        # prevent map background from filling the full canvas
        ctx.rectangle(0, 0, w, h / 2)
        ctx.clip()

        # Render the map !
        mapnik.render(self._front_page_map.get_rendered_map(), ctx)
        
        for ov_canvas in self._frontpage_overlay_canvases:
            rendered_map = ov_canvas.get_rendered_map()
            mapnik.render(rendered_map, ctx)

        # apply effect overlays
        ctx.save()
        # we have to undo border adjustments here
        ctx.translate(0, -(0.3 * h + Renderer.PRINT_SAFE_MARGIN_PT))
        self._map_canvas = self._front_page_map;
        for plugin_name, effect in self._frontpage_overlay_effects.items():
            try:
                effect.render(self, ctx)
            except Exception as e:
                # TODO better logging
                LOG.warning("Error while rendering overlay: %s\n%s" % (plugin_name, e))
        ctx.restore()
    

            
        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.')
        notice+= '\n\n'
        notice+= _(u'Map data © %(year)d OpenStreetMap contributors (see http://osm.org/copyright)')
        notice+= '\n'
        annotations = []

        if self.rc.stylesheet.annotation != '':
            annotations.append(self.rc.stylesheet.annotation)
            for overlay in self._overlays:
                if overlay.annotation != '':
                    annotations.append(overlay.annotation)
        if len(annotations) > 0:
            notice+= _(u'Map styles:')
            notice+= ' ' + '; '.join(annotations) + '\n'

        notice+= _(u'Map rendered on: %(date)s. OSM data updated on: %(osmdate)s.')
        notice+= '\n'
        notice+= _(u'The map may be incomplete or inaccurate.')

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

        draw_utils.draw_text_adjusted(ctx, notice,
                Renderer.PRINT_SAFE_MARGIN_PT, footer_h/2, footer_w,
                footer_h, align=Pango.Alignment.LEFT)
        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_map(ctx, dpi, w, h)
        self._render_front_page_header(ctx, w, h)
        self._render_front_page_footer(ctx, w, h, osm_date)

        ctx.restore()

        cairo_surface.set_page_label('Front page')
        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)
        draw_utils.draw_simpletext_center(ctx, _('This page is intentionally left '\
                                            'blank.'), w/2.0, 0.95*h)
        draw_utils.render_page_number(ctx, 2,
                                      self._usable_area_width_pt,
                                      self._usable_area_height_pt,
                                      self.grayed_margin_pt,
                                      transparent_background=False)
        cairo_surface.set_page_label('Blank')
        cairo_surface.show_page()
        ctx.restore()

    def _render_overview_page(self, ctx, cairo_surface, dpi):
        rendered_map = self.overview_canvas.get_rendered_map()
        mapnik.render(rendered_map, ctx)

        for ov_canvas in self.overview_overlay_canvases:
            rendered_map = ov_canvas.get_rendered_map()
            mapnik.render(rendered_map, ctx)

        # apply effect overlays
        ctx.save()
        # we have to undo border adjustments here
        ctx.translate(
                -commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT),
                -commons.convert_pt_to_dots(Renderer.PRINT_SAFE_MARGIN_PT))
        self._map_canvas = self.overview_canvas;
        for plugin_name, effect in self.overview_overlay_effects.items():
            try:
                effect.render(self, ctx)
            except Exception as e:
                # TODO better logging
                LOG.warning("Error while rendering overlay: %s\n%s" % (plugin_name, e))

        ctx.restore()
            
        # 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
        draw_utils.render_page_number(ctx, 3,
                                      self._usable_area_width_pt,
                                      self._usable_area_height_pt,
                                      self.grayed_margin_pt,
                                      transparent_background = True)

        cairo_surface.set_page_label('Overview')
        cairo_surface.show_page()

    def _draw_arrow(self, ctx, cairo_surface, number, max_digit_number,
                    reverse_text=False):
        arrow_edge = self.grayed_margin_pt*.6
        ctx.save()
        ctx.set_source_rgb(0, 0, 0)
        ctx.translate(-arrow_edge/2, -arrow_edge*0.45)

        dest_name = "mypage%d" % number
        draw_utils.begin_internal_link(ctx, dest_name)

        ctx.line_to(0, 0)
        ctx.line_to(0, arrow_edge)
        ctx.line_to(arrow_edge, arrow_edge)
        ctx.line_to(arrow_edge, 0)
        ctx.line_to(arrow_edge/2, -arrow_edge*.25)
        ctx.close_path()
        ctx.fill()
        draw_utils.end_link(ctx)
        ctx.restore()

        ctx.save()
        if reverse_text:
            ctx.rotate(math.pi)
        draw_utils.begin_internal_link(ctx, dest_name)
        draw_utils.draw_text_adjusted(ctx, str(number), 0, 0, arrow_edge,
                        arrow_edge, max_char_number=max_digit_number,
                        text_color=(1, 1, 1, 1), width_adjust=0.85,
                        height_adjust=0.9)
        draw_utils.end_link(ctx)
        ctx.restore()

    def _render_neighbour_arrows(self, ctx, cairo_surface, map_number,
                                 max_digit_number):
        current_line, current_col = None, None
        for line_nb in range(self.nb_pages_height):
            if map_number in self.page_disposition[line_nb]:
                current_line = line_nb
                current_col = self.page_disposition[line_nb].index(
                                                             map_number)
                break
        if current_line == None:
            # page not referenced
            return

        # north arrow
        for line_nb in reversed(range(current_line)):
            if self.page_disposition[line_nb][current_col] != None:
                north_arrow = self.page_disposition[line_nb][current_col]
                ctx.save()
                ctx.translate(self._usable_area_width_pt/2,
                    commons.convert_pt_to_dots(self.grayed_margin_pt)/2)
                self._draw_arrow(ctx, cairo_surface,
                              north_arrow + self._first_map_page_number, max_digit_number)
                ctx.restore()
                break

        # south arrow
        for line_nb in range(current_line + 1, self.nb_pages_height):
            if self.page_disposition[line_nb][current_col] != None:
                south_arrow = self.page_disposition[line_nb][current_col]
                ctx.save()
                ctx.translate(self._usable_area_width_pt/2,
                     self._usable_area_height_pt \
                      - commons.convert_pt_to_dots(self.grayed_margin_pt)/2)
                ctx.rotate(math.pi)
                self._draw_arrow(ctx, cairo_surface,
                      south_arrow + self._first_map_page_number, max_digit_number,
                      reverse_text=True)
                ctx.restore()
                break

        # west arrow
        for col_nb in reversed(range(0, current_col)):
            if self.page_disposition[current_line][col_nb] != None:
                west_arrow = self.page_disposition[current_line][col_nb]
                ctx.save()
                ctx.translate(
                    commons.convert_pt_to_dots(self.grayed_margin_pt)/2,
                    self._usable_area_height_pt/2)
                ctx.rotate(-math.pi/2)
                self._draw_arrow(ctx, cairo_surface,
                               west_arrow + self._first_map_page_number, max_digit_number)
                ctx.restore()
                break

        # east arrow
        for col_nb in range(current_col + 1, self.nb_pages_width):
            if self.page_disposition[current_line][col_nb] != None:
                east_arrow = self.page_disposition[current_line][col_nb]
                ctx.save()
                ctx.translate(
                    self._usable_area_width_pt \
                     - commons.convert_pt_to_dots(self.grayed_margin_pt)/2,
                    self._usable_area_height_pt/2)
                ctx.rotate(math.pi/2)
                self._draw_arrow(ctx, cairo_surface,
                               east_arrow + self._first_map_page_number, max_digit_number)
                ctx.restore()
                break

    @staticmethod
    def _draw_labels(ctx, map_grid,
                     map_area_width_dots, map_area_height_dots,
                     grid_legend_margin_dots):
        """
        Draw the Grid labels at current position.

        Args:
           ctx (cairo.Context): The cairo context to use to draw.
           map_grid (Grid): the grid objects whose labels we want to draw.
           map_area_width_dots/map_area_height_dots (numbers): size of the
              map (cairo units).
           grid_legend_margin_dots (number): margin between border of
              map and grid labels (cairo units).
        """
        ctx.save()

        ctx.set_source_rgba(0, 0, 0, 0.7);

        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 / 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))
        ctx.set_source_rgba(0, 0, 0, 1)

        # TODO labels can overlap with next page arrows,
        # if they do -> hide them? or move them out of the
        # grid center a bit?

        for i, label in enumerate(map_grid.horizontal_labels):
            x = i * step_horiz

            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

            draw_utils.draw_halotext_center(ctx, label,
                                            x, - grid_legend_margin_dots/1.0)

            draw_utils.draw_halotext_center(ctx, label,
                                            x, map_area_height_dots +
                                            grid_legend_margin_dots/1.0)

        for i, label in enumerate(map_grid.vertical_labels):
            y = i * step_vert

            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

            draw_utils.draw_halotext_center(ctx, label,
                                            -grid_legend_margin_dots, y)

            draw_utils.draw_halotext_center(ctx, label,
                                            map_area_width_dots +
                                            grid_legend_margin_dots, y)

        ctx.restore()



    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))
        ctx.rectangle(0, 0, self._usable_area_width_pt, self._usable_area_height_pt)
        ctx.clip()

        self._render_overview_page(ctx, cairo_surface, dpi)

        for map_number, (canvas, grid, overlay_canvases, overlay_effects) in enumerate(self.pages):
            LOG.info('Map page %d of %d' % (map_number + 1, len(self.pages)))
            rendered_map = canvas.get_rendered_map()
            LOG.debug('Mapnik scale: 1/%f' % rendered_map.scale_denominator())
            LOG.debug('Actual scale: 1/%f' % canvas.get_actual_scale())

            dest_tag = "mypage%d" % (map_number + self._first_map_page_number)
            draw_utils.anchor(ctx, dest_tag)

            mapnik.render(rendered_map, ctx)

            for overlay_canvas in overlay_canvases:
                rendered_overlay = overlay_canvas.get_rendered_map()
                mapnik.render(rendered_overlay, 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()


            # apply effect overlays
            ctx.save()
            # we have to undo border adjustments here
            ctx.translate(-commons.convert_pt_to_dots(self.grayed_margin_pt)/2,
                      -commons.convert_pt_to_dots(self.grayed_margin_pt)/2)
            self._map_canvas = canvas;
            for plugin_name, effect in overlay_effects.items():
                self.grid = grid
                try:
                    effect.render(self, ctx)
                except Exception as e:
                    # TODO better logging
                    LOG.warning("Error while rendering overlay: %s\n%s" % (plugin_name, e))
                    effect.render(self, ctx)
            ctx.restore()


            # Render the page number
            draw_utils.render_page_number(ctx, map_number + self._first_map_page_number,
                                          self._usable_area_width_pt,
                                          self._usable_area_height_pt,
                                          self.grayed_margin_pt,
                                          transparent_background = True)
            self._render_neighbour_arrows(ctx, cairo_surface, map_number,
                                          len(str(len(self.pages) + self._first_map_page_number)))

            cairo_surface.set_page_label('Map page %d' % (map_number + self._first_map_page_number))
            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),
                                              map_number+5)

        mpsir.render()

        cairo_surface.flush()

    # In multi-page mode, we only render pdf format
    @staticmethod
    def get_compatible_output_formats():
        return [ "pdf" ]

    @staticmethod
    def get_compatible_paper_sizes(bounding_box,
                                   renderer_context,
                                   scale=Renderer.DEFAULT_MULTIPAGE_SCALE,
                                   index_position=None, hsplit=1, vsplit=1):
        valid_sizes = []
        LOG.warning("getting multipage paper size options")
        is_default = True
        for sz in renderer_context.get_all_paper_sizes('multipage'):
            valid_sizes.append({
                    "name": sz[0],
                    "width": sz[1],
                    "height": sz[2],
                    "portrait_ok": True,
                    "landscape_ok": True,
                    "default": is_default,
                    "landscape_preferred": False
                })
            is_default = False

        return valid_sizes

    def _draw_overview_labels(self, 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

            draw_utils.draw_text_adjusted(ctx, str(idx + self._first_map_page_number),
                                          x, y, w, h,
                                          max_char_number=len(str(len(overview_grid._pages_bbox)+3)),
                                          text_color=(0, 0, 0, 0.6))

            ctx.save()
            ctx.translate(x-w/2, y-h/2)
            ctx.set_source_rgba(0,0,0,0.1)
            draw_utils.begin_internal_link(ctx, "mypage%d" % (idx + self._first_map_page_number))
            ctx.rectangle(0,0,w,h)
            ctx.stroke()
            draw_utils.end_link(ctx)
            ctx.restore()

        ctx.restore()
Пример #9
0
    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), 'extra_page' (index on 2nd page for PDF).
        """

        Renderer.__init__(self, db, rc, tmpdir, dpi)

        self.file_prefix = file_prefix

        # Prepare the index
        if rc.poi_file:
            self.street_index = PoiIndex(rc.poi_file)
        else:
            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.index_position = index_position

        # grid marker offset (originally used for solid grid frame,
        # now just for the letter/number overlay offset inside the map)
        self._grid_legend_margin_pt = \
            min(Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_width_pt,
                Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_height_pt)

        # reserve space for the page title if given
        if self.rc.title:
            self._title_margin_pt = 0.05 * self.paper_height_pt
        else:
            self._title_margin_pt = 0

        # reserve space for the page footer
        self._copyright_margin_pt = 0.03 * self.paper_height_pt

        # calculate remaining usable paper space after taking header
        # and footer into account
        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)
        else:
            self._index_renderer, self._index_area = None, None

        # Prepare the layout of the whole page
        if not self._index_area or index_position == 'extra_page':
            # 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,
            rc.osmid != None )

        # Prepare overlay styles for uploaded files
        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)

        # Prepare map overlays
        self._overlay_canvases = []
        self._overlay_effects  = {}
        for overlay in self._overlays:
            path = overlay.path.strip()
            if path.startswith('internal:'):
                plugin_name = path.lstrip('internal:')
                self._overlay_effects[plugin_name] = self.get_plugin(plugin_name)
            else:
                self._overlay_canvases.append(MapCanvas(overlay,
                                              self.rc.bounding_box,
                                              float(self._map_coords[2]),  # W
                                              float(self._map_coords[3]),  # H
                                              dpi))

        # add special POI marker overlay if a POI file is given
        # TODO: refactor this special case
        if self.rc.poi_file:
            self._overlay_effects['poi_markers'] = self.get_plugin('poi_markers')

        # Prepare the grid
        self.grid = self._create_grid(self._map_canvas, dpi)
        if index_position: # only show grid if an actual index refers to it
            self._apply_grid(self.grid, self._map_canvas)

        # Commit the internal rendering stack of the map
        self._map_canvas.render()
        for overlay_canvas in self._overlay_canvases:
           overlay_canvas.render()
Пример #10
0
    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,
            rc.osmid is not None )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(self.rc.overlay,
                                             self.rc.bounding_box,
                                             float(self._map_coords[2]),  # W
                                             float(self._map_coords[3]),  # H
                                             dpi)

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(self.rc.overlay,
                                             self.rc.bounding_box,
                                             float(self._map_coords[2]),  # W
                                             float(self._map_coords[3]),  # H
                                             dpi)

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(self.rc.overlay,
                                             self.rc.bounding_box,
                                             float(self._map_coords[2]),  # W
                                             float(self._map_coords[3]),  # H
                                             dpi)

        # Prepare the grid
        self.grid = self._create_grid(self._map_canvas, dpi)

        # 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()
        if self.rc.overlay:
           self._overlay_canvas.render()
Пример #11
0
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,
            rc.osmid is not None )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(self.rc.overlay,
                                             self.rc.bounding_box,
                                             float(self._map_coords[2]),  # W
                                             float(self._map_coords[3]),  # H
                                             dpi)

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(self.rc.overlay,
                                             self.rc.bounding_box,
                                             float(self._map_coords[2]),  # W
                                             float(self._map_coords[3]),  # H
                                             dpi)

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(self.rc.overlay,
                                             self.rc.bounding_box,
                                             float(self._map_coords[2]),  # W
                                             float(self._map_coords[3]),  # H
                                             dpi)

        # Prepare the grid
        self.grid = self._create_grid(self._map_canvas, dpi)

        # 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()
        if self.rc.overlay:
           self._overlay_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 map, scaled to fit the designated area
        ##
        ctx.save()

        # prevent map background from filling the full canvas
        ctx.rectangle(map_coords_dots[0], map_coords_dots[1], map_coords_dots[2], map_coords_dots[3])
        ctx.clip()

        # 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()
        scale_factor = dpi / 72
        rendered_map = self._map_canvas.get_rendered_map()
        LOG.debug('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, scale_factor, 0, 0)
        ctx.restore()

        # Draw the rescaled Overlay
        if self.rc.overlay:
            LOG.debug('Overlay:')
            ctx.save()
            scale_factor = dpi / 72
            rendered_overlay = self._overlay_canvas.get_rendered_map()
            mapnik.render(rendered_overlay, ctx, scale_factor, 0, 0)
            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 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 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()

        # Draw compass rose
        # TODO: proper positioning/scaling, move to abstract renderer
        ctx.save();
        ctx.translate(50, title_margin_dots + 50);
        ctx.scale(0.33, 0.33)
        compass_path = os.path.abspath(os.path.join(
            os.path.dirname(__file__), '..', '..', 'images', 'compass-rose.svg'))
        svg = rsvg.Handle(compass_path)
        svg.render_cairo(ctx)
        ctx.restore();

        # TODO: map scale

        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 ocitysmap.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
Пример #12
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)
Пример #13
0
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, 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)

    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()
        draw_utils.draw_text_adjusted(ctx, self.rc.title, blue_w/2, blue_h/2,
                 blue_w, blue_h)

    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)

        draw_utils.draw_text_adjusted(ctx, notice,
                Renderer.PRINT_SAFE_MARGIN_PT, footer_h/2, footer_w,
                footer_h, align=pango.ALIGN_LEFT)
        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)
        draw_utils.draw_simpletext_center(ctx, _('This page is intentionally left '\
                                            'blank.'), w/2.0, 0.95*h)
        draw_utils.render_page_number(ctx, 2,
                                      self._usable_area_width_pt,
                                      self._usable_area_height_pt,
                                      self.grayed_margin_pt,
                                      transparent_background=False)
        cairo_surface.show_page()
        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
        draw_utils.render_page_number(ctx, 3,
                                      self._usable_area_width_pt,
                                      self._usable_area_height_pt,
                                      self.grayed_margin_pt,
                                      transparent_background = True)

        cairo_surface.show_page()

    def _draw_arrow(self, ctx, cairo_surface, number, max_digit_number,
                    reverse_text=False):
        arrow_edge = self.grayed_margin_pt*.6
        ctx.save()
        ctx.set_source_rgb(0, 0, 0)
        ctx.translate(-arrow_edge/2, -arrow_edge*0.45)
        ctx.line_to(0, 0)
        ctx.line_to(0, arrow_edge)
        ctx.line_to(arrow_edge, arrow_edge)
        ctx.line_to(arrow_edge, 0)
        ctx.line_to(arrow_edge/2, -arrow_edge*.25)
        ctx.close_path()
        ctx.fill()
        ctx.restore()

        ctx.save()
        if reverse_text:
            ctx.rotate(math.pi)
        draw_utils.draw_text_adjusted(ctx, unicode(number), 0, 0, arrow_edge,
                        arrow_edge, max_char_number=max_digit_number,
                        text_color=(1, 1, 1, 1), width_adjust=0.85,
                        height_adjust=0.9)
        ctx.restore()

    def _render_neighbour_arrows(self, ctx, cairo_surface, map_number,
                                 max_digit_number):
        nb_previous_pages = 4
        current_line, current_col = None, None
        for line_nb in xrange(self.nb_pages_height):
            if map_number in self.page_disposition[line_nb]:
                current_line = line_nb
                current_col = self.page_disposition[line_nb].index(
                                                             map_number)
                break
        if current_line == None:
            # page not referenced
            return

        # north arrow
        for line_nb in reversed(xrange(current_line)):
            if self.page_disposition[line_nb][current_col] != None:
                north_arrow = self.page_disposition[line_nb][current_col]
                ctx.save()
                ctx.translate(self._usable_area_width_pt/2,
                    commons.convert_pt_to_dots(self.grayed_margin_pt)/2)
                self._draw_arrow(ctx, cairo_surface,
                              north_arrow + nb_previous_pages, max_digit_number)
                ctx.restore()
                break

        # south arrow
        for line_nb in xrange(current_line + 1, self.nb_pages_height):
            if self.page_disposition[line_nb][current_col] != None:
                south_arrow = self.page_disposition[line_nb][current_col]
                ctx.save()
                ctx.translate(self._usable_area_width_pt/2,
                     self._usable_area_height_pt \
                      - commons.convert_pt_to_dots(self.grayed_margin_pt)/2)
                ctx.rotate(math.pi)
                self._draw_arrow(ctx, cairo_surface,
                      south_arrow + nb_previous_pages, max_digit_number,
                      reverse_text=True)
                ctx.restore()
                break

        # west arrow
        for col_nb in reversed(xrange(0, current_col)):
            if self.page_disposition[current_line][col_nb] != None:
                west_arrow = self.page_disposition[current_line][col_nb]
                ctx.save()
                ctx.translate(
                    commons.convert_pt_to_dots(self.grayed_margin_pt)/2,
                    self._usable_area_height_pt/2)
                ctx.rotate(-math.pi/2)
                self._draw_arrow(ctx, cairo_surface,
                               west_arrow + nb_previous_pages, max_digit_number)
                ctx.restore()
                break

        # east arrow
        for col_nb in xrange(current_col + 1, self.nb_pages_width):
            if self.page_disposition[current_line][col_nb] != None:
                east_arrow = self.page_disposition[current_line][col_nb]
                ctx.save()
                ctx.translate(
                    self._usable_area_width_pt \
                     - commons.convert_pt_to_dots(self.grayed_margin_pt)/2,
                    self._usable_area_height_pt/2)
                ctx.rotate(math.pi/2)
                self._draw_arrow(ctx, cairo_surface,
                               east_arrow + nb_previous_pages, max_digit_number)
                ctx.restore()
                break

    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 map_number, (canvas, grid) in enumerate(self.pages):

            rendered_map = canvas.get_rendered_map()
            LOG.debug('Mapnik scale: 1/%f' % rendered_map.scale_denominator())
            LOG.debug('Actual scale: 1/%f' % canvas.get_actual_scale())
            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
            draw_utils.render_page_number(ctx, map_number+4,
                                          self._usable_area_width_pt,
                                          self._usable_area_height_pt,
                                          self.grayed_margin_pt,
                                          transparent_background = True)
            self._render_neighbour_arrows(ctx, cairo_surface, map_number,
                                          len(unicode(len(self.pages)+4)))

            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),
                                              map_number+5)

        mpsir.render()

        cairo_surface.flush()

    # In multi-page mode, we only render pdf format
    @staticmethod
    def get_compatible_output_formats():
        return [ "pdf" ]

    # In multi-page mode, we only accept A4, A5 and US letter as paper
    # sizes. The goal is to render booklets, not posters.
    # The default paper size is A4 portrait
    @staticmethod
    def get_compatible_paper_sizes(bounding_box,
                                   scale=Renderer.DEFAULT_SCALE,
                                   index_position=None, hsplit=1, vsplit=1):
        valid_sizes = []
        acceptable_formats = [ 'A5', 'A4', 'US letter' ]
        for sz in ocitysmap.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, sz[0] == 'A4'))
        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
            draw_utils.draw_text_adjusted(ctx, unicode(idx+4), x, y, w, h,
                 max_char_number=len(unicode(len(overview_grid._pages_bbox)+3)),
                 text_color=(0, 0, 0, 0.6))

        ctx.restore()
Пример #14
0
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.0

    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]), float(self._map_coords[3]), dpi, rc.osmid is not None  # W  # H
        )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(
                self.rc.overlay,
                self.rc.bounding_box,
                float(self._map_coords[2]),  # W
                float(self._map_coords[3]),  # H
                dpi,
            )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(
                self.rc.overlay,
                self.rc.bounding_box,
                float(self._map_coords[2]),  # W
                float(self._map_coords[3]),  # H
                dpi,
            )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(
                self.rc.overlay,
                self.rc.bounding_box,
                float(self._map_coords[2]),  # W
                float(self._map_coords[3]),  # H
                dpi,
            )

        # Prepare the grid
        self.grid = self._create_grid(self._map_canvas, dpi)

        # 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()
        if self.rc.overlay:
            self._overlay_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 map, scaled to fit the designated area
        ##
        ctx.save()

        # prevent map background from filling the full canvas
        ctx.rectangle(map_coords_dots[0], map_coords_dots[1], map_coords_dots[2], map_coords_dots[3])
        ctx.clip()

        # 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()
        scale_factor = dpi / 72
        rendered_map = self._map_canvas.get_rendered_map()
        LOG.debug("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, scale_factor, 0, 0)
        ctx.restore()

        # Draw the rescaled Overlay
        if self.rc.overlay:
            LOG.debug("Overlay:")
            ctx.save()
            scale_factor = dpi / 72
            rendered_overlay = self._overlay_canvas.get_rendered_map()
            mapnik.render(rendered_overlay, ctx, scale_factor, 0, 0)
            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 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 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.0),
        )

        # Draw the copyright notice
        self._draw_copyright_notice(ctx, usable_area_width_dots, copyright_margin_dots, osm_date=osm_date)
        ctx.restore()

        # Draw compass rose
        # TODO: proper positioning/scaling, move to abstract renderer
        ctx.save()
        ctx.translate(50, title_margin_dots + 50)
        ctx.scale(0.33, 0.33)
        compass_path = os.path.abspath(
            os.path.join(os.path.dirname(__file__), "..", "..", "images", "compass-rose.svg")
        )
        svg = rsvg.Handle(compass_path)
        svg.render_cairo(ctx)
        ctx.restore()

        # TODO: map scale

        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.0, paper_height_mm / 10.0)
        )

        # Take index into account, when applicable
        if index_position == "side":
            paper_width_mm /= 1.0 - SinglePageRenderer.MAX_INDEX_OCCUPATION_RATIO
        elif index_position == "bottom":
            paper_height_mm /= 1.0 - 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.0, paper_height_mm / 10.0))

        # Test both portrait and landscape orientations when checking for paper
        # sizes.
        valid_sizes = []
        for name, w, h in ocitysmap.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
Пример #15
0
    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()
        self._front_page_map = front_page_map

        self._frontpage_overlay_canvases = []
        self._frontpage_overlay_effects  = {}
        for overlay in self._overlays:
            path = overlay.path.strip()
            plugin_name = path.lstrip('internal:')
            if path.startswith('internal:'):
                self._frontpage_overlay_effects[plugin_name] = self.get_plugin(plugin_name)
            else:
                ov_canvas = MapCanvas(overlay,
                                      self.rc.bounding_box,
                                      front_page_map_w,
                                      front_page_map_h,
                                      dpi,
                                      extend_bbox_to_ratio=True)
                ov_canvas.render()
                self._frontpage_overlay_canvases.append(ov_canvas)
Пример #16
0
    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]), float(self._map_coords[3]), dpi, rc.osmid is not None  # W  # H
        )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(
                self.rc.overlay,
                self.rc.bounding_box,
                float(self._map_coords[2]),  # W
                float(self._map_coords[3]),  # H
                dpi,
            )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(
                self.rc.overlay,
                self.rc.bounding_box,
                float(self._map_coords[2]),  # W
                float(self._map_coords[3]),  # H
                dpi,
            )

        # Prepare map overlay
        if self.rc.overlay:
            self._overlay_canvas = MapCanvas(
                self.rc.overlay,
                self.rc.bounding_box,
                float(self._map_coords[2]),  # W
                float(self._map_coords[3]),  # H
                dpi,
            )

        # Prepare the grid
        self.grid = self._create_grid(self._map_canvas, dpi)

        # 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()
        if self.rc.overlay:
            self._overlay_canvas.render()
Пример #17
0
    def __init__(self, db, rc, tmpdir, dpi, file_prefix,
                 index_position = 'side'):
        """
        Create the renderer.

        Args:
           db (psycopg2 DB): The GIS database
           rc (RenderingConfiguration): rendering parameters.
           tmpdir (os.path): Path to a temp dir that can hold temp files.
           file_prefix: prefix for all output file formats to be generated
           dpi (integer): output resolution for bitmap formats
           index_position (str): None or 'side' (index on side),
              'bottom' (index at bottom), 'extra_page' (index on 2nd page for PDF).
        """

        Renderer.__init__(self, db, rc, tmpdir, dpi)

        self.file_prefix = file_prefix

        # Prepare the index
        self.index_position = index_position
        if index_position is None: 
            self.street_index = None
        else:
            if rc.poi_file:
                self.street_index = PoiIndex(rc.poi_file)
            else:
                try:
                    indexer_class = globals()[rc.indexer]
                    # TODO: check that it actually implements a working indexer class
                except:
                    LOG.warning("Indexer class '%s' not found" % rc.indexer)
                    self.street_index = None
                    self.index_position = None
                else:
                    self.street_index = indexer_class(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.index_position = None


        # grid marker offset (originally used for solid grid frame,
        # now just for the letter/number overlay offset inside the map)
        self._grid_legend_margin_pt = \
            min(Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_width_pt,
                Renderer.GRID_LEGEND_MARGIN_RATIO * self.paper_height_pt)

        # reserve space for the page title if given
        if self.rc.title:
            self._title_margin_pt = Renderer.TITLE_MARGIN_RATIO * self.paper_height_pt
        else:
            self._title_margin_pt = 0

        # reserve space for the page footer
        self._copyright_margin_pt = Renderer.ANNOTATION_MARGIN_RATIO * self.paper_height_pt

        # calculate remaining usable paper space after taking header
        # and footer into account
        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)
        try:
            if ( index_position and self.street_index
                 and self.street_index.categories ):
                self._index_renderer, self._index_area \
                    = self._create_index_rendering(index_position)
            else:
                self._index_renderer, self._index_area = None, None
        except IndexDoesNotFitError as e:
                self._index_renderer, self._index_area = None, None

        self._map_coords = self._get_map_coords(index_position if self._index_area else None)

        # Prepare the map
        self._map_canvas = self._create_map_canvas(
            float(self._map_coords[2]),  # W
            float(self._map_coords[3]),  # H
            dpi,
            rc.osmid is not None )

        # Prepare overlay styles for uploaded files
        self._overlays = copy(self.rc.overlays)
        self._overlay_effects  = {}

        # Prepare overlays for all additional import files
        gpx_colors = ['red', 'blue', 'green', 'violet', 'orange']
        gpx_color_index = 0
        if self.rc.import_files:
            for (file_type, import_file) in self.rc.import_files:
                if file_type == 'gpx':
                    try:
                        color = gpx_colors[gpx_color_index]
                        gpx_color_index += 1
                        if gpx_color_index == len(gpx_colors):
                            gpx_color_index = 0
                        gpx_style = GpxStylesheet(import_file, self.tmpdir, color)
                    except Exception as e:
                        LOG.warning("GPX stylesheet error: %s" % e)
                    else:
                        self._overlays.append(gpx_style)
                elif file_type == 'umap':
                    try:
                        umap_style = UmapStylesheet(import_file, self.tmpdir)
                    except Exception as e:
                        LOG.warning("UMAP stylesheet error: %s" % e)
                    else:
                        self._overlays.append(umap_style)
                elif file_type == 'poi':
                    # TODO: refactor this special case
                    self._overlay_effects['poi_markers'] = self.get_plugin('poi_markers')
                    self.rc.poi_file = import_file
                else:
                    LOG.warning("Unsupported file type '%s' for file '%s" % (file_type, import_file))

        # Prepare map overlays
        self._overlay_canvases = []
        for overlay in self._overlays:
            path = overlay.path.strip()
            if path.startswith('internal:'):
                plugin_name = path.lstrip('internal:')
                self._overlay_effects[plugin_name] = self.get_plugin(plugin_name)
            else:
                self._overlay_canvases.append(MapCanvas(overlay,
                                              self.rc.bounding_box,
                                              float(self._map_coords[2]),  # W
                                              float(self._map_coords[3]),  # H
                                              dpi))

        # Prepare the grid
        self.grid = self._create_grid(self._map_canvas, dpi)
        if index_position: # only show grid if an actual index refers to it
            self._apply_grid(self.grid, self._map_canvas)

        # Commit the internal rendering stack of the map
        self._map_canvas.render()
        for overlay_canvas in self._overlay_canvases:
           overlay_canvas.render()