kopia lustrzana https://github.com/hholzgra/ocitysmap
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
rodzic
04ed36dd85
commit
7707cdf37e
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue