Make the OCitySMap object actually render maps

Integrate the main OCitySMap object to the layout renderers so it can
render maps. Renderers now work in typographic points.

Signed-off-by: Maxime Petazzoni <maxime.petazzoni@bulix.org>
stable
Maxime Petazzoni 2010-08-04 23:21:37 +02:00
rodzic 04ed36dd85
commit 7707cdf37e
4 zmienionych plików z 113 dodań i 40 usunięć

Wyświetl plik

@ -30,6 +30,7 @@ __version__ = '0.2'
import cairo
import ConfigParser
import gzip
import logging
import os
import psycopg2
@ -37,6 +38,7 @@ import tempfile
import coords
import i18n
import index
import renderers
l = logging.getLogger('ocitysmap')
@ -72,6 +74,8 @@ class Stylesheet:
self.grid_line_alpha = 0.8
self.grid_line_width = 3
self.zoom_level = 16
class OCitySMap:
@ -114,7 +118,6 @@ class OCitySMap:
timeout = OCitySMap.DEFAULT_REQUEST_TIMEOUT_MIN
self._set_request_timeout(timeout)
def _set_request_timeout(self, timeout_minutes=15):
"""Sets the PostgreSQL request timeout to avoid long-running queries on
the database."""
@ -175,7 +178,9 @@ class OCitySMap:
assert config.osmid or config.bbox, \
'At least an OSM ID or a bounding box must be provided!'
self._i18n = i18n.install_translation(config.language, self._locale_path)
output_formats = map(lambda x: x.lower(), output_formats)
self._i18n = i18n.install_translation(config.language,
self._locale_path)
l.info('Rendering language: %s.' % self._i18n.language_code())
# Make sure we have a bounding box
@ -186,13 +191,54 @@ class OCitySMap:
tmpdir = tempfile.mkdtemp(prefix='ocitysmap')
l.debug('Rendering in temporary directory %s' % tmpdir)
canvas, grid = renderer.create_map_canvas(config, tmpdir)
canvas.render()
street_index = index.StreetIndex(config.osmid,
canvas.get_actual_bounding_box(),
config.language, grid)
try:
surface = cairo.PDFSurface('/tmp/plain.pdf', 2000, 2000)
renderer.render(config, surface, None, tmpdir)
surface.finish()
for output_format in output_formats:
output_filename = '%s.%s' % (file_prefix, output_format)
self._render_one(config, canvas, renderer, street_index,
output_filename, output_format)
# TODO: street_index.as_csv()
finally:
self._cleanup_tempdir(tmpdir)
def _render_one(self, config, canvas, renderer, street_index,
filename, output_format):
l.info('Rendering %s...' % filename)
factory = None
if output_format == 'png':
raise NotImplementedError
elif output_format == 'svg':
factory = lambda w,h: cairo.SVGSurface(filename, w, h)
elif output_format == 'svgz':
factory = lambda w,h: cairo.SVGSurface(
gzip.GzipFile(filename, 'wb'), w, h)
elif output_format == 'pdf':
factory = lambda w,h: cairo.PDFSurface(filename, w, h)
elif output_format == 'ps':
factory = lambda w,h: cairo.PDFSurface(filename, w, h)
else:
raise ValueError, \
'Unsupported output format: %s!' % output_format.upper()
surface = factory(renderers.Renderer.convert_mm_to_pt(config.paper_width_mm),
renderers.Renderer.convert_mm_to_pt(config.paper_height_mm))
renderer.render(config, canvas, surface, street_index)
surface.finish()
if __name__ == '__main__':
s = Stylesheet()
s.name = 'osm'
@ -210,4 +256,4 @@ if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
o = OCitySMap(['/home/sam/src/python/maposmatic/ocitysmap/ocitysmap.conf.mine'])
o.render(c, renderers.PlainRenderer(), None, None)
o.render(c, renderers.PlainRenderer(), ['pdf', 'svgz'], '/tmp/mymap')

Wyświetl plik

@ -36,12 +36,15 @@ class StreetIndex:
self._language = language
self._grid = grid
# TODO: seed index data from bounding box or osmid
def render(self, surface, x, y, w, h):
"""Render the street and amenities index at the given (x,y) coordinates
into the provided Cairo surface. The index must not be larger than the
provided width and height (in pixels).
Args:
surface (cairo.Surface): the cairo surface to render into.
x (int): horizontal origin position, in pixels.
y (int): vertical origin position, in pixels.
w (int): maximum usable width for the index, in pixels.

Wyświetl plik

@ -42,13 +42,13 @@ class MapCanvas:
their respective alpha levels.
"""
def __init__(self, stylesheet, bounding_box, graphical_ratio,
zoom_level):
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)
@ -65,7 +65,8 @@ class MapCanvas:
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(zoom_level)
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))
@ -121,6 +122,7 @@ class MapCanvas:
for shape in self._shapes:
self._render_shape_file(**shape)
def get_rendered_map(self):
return self._map
def get_actual_bounding_box(self):
@ -165,13 +167,14 @@ if __name__ == '__main__':
class StylesheetMock:
def __init__(self):
self.path = '/home/sam/src/python/maposmatic/mapnik-osm/osm.xml'
self.zoom_level = 16
logging.basicConfig(level=logging.DEBUG)
# Basic unit test
bbox = coords.BoundingBox(48.7148, 2.0155, 48.6950, 2.0670)
canvas = MapCanvas(StylesheetMock(), bbox, 297.0/210, 16)
canvas = MapCanvas(StylesheetMock(), bbox, 297.0/210)
new_bbox = canvas.get_actual_bounding_box()

Wyświetl plik

@ -22,6 +22,7 @@
# 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 cairo
import logging
import mapnik
import os
@ -57,51 +58,69 @@ class Renderer:
('40x40cm', 400, 400),
]
def render(self, rc, surface, street_index, zoom_level, tmpdir):
def _create_map_canvas(self, rc, graphical_ratio, tmpdir):
canvas = map_canvas.MapCanvas(rc.stylesheet, rc.bounding_box,
graphical_ratio)
_grid = grid.Grid(canvas.get_actual_bounding_box())
grid_shape = _grid.generate_shape_file(os.path.join(tmpdir, 'grid.shp'))
canvas.add_shape_file(grid_shape,
rc.stylesheet.grid_line_color,
rc.stylesheet.grid_line_alpha,
rc.stylesheet.grid_line_width)
return canvas, _grid
def create_map_canvas(self, rc, tmpdir):
"""Returns the map canvas object and the grid object that has been
overlayed on the created map.
Args:
rc (RenderingConfiguration): the rendering configuration.
tmpdir (path): path to a directory for temporary shape files.
"""
raise NotImplementedError
def render(self, rc, canvas, surface, street_index):
raise NotImplementedError
def get_compatible_paper_sizes(self, bounding_box, zoom_level,
resolution_km_in_mm):
raise NotImplementedError
@staticmethod
def convert_mm_to_pt(mm):
return ((mm/10.0) / 2.54) * 72
class PlainRenderer(Renderer):
def __init__(self):
self.name = 'plain'
self.description = 'A basic, full-page layout for the map.'
def render(self, rc, surface, street_index, zoom_level, tmpdir):
def create_map_canvas(self, rc, tmpdir):
return self._create_map_canvas(rc, (float(rc.paper_width_mm) /
rc.paper_height_mm), tmpdir)
def render(self, rc, canvas, surface, street_index):
"""..."""
l.info('PlainRenderer rendering on %dx%dmm paper.' %
(rc.paper_width_mm, rc.paper_height_mm))
canvas = map_canvas.MapCanvas(rc.stylesheet, rc.bounding_box,
(float(rc.paper_width_mm) /
rc.paper_height_mm),
zoom_level)
rendered_map = canvas.get_rendered_map()
grid_shape = (grid.Grid(canvas.get_actual_bounding_box())
.generate_shape_file(os.path.join(tmpdir, 'grid.shp')))
canvas.add_shape_file(grid_shape,
rc.stylesheet.grid_line_color,
rc.stylesheet.grid_line_alpha,
rc.stylesheet.grid_line_width)
rendered_map = canvas.render()
ctx = cairo.Context(surface)
def mm_to_pt(mm):
return ((mm/10.0) / 2.54) * 72
ctx.scale(mm_to_pt(rc.paper_width_mm) / rendered_map.width,
mm_to_pt(rc.paper_height_mm) / rendered_map.height)
mapnik.render(rendered_map, ctx)
surface.flush()
ctx.scale(Renderer.convert_mm_to_pt(rc.paper_width_mm) /
rendered_map.width,
Renderer.convert_mm_to_pt(rc.paper_height_mm) /
rendered_map.height)
# TODO: scale
# TODO: compass rose
mapnik.render(rendered_map, ctx)
surface.flush()
return surface
def get_compatible_paper_sizes(self, bounding_box, zoom_level,
@ -148,7 +167,8 @@ if __name__ == '__main__':
plain = PlainRenderer()
papers = plain.get_compatible_paper_sizes(bbox, zoom, resolution_km_in_mm=150)
papers = plain.get_compatible_paper_sizes(bbox, zoom,
resolution_km_in_mm=110)
print 'Compatible paper sizes:'
for p in papers:
print ' * %s (%.1fx%.1fcm)' % (p[0], p[1]/10.0, p[2]/10.0)
@ -160,6 +180,7 @@ if __name__ == '__main__':
self.grid_line_color = 'black'
self.grid_line_alpha = 0.9
self.grid_line_width = 2
self.zoom_level = 16
class RenderingConfigurationMock:
def __init__(self):
@ -171,13 +192,13 @@ if __name__ == '__main__':
config = RenderingConfigurationMock()
def mm_to_pt(mm):
return ((mm/10.0) / 2.54) * 72
surface = cairo.PDFSurface('/tmp/plain.pdf',
mm_to_pt(config.paper_width_mm),
mm_to_pt(config.paper_height_mm))
plain.render(config, surface, None, zoom, '/tmp')
Renderer.convert_mm_to_pt(config.paper_width_mm),
Renderer.convert_mm_to_pt(config.paper_height_mm))
canvas, _ = plain.create_map_canvas(config, '/tmp')
canvas.render()
plain.render(config, canvas, surface, None)
surface.finish()