1 | #!/usr/bin/env python |
---|
2 | |
---|
3 | import os |
---|
4 | import re |
---|
5 | import sys |
---|
6 | import glob |
---|
7 | import optparse |
---|
8 | import platform |
---|
9 | import tempfile |
---|
10 | |
---|
11 | __version__ = '0.1.0' |
---|
12 | |
---|
13 | REASONABLE_DEFAULTS = { |
---|
14 | 'epsg':'900913', # default osm2pgsql import srid |
---|
15 | 'world_boundaries':'world_boundaries', # relative path |
---|
16 | 'symbols':'symbols', # relative path |
---|
17 | 'prefix':'planet_osm', # default osm2pgsql table prefix |
---|
18 | 'extent':'-20037508,-19929239,20037508,19929239', # world in merc |
---|
19 | 'inc':'inc/*.template', # search path for inc templates to parse |
---|
20 | 'estimate_extent':'false', |
---|
21 | 'extent':'-20037508,-19929239,20037508,19929239', |
---|
22 | } |
---|
23 | |
---|
24 | def color_text(color, text): |
---|
25 | if os.name == 'nt': |
---|
26 | return text |
---|
27 | return "\033[9%sm%s\033[0m" % (color,text) |
---|
28 | |
---|
29 | class Params: |
---|
30 | def __init__(self,params,accept_none): |
---|
31 | self.params = params |
---|
32 | self.accept_none = accept_none |
---|
33 | self.missing = [] |
---|
34 | |
---|
35 | def blend_with_env(self,opts): |
---|
36 | d = {} |
---|
37 | |
---|
38 | for p in self.params: |
---|
39 | env_var_name = 'MAPNIK_%s' % p.upper() |
---|
40 | |
---|
41 | # first pull from passed options... |
---|
42 | if not opts.get(p) is None: |
---|
43 | d[p] = opts[p] |
---|
44 | |
---|
45 | # then try to pull from environment settings |
---|
46 | elif not os.environ.get(env_var_name) is None: |
---|
47 | d[p] = os.environ[env_var_name] |
---|
48 | |
---|
49 | # then assign any reasonable default values... |
---|
50 | elif p in REASONABLE_DEFAULTS.keys(): |
---|
51 | d[p] = REASONABLE_DEFAULTS[p] |
---|
52 | # if --accept-none is passed then we assume |
---|
53 | # its a paramater that mapnik likely does not need |
---|
54 | # and will ignore if it is an empty string (e.g. db values) |
---|
55 | elif self.accept_none: |
---|
56 | d[p] = '' |
---|
57 | else: |
---|
58 | self.missing.append(p) |
---|
59 | return d |
---|
60 | |
---|
61 | def serialize(xml,options): |
---|
62 | try: |
---|
63 | import mapnik |
---|
64 | except: |
---|
65 | sys.exit(color_text(1,'Error: saving xml requires Mapnik python bindings to be installed')) |
---|
66 | m = mapnik.Map(1,1) |
---|
67 | if options.from_string: |
---|
68 | mapnik.load_map_from_string(m,xml,True) |
---|
69 | else: |
---|
70 | mapnik.load_map(m,xml,True) |
---|
71 | if options.output: |
---|
72 | mapnik.save_map(m,options.output) |
---|
73 | else: |
---|
74 | if hasattr(mapnik,'mapnik_version') and mapnik.mapnik_version() >= 700: |
---|
75 | print mapnik.save_map_to_string(m) |
---|
76 | else: |
---|
77 | sys.exit(color_text(1,'Minor error: printing XML to stdout requires Mapnik >=0.7.0, please provide a second argument to save the output to a file')) |
---|
78 | |
---|
79 | def validate(params,parser): |
---|
80 | if not os.path.exists(params['world_boundaries']): |
---|
81 | parser.error("Directory '%s' used for param '%s' not found" % (params['world_boundaries'],'world_boundaries')) |
---|
82 | supported_srs = [900913,4326] |
---|
83 | if not int(params['epsg']) in supported_srs: |
---|
84 | parser.error('Sorry only supported projections are: %s' % supported_srs) |
---|
85 | if not params['estimate_extent'] == 'false': |
---|
86 | params['extent'] = '' |
---|
87 | |
---|
88 | # set up parser... |
---|
89 | parser = optparse.OptionParser(usage="""%prog [template xml] [output xml] <parameters> |
---|
90 | |
---|
91 | Full help: |
---|
92 | $ %prog -h (or --help for possible options) |
---|
93 | |
---|
94 | Read 'osm.xml' and print resulting xml to stdout: |
---|
95 | $ %prog osm.xml |
---|
96 | |
---|
97 | Read template, save output xml, and pass variables as options |
---|
98 | $ %prog osm.xml my_osm.xml --dbname spain --user postgres --host ''""", version='%prog ' + __version__) |
---|
99 | |
---|
100 | |
---|
101 | if __name__ == '__main__': |
---|
102 | |
---|
103 | # custom parse of includes directory if supplied... |
---|
104 | if '--inc' in sys.argv: |
---|
105 | idx = sys.argv.index('--inc') |
---|
106 | if not len(sys.argv) > (idx+1) or '--' in sys.argv[idx+1]: |
---|
107 | parser.error("--inc argument requires a path to a directory as an argument") |
---|
108 | else: |
---|
109 | search_path = os.path.join(sys.argv[idx+1],'*.template') |
---|
110 | else: |
---|
111 | search_path = REASONABLE_DEFAULTS['inc'] |
---|
112 | |
---|
113 | inc_dir = os.path.dirname(search_path) |
---|
114 | parser.add_option('--inc', dest='inc', help="Includes dir (default: '%s')" % inc_dir ) |
---|
115 | |
---|
116 | parser.add_option('--accept-none', dest='accept_none', action='store_true', help="Interpret lacking value as unneeded") |
---|
117 | |
---|
118 | if not os.path.exists(inc_dir): |
---|
119 | parser.error("The 'inc' path you gave is bogus!") |
---|
120 | |
---|
121 | # get all the includes |
---|
122 | includes = glob.glob(search_path) |
---|
123 | |
---|
124 | if not includes: |
---|
125 | parser.error("Can't find include templates, please provide search path using '--inc' , currently using '%s'" % search_path) |
---|
126 | |
---|
127 | p = re.compile('.*%\((\w+)\)s.*') |
---|
128 | |
---|
129 | text = '' |
---|
130 | for xml in includes: |
---|
131 | text += file(xml,'rb').read() |
---|
132 | |
---|
133 | # find all variables in template includes |
---|
134 | matches = p.findall(text) |
---|
135 | |
---|
136 | if not matches: |
---|
137 | parser.error(color_text(1,"Can't properly parse out variables in include templates.\nMake sure they are all wrapped like '%(variable)s'")) |
---|
138 | |
---|
139 | # look ahead and build up --help text... |
---|
140 | #import pdb;pdb.set_trace() |
---|
141 | p = Params(matches,accept_none=False) |
---|
142 | blended = p.blend_with_env({}) |
---|
143 | for var in matches: |
---|
144 | msg = "(default: '%(" + var + ")s')" |
---|
145 | if var in blended: |
---|
146 | default = msg % blended |
---|
147 | else: |
---|
148 | default = ''#msg % {var:'None'} |
---|
149 | parser.add_option('--%s' % var, dest=var, |
---|
150 | help="Set value of '%s' %s" % (var,default)) |
---|
151 | |
---|
152 | # now, actually run the tool... |
---|
153 | (options, args) = parser.parse_args() |
---|
154 | p = Params(matches,options.accept_none) |
---|
155 | blended = p.blend_with_env(options.__dict__) |
---|
156 | |
---|
157 | help_text = "\n\nNote: use --accept-none to pass blank values for other parameters " |
---|
158 | if p.missing: |
---|
159 | parser.error(color_text(1,"\nPlease provide the following parameters values (or set as env variables):\n%s" % ''.join([" --%s 'value' " % v for v in p.missing]) + help_text)) |
---|
160 | |
---|
161 | validate(blended,parser) |
---|
162 | |
---|
163 | for xml in includes: |
---|
164 | template = file(xml,'rb') |
---|
165 | new_name = xml.replace('.template','') |
---|
166 | new_file = file(new_name,'wb') |
---|
167 | try: |
---|
168 | new_file.write(template.read() % blended) |
---|
169 | except ValueError, e: |
---|
170 | parser.error(color_text(1,"\n%s (found in %s)" % (e,xml))) |
---|
171 | template.close() |
---|
172 | new_file.close() |
---|
173 | |
---|
174 | options.output = None |
---|
175 | options.from_string = False |
---|
176 | template_xml = None |
---|
177 | # accepting XML as stream... |
---|
178 | if not sys.stdin.isatty(): |
---|
179 | template_xml = sys.stdin.read() |
---|
180 | options.from_string = True |
---|
181 | if len(args) > 0: |
---|
182 | options.output = args[0] |
---|
183 | elif len(args) == 0: |
---|
184 | template_xml = None |
---|
185 | options.output = None |
---|
186 | else: |
---|
187 | template_xml = args[0] |
---|
188 | if len(args) > 1: |
---|
189 | options.output = args[1] |
---|
190 | |
---|
191 | if template_xml: |
---|
192 | serialize(template_xml,options) |
---|
193 | else: |
---|
194 | print 'Include files written successfully! Pass the osm.xml file as an argument if you want to serialize a new version or test reading the XML' |
---|