From 17298cd3feb8d5925d351ec47bd3af6e6f287cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Loks?= Date: Fri, 30 Mar 2012 18:51:17 +0200 Subject: [PATCH] Add an overview page in the multi-page renderer. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * get the right bounding box * create the overview canvas * draw the map and the appropriate shade * draw boxes for each pages (OverwiewGrid) * add the overview to the list of pages to be rendered Signed-off-by: Étienne Loks --- ocitysmap2/layoutlib/multi_page_renderer.py | 47 ++++++++- ocitysmap2/maplib/overview_grid.py | 103 ++++++++++++++++++++ ocitysmap2/maplib/shapes.py | 29 ++++++ 3 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 ocitysmap2/maplib/overview_grid.py diff --git a/ocitysmap2/layoutlib/multi_page_renderer.py b/ocitysmap2/layoutlib/multi_page_renderer.py index e214860..cc63139 100644 --- a/ocitysmap2/layoutlib/multi_page_renderer.py +++ b/ocitysmap2/layoutlib/multi_page_renderer.py @@ -28,7 +28,10 @@ import tempfile import math import sys import cairo -import mapnik +try: + import mapnik2 as mapnik +except ImportError: + import mapnik import coords import locale import pangocairo @@ -41,6 +44,7 @@ from abstract_renderer import Renderer from ocitysmap2.maplib.map_canvas import MapCanvas from ocitysmap2.maplib.grid import Grid +from ocitysmap2.maplib.overview_grid import OverviewGrid from indexlib.indexer import StreetIndex from indexlib.multi_page_renderer import MultiPageStreetIndexRenderer @@ -133,7 +137,7 @@ class MultiPageRenderer(Renderer): self._usable_area_height_pt + (self._usable_area_height_pt - overlap_margin_pt) * (nb_pages_height - 1) # Convert this paper area available in the number of Mercator - # meters that can we rendered on the map + # 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 = \ @@ -186,8 +190,45 @@ class MultiPageRenderer(Renderer): # for i, (bb, bb_inner) in enumerate(bboxes): # print bb.as_javascript(name="p%d" % i) - # Create the map canvas for each page self.pages = [] + # Create an overview map + + # Create the gray shape around the map + first_bbox, last_bbox = bboxes[0][0], bboxes[-1][0] + overview_coord = list(first_bbox.get_top_left()) + \ + list(last_bbox.get_bottom_right()) + overview_bb = coords.BoundingBox(*overview_coord + ).create_expanded(0.001, 0.001) + + exterior = shapely.wkt.loads(overview_bb.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) + + # Create the grid + map_grid = OverviewGrid(overview_bb, + [bb for bb, bb_inner in bboxes], self.rc.i18n.isrtl()) + + grid_shape = map_grid.generate_shape_file( + os.path.join(self.tmpdir, 'grid_overview.shp')) + + # Create one canvas for the current page + map_canvas = MapCanvas(self.rc.stylesheet, + overview_bb, graphical_ratio=None) + + map_canvas.add_shape_file(shade) + 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 map canvas for each page indexes = [] for i, (bb, bb_inner) in enumerate(bboxes): diff --git a/ocitysmap2/maplib/overview_grid.py b/ocitysmap2/maplib/overview_grid.py new file mode 100644 index 0000000..d196036 --- /dev/null +++ b/ocitysmap2/maplib/overview_grid.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# ocitysmap, city map and street index generator from OpenStreetMap data + +import logging +import math + +import shapes + +l = logging.getLogger('ocitysmap') + +class OverviewGrid: + """ + The OverviewGrid class draw the grid overlayed on the overview map of a + multi-page render. + """ + + def __init__(self, bounding_box, pages_bounding_boxes, rtl=False): + """Creates a new grid for the given bounding boxes. + + Args: + bounding_box (coords.BoundingBox): the map bounding box. + bounding_box (list of coords.BoundingBox): bounding boxes of the + pages. + rtl (boolean): whether the map is rendered in right-to-left mode or + not. Defaults to False. + """ + + self._bbox = bounding_box + self._pages_bbox = pages_bounding_boxes + self.rtl = rtl + self._height_m, self._width_m = bounding_box.spheric_sizes() + + l.info('Laying out of overview grid on %.1fx%.1fm area...' % + (self._width_m, self._height_m)) + + self._pages_wkt = [bb.as_wkt() for bb in self._pages_bbox] + + self.horiz_count = 1 + self.vert_count = 1 + self.horizontal_labels = ['plouf'] + self.vertical_labels = ['plouf'] + + def generate_shape_file(self, filename): + """Generates the grid shapefile with all the horizontal and + vertical lines added. + + Args: + filename (string): path to the temporary shape file that will be + generated. + Returns the ShapeFile object. + """ + + # Use a slightly larger bounding box for the shape file to accomodate + # for the small imprecisions of re-projecting. + g = shapes.BoxShapeFile(self._bbox.create_expanded(0.001, 0.001), + filename, 'grid') + map(g.add_box, self._pages_bbox) + return g + + def _gen_horizontal_square_label(self, x): + """Generates a human-readable label for the given horizontal square + number. For example: + 1 -> A + 2 -> B + 26 -> Z + 27 -> AA + 28 -> AB + ... + """ + if self.rtl: + x = len(self._vertical_lines) - x + + label = '' + while x != -1: + label = chr(ord('A') + x % 26) + label + x = x/26 - 1 + return label + + def _gen_vertical_square_label(self, x): + """Generate a human-readable label for the given vertical square + number. Since we put numbers verticaly, this is simply x+1.""" + return str(x + 1) + + def get_location_str(self, lattitude, longitude): + """ + Translate the given lattitude/longitude (EPSG:4326) into a + string of the form "CA42" + """ + hdelta = min(abs(longitude - self._bbox.get_top_left()[1]), + self._horiz_angle_span) + hlabel = self.horizontal_labels[int(hdelta / self._horiz_unit_angle)] + + vdelta = min(abs(lattitude - self._bbox.get_top_left()[0]), + self._vert_angle_span) + vlabel = self.vertical_labels[int(vdelta / self._vert_unit_angle)] + + return "%s%s" % (hlabel, vlabel) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + pass diff --git a/ocitysmap2/maplib/shapes.py b/ocitysmap2/maplib/shapes.py index a4739f1..fe915fd 100644 --- a/ocitysmap2/maplib/shapes.py +++ b/ocitysmap2/maplib/shapes.py @@ -125,6 +125,35 @@ class LineShapeFile(_ShapeFile): self._add_feature(line) return self +class BoxShapeFile(LineShapeFile): + """ + Shape file for Box geometries. + """ + + def add_box(self, box): + top_left, bottom_right = box.get_top_left(), box.get_bottom_right() + + line = ogr.Geometry(type = ogr.wkbLineString) + line.AddPoint_2D(*list(reversed(top_left))) + line.AddPoint_2D(bottom_right[1], top_left[0]) + self._add_feature(line) + + line = ogr.Geometry(type = ogr.wkbLineString) + line.AddPoint_2D(bottom_right[1], top_left[0]) + line.AddPoint_2D(*list(reversed(bottom_right))) + self._add_feature(line) + + line = ogr.Geometry(type = ogr.wkbLineString) + line.AddPoint_2D(*list(reversed(bottom_right))) + line.AddPoint_2D(top_left[1], bottom_right[0]) + self._add_feature(line) + + line = ogr.Geometry(type = ogr.wkbLineString) + line.AddPoint_2D(top_left[1], bottom_right[0]) + line.AddPoint_2D(*list(reversed(top_left))) + self._add_feature(line) + return self + class PolyShapeFile(_ShapeFile): """ Shape file for Polygon geometries.