ocitysmap/ocitysmap2/map_canvas.py

191 wiersze
7.3 KiB
Python

# -*- coding: utf-8 -*-
# ocitysmap, city map and street index generator from OpenStreetMap data
# Copyright (C) 2010 David Decotigny
# Copyright (C) 2010 Frédéric Lehobey
# Copyright (C) 2010 Pierre Mauduit
# Copyright (C) 2010 David Mentré
# Copyright (C) 2010 Maxime Petazzoni
# Copyright (C) 2010 Thomas Petazzoni
# Copyright (C) 2010 Gaël Utard
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
import mapnik
import os
import coords
import shapes
l = logging.getLogger('ocitysmap')
# TODO: use 4002 instead?
_MAIN_PROJECTION = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over"
class MapCanvas:
"""
The MapCanvas renders a geographic bounding box into a Cairo surface of a
given width and height (in pixels). Shape files can be overlayed on the
map; the order they are added to the map being important with regard to
their respective alpha levels.
"""
def __init__(self, stylesheet, bounding_box, graphical_ratio):
"""Initialize the map canvas for rendering.
Args:
stylesheet (Stylesheet): map stylesheet.
bounding_box (coords.BoundingBox): geographic bounding box.
graphical_ratio (float): ratio of the map area (width/height).
"""
self._proj = mapnik.Projection(_MAIN_PROJECTION)
# TODO: document!
orig_envelope = self._project_envelope(bounding_box)
off_x, off_y, width, height = self._fix_bbox_ratio(
orig_envelope.minx, orig_envelope.miny,
orig_envelope.width(), orig_envelope.height(),
graphical_ratio)
envelope = mapnik.Envelope(off_x, off_y,
off_x+width, off_y+height)
self._geo_bbox = self._inverse_envelope(envelope)
g_width, g_height = self._geo_bbox.get_pixel_size_for_zoom_factor(
stylesheet.zoom_level)
l.debug('Corrected bounding box from %s to %s (ratio: %.2f).' %
(bounding_box, self._geo_bbox, graphical_ratio))
# Create the Mapnik map with the corrected width and height and zoom to
# the corrected bounding box ('envelope' in the Mapnik jargon)
self._map = mapnik.Map(g_width, g_height, _MAIN_PROJECTION)
mapnik.load_map(self._map, stylesheet.path)
self._map.zoom_to_box(envelope)
# Added shapes to render
self._shapes = []
l.info('MapCanvas rendering map on %dx%dpx.' % (g_width, g_height))
def _fix_bbox_ratio(self, off_x, off_y, width, height, dest_ratio):
cur_ratio = float(width)/height
if cur_ratio < dest_ratio:
w = width
width *= float(dest_ratio)/cur_ratio
off_x -= (width - w)/2.0
else:
h = height
height *= float(cur_ratio)/dest_ratio
off_y -= (height - h)/2.0
return map(int, (off_x, off_y, width, height))
def add_shape_file(self, shape_file, str_color='grey', alpha=0.5,
line_width=1.0):
"""
Args:
shape_file (shapes.ShapeFile): ...
"""
col = mapnik.Color(str_color)
col.a = int(255 * alpha)
self._shapes.append({'shape_file': shape_file,
'color': col,
'line_width': line_width})
l.debug('Added shape file %s to map canvas as layer %s.' %
(shape_file.get_filepath(), shape_file.get_layer_name()))
return shape_file
def render(self):
"""Render the map in memory with all the added shapes. Returns the
corresponding mapnik.Map object."""
# Add all shapes to the map
for shape in self._shapes:
self._render_shape_file(**shape)
def get_rendered_map(self):
return self._map
def get_actual_bounding_box(self):
"""Returns the actual greographic bounding box that will be rendered by
Mapnik."""
return self._geo_bbox
def _render_shape_file(self, shape_file, color, line_width):
shape_file.flush()
shpid = os.path.basename(shape_file.get_filepath())
s,r = mapnik.Style(), mapnik.Rule()
r.symbols.append(mapnik.PolygonSymbolizer(color))
r.symbols.append(mapnik.LineSymbolizer(color, line_width))
s.rules.append(r)
self._map.append_style('style_%s' % shpid, s)
layer = mapnik.Layer(shpid)
layer.datasource = mapnik.Shapefile(file=shape_file.get_filepath())
layer.styles.append('style_%s' % shpid)
self._map.layers.append(layer)
def _project_envelope(self, bbox):
"""Project the given bounding box into the rendering projection."""
envelope = mapnik.Envelope(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.Envelope(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)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
class StylesheetMock:
def __init__(self):
self.path = '/home/sam/src/python/maposmatic/mapnik-osm/osm.xml'
self.zoom_level = 16
bbox = coords.BoundingBox(48.7148, 2.0155, 48.6950, 2.0670)
canvas = MapCanvas(StylesheetMock(), bbox, 297.0/210)
new_bbox = canvas.get_actual_bounding_box()
print bbox
print new_bbox
canvas.add_shape_file(
shapes.LineShapeFile(new_bbox, '/tmp/mygrid.shp', 'grid')
.add_vert_line(2.04)
.add_horiz_line(48.7),
'red', 0.3, 10.0)
canvas.add_shape_file(
shapes.PolyShapeFile(new_bbox, '/tmp/mypoly.shp', 'shade')
.add_shade_from_wkt('POLYGON((2.04537559754772 48.702794853359,2.0456929723376 48.7033682610593,2.0457757970068 48.7037022715908,2.04577876144723 48.7043963708738,2.04589724923321 48.7043963708738,2.04589428479277 48.704519562418,2.04746445007788 48.7044706533954,2.04723043894637 48.7024665875529,2.04674876229103 48.7024238422904,2.04615641319268 48.702500973452,2.04537559754772 48.702794853359))'),
'blue', 0.3)
canvas.render()
mapnik.render_to_file(canvas.get_rendered_map(), '/tmp/mymap.png', 'png')