Support for complex city envelopes
[ocitysmap-polish-translation:ocitysmap-polish-translation.git] / ocitysmap2-render
1 #!/usr/bin/env python
2 # -*- coding: utf-8; mode: Python -*-
3
4 # ocitysmap, city map and street index generator from OpenStreetMap data
5 # Copyright (C) 2009  David Decotigny
6 # Copyright (C) 2009  Frédéric Lehobey
7 # Copyright (C) 2009  David Mentré
8 # Copyright (C) 2009  Maxime Petazzoni
9 # Copyright (C) 2009  Thomas Petazzoni
10 # Copyright (C) 2009  Gaël Utard
11
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU Affero General Public License as
14 # published by the Free Software Foundation, either version 3 of the
15 # License, or any later version.
16
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU Affero General Public License for more details.
21
22 # You should have received a copy of the GNU Affero General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
25 __version__ = '0.1'
26
27 import logging
28 import optparse
29 import sys, os
30
31 import ocitysmap2
32 import ocitysmap2.layoutlib.renderers
33 from coords import BoundingBox
34
35 def main():
36     logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
37
38     # Paper sizes, sorted in increasing widths
39     KNOWN_PAPER_SIZE_NAMES = \
40         map(lambda p: p[0],
41             sorted(ocitysmap2.layoutlib.PAPER_SIZES,
42                    key=lambda p: p[1]))
43
44     # Known renderer names
45     KNOWN_RENDERERS_NAMES = \
46         map(lambda r: "%s (%s)" % (r.name, r.description),
47             ocitysmap2.layoutlib.renderers.get_renderers())
48
49     usage = '%prog [options] [-b <lat1,long1 lat2,long2>|--polygon-osmid <osmid>]'
50     parser = optparse.OptionParser(usage=usage,
51                                    version='%%prog %s' % __version__)
52     parser.add_option('-C', '--config', dest='config_file', metavar='FILE',
53                       help='Specify the location of the config file.')
54     parser.add_option('-p', '--prefix', dest='output_prefix', metavar='PREFIX',
55                       help='Specify the prefix of generated files. '
56                            'Defaults to "citymap".',
57                       default='citymap')
58     parser.add_option('-f', '--format', dest='output_formats', metavar='FMT',
59                       help='Specify the output formats. Supported file '
60                            'formats: svg, svgz, pdf, ps, ps.gz, png, and csv. '
61                            'Defaults to PDF. May be specified multiple times.',
62                       action='append')
63     parser.add_option('-t', '--title', dest='output_title', metavar='TITLE',
64                       help='Specify the title displayed in the output files.',
65                       default="My Map")
66     parser.add_option('--polygon-osmid', dest='polygon_osmid', metavar='OSMID',
67                       help='OSM id representing the polygon of the city '
68                       'to render.', type="int"),
69     parser.add_option('-b', '--bounding-box', dest='bbox',  nargs=2,
70                       metavar='LAT1,LON1 LAT2,LON2',
71                       help='Bounding box (EPSG: 4326).')
72     parser.add_option('-L', '--language', dest='language',
73                       metavar='LANGUAGE_CODE',
74                       help='Language to use when generating the index'
75                            ' (default=fr_FR.UTF-8).',
76                       default='fr_FR.UTF-8')
77     parser.add_option('-s', '--stylesheet', dest='stylesheet',
78                       metavar='NAME',
79                       help="Name of the stylesheet to use. Defaults to the "
80                       "first specified in the config file.")
81     parser.add_option('-l', '--layout', dest='layout',
82                       metavar='NAME',
83                       default=KNOWN_RENDERERS_NAMES[0].split()[0],
84                       help= ("Name of the layout to use, among %s. Default: %s."
85                              % (', '.join(KNOWN_RENDERERS_NAMES),
86                                 KNOWN_RENDERERS_NAMES[0].split()[0])))
87     parser.add_option('--paper-format', metavar='FMT',
88                       help='Either "first" for the first allowed (default), '
89                       'or one of %s.'\
90                           % ', '.join(KNOWN_PAPER_SIZE_NAMES),
91                       default='first')
92
93     (options, args) = parser.parse_args()
94     if len(args):
95         parser.print_help()
96         return 1
97
98     # Make sure either -b or -c is given
99     optcnt = 0
100     for var in options.bbox, options.polygon_osmid:
101         if var:
102             optcnt += 1
103
104     if optcnt == 0:
105         parser.error("One of --bounding-box "
106                      "or --polygon-osmid is mandatory")
107
108     if optcnt > 1:
109         parser.error("Options --bounding-box "
110                      "or --polygon-osmid are exclusive")
111
112     # Parse config file and instanciate main object
113     mapper = ocitysmap2.OCitySMap([options.config_file
114                                    or os.path.join(os.environ["HOME"],
115                                                    '.ocitysmap.conf')])
116
117     # Output file formats
118     if not options.output_formats:
119         options.output_formats = ['pdf']
120     options.output_formats = set(options.output_formats)
121
122     # Parse bounding box arguments when given
123     bbox = None
124     if options.bbox:
125         try:
126             bbox = BoundingBox.parse_latlon_strtuple(options.bbox)
127         except ValueError:
128             parser.error('Invalid bounding box!\n')
129
130     # Parse OSM id when given
131     osmid = None
132     if options.polygon_osmid:
133         try:
134             bbox  = BoundingBox.parse_wkt(
135                 mapper.get_geographic_info(options.polygon_osmid)[0])
136         except LookupError:
137             parser.error('Invalid polygon OSM id!\n')
138
139     # Parse stylesheet (defaults to 1st one)
140     if options.stylesheet is None:
141         stylesheet = mapper.get_all_style_configurations()[0]
142     else:
143         try:
144             stylesheet = mapper.get_stylesheet_by_name(options.stylesheet)
145         except LookupError, ex:
146             parser.error("%s. Available stylesheets: %s."
147                          % (ex, ', '.join(map(lambda s: "%s (%s)"
148                                               % (s.name, s.description),
149                                               mapper.STYLESHEET_REGISTRY))))
150
151     # Parse rendering layout
152     if options.layout is None:
153         cls_renderer = ocitysmap2.layoutlib.renderers.get_renderers()[0]
154     else:
155         try:
156             cls_renderer = ocitysmap2.layoutlib.renderers.get_renderer_class_by_name(options.layout)
157         except LookupError, ex:
158             parser.error("%s\nAvailable layouts: %s."
159                          % (ex, ', '.join(map(lambda lo: "%s (%s)"
160                                               % (lo.name, lo.description),
161                                               ocitysmap2.layoutlib.renderers.get_renderers()))))
162
163     # Parse paper size
164     if (options.paper_format != 'first') \
165             and options.paper_format not in KNOWN_PAPER_SIZE_NAMES:
166         parser.error("Invalid paper format. Allowed formats = first, %s"
167                      % KNOWN_PAPER_SIZE_NAMES)
168
169     # Determine actual paper size
170     compat_papers \
171         = sorted(cls_renderer.get_compatible_paper_sizes(bbox,
172                                                          stylesheet.zoom_level),
173                  key = lambda p: p[1])
174     if not compat_papers:
175         parser.error("No paper size compatible with this rendering.")
176
177     paper_descr = None
178     if options.paper_format == 'first':
179         if len(compat_papers) > 1:
180             paper_descr = compat_papers[1] # First non-Best-fit
181         else:
182             paper_descr = compat_papers[0] # Best Fit
183     else:
184         # Make sure the requested paper size is in list
185         for p in compat_papers:
186             if p[0] == options.paper_format:
187                 paper_descr = p
188                 break
189     if not paper_descr:
190         parser.error("Requested paper format not compatible with rendering. Compatible paper formats are: %s."
191                      % ', '.join(map(lambda p: "%s (%.1fx%.1fcm²)" %
192                                      (p[0], p[1]/10., p[2]/10.),
193                                      compat_papers)))
194     assert paper_descr[3] or paper_descr[4] # Portrait or Landscape accepted
195
196     # Prepare the rendering config
197     rc              = ocitysmap2.RenderingConfiguration()
198     rc.title        = options.output_title
199     rc.osmid        = osmid
200     rc.bounding_box = bbox
201     rc.language     = options.language
202     rc.stylesheet   = stylesheet
203     if paper_descr[3]:
204         # Portrait allowed, used in priority
205         rc.paper_width_mm  = paper_descr[1]
206         rc.paper_height_mm = paper_descr[2]
207     else:
208         # Rotate sheet -> landscape
209         rc.paper_width_mm  = paper_descr[2]
210         rc.paper_height_mm = paper_descr[1]
211
212     # Go !...
213     mapper.render(rc, cls_renderer.name, options.output_formats,
214                   options.output_prefix)
215
216     return 0
217
218 if __name__ == '__main__':
219     sys.exit(main())