ms_export.py
1 |
#***************************************************************************
|
---|---|
2 |
# ms_export.py
|
3 |
# --------------------------------------
|
4 |
# Date : Sun Sep 16 12:33:46 AKDT 2007
|
5 |
# Copyright : (C) 2008 by Gary E. Sherman
|
6 |
# Email : sherman at mrcc dot com
|
7 |
#***************************************************************************
|
8 |
#* *
|
9 |
#* This program is free software; you can redistribute it and/or modify *
|
10 |
#* it under the terms of the GNU General Public License as published by *
|
11 |
#* the Free Software Foundation; either version 2 of the License, or *
|
12 |
#* (at your option) any later version. *
|
13 |
#* *
|
14 |
#***************************************************************************/
|
15 |
# This class exports a QGIS project file to a mapserver .map file.
|
16 |
# All the work is done in the writeMapFile method. The msexport binary
|
17 |
# presents a Qt based GUI that collects the needed information for this
|
18 |
# script.
|
19 |
# Matthew Perry contributed major portions of this work.
|
20 |
# Adapted by Erik van de Pol
|
21 |
#
|
22 |
# CHANGES SHOULD NOT BE MADE TO THE writeMapFile METHOD UNLESS YOU
|
23 |
# ARE CHANGING THE QgsMapserverExport CLASS AND YOU KNOW WHAT YOU ARE
|
24 |
# DOING
|
25 |
|
26 |
from xml.dom import minidom |
27 |
from string import * |
28 |
import platform |
29 |
from qgis.core import QgsDataSourceURI |
30 |
from qgis.core import QgsMapLayerRegistry |
31 |
#from qgis.core import QgsProject
|
32 |
from PyQt4.QtCore import QString |
33 |
from PyQt4.QtCore import QVariant |
34 |
|
35 |
|
36 |
# symbol map
|
37 |
qgis2map_symbol = { |
38 |
"hard:circle" : "circle", |
39 |
"hard:triangle" : "triangle", |
40 |
"hard:equilateral_triangle" : "equilateral-triangle", |
41 |
"hard:rectangle" : "square", |
42 |
"hard:regular_star" : "star", |
43 |
"hard:diamond" : "diamond" |
44 |
} |
45 |
|
46 |
# alignment/position map
|
47 |
qgis2map_aligment2position = { |
48 |
"center" : "cc", |
49 |
"above" : "uc", |
50 |
"right" : "cr", |
51 |
"below" : "lc", |
52 |
"left" : "cl", |
53 |
"aboveright" : "ur", |
54 |
"belowright" : "lr", |
55 |
"belowleft" : "ll", |
56 |
"aboveleft" : "ul" |
57 |
} |
58 |
|
59 |
# the keys are fonts that must be available in QGis
|
60 |
# the values in this dictionary must correspond to
|
61 |
# the fonts in the file denoted by the FONTSET in the mapfile
|
62 |
#
|
63 |
# "MS Shell Dlg 2" is the default label font in QGis.
|
64 |
# The automatic mapping to "Tahoma" is correct for Windows 2000, Windows XP,
|
65 |
# Windows Server 2003, Windows Vista and Windows 7.
|
66 |
# See: http://msdn.microsoft.com/en-us/library/dd374112%28VS.85%29.aspx
|
67 |
qgis2map_fontset = { |
68 |
"Arial" : "arial", |
69 |
"Courier" : "courier", |
70 |
"Georgia" : "georgia", |
71 |
"Times New Roman" : "times", |
72 |
"Trebuchet MS" : "trebuchet_ms", |
73 |
"Verdana" : "verdana", |
74 |
"Tahoma" : "tahoma", |
75 |
"MS Shell Dlg 2" : "tahoma" |
76 |
} |
77 |
# Note that tahoma-italic and tahoma-bold-italic do not exist.
|
78 |
# Therefore a mapping to the corresponding verdana-variants is made
|
79 |
# in the fonts file pointed to by the fontsPath. Feel free to map to another font there.
|
80 |
|
81 |
bool2str = {True: "true", False: "false"} |
82 |
|
83 |
# This is a magic number now. Rationale?
|
84 |
symbolSizeMultiplier = 3.5
|
85 |
|
86 |
|
87 |
class Qgis2MapDefaults: pass |
88 |
|
89 |
defaults = Qgis2MapDefaults() |
90 |
|
91 |
defaults.fontsPath = "./fonts/fonts.txt"
|
92 |
defaults.symbolsPath = "./symbols/symbols.txt"
|
93 |
if platform.system() == "Windows": |
94 |
defaults.mapServerUrl = "http://my.host.com/cgi-bin/mapserv.exe"
|
95 |
else:
|
96 |
defaults.mapServerUrl = "http://localhost/cgi-bin/mapserv"
|
97 |
defaults.width = "100"
|
98 |
defaults.height = "100"
|
99 |
|
100 |
defaults.dump = True
|
101 |
defaults.force = True
|
102 |
defaults.partials = True
|
103 |
defaults.antialias = True
|
104 |
|
105 |
|
106 |
|
107 |
class Qgis2Map: |
108 |
def __init__(self, mapFile): |
109 |
self.mapFile = mapFile
|
110 |
# init the other members that are not set by the constructor
|
111 |
self.mapServerUrl = defaults.mapServerUrl
|
112 |
self.fontsPath = defaults.fontsPath
|
113 |
self.symbolsPath = defaults.symbolsPath
|
114 |
self.units = '' |
115 |
self.imageType = '' |
116 |
self.mapName = '' |
117 |
self.width = defaults.width
|
118 |
self.height = defaults.height
|
119 |
self.minimumScale = '' |
120 |
self.maximumScale = '' |
121 |
self.template = '' |
122 |
self.header = '' |
123 |
self.footer = '' |
124 |
self.dump = bool2str[defaults.dump]
|
125 |
self.force = bool2str[defaults.force]
|
126 |
self.antialias = bool2str[defaults.antialias]
|
127 |
self.partials = bool2str[defaults.partials]
|
128 |
self.symbolQueue = {}
|
129 |
|
130 |
def setQgsProject(self, projectFileName): |
131 |
try:
|
132 |
self.projectFileName = projectFileName
|
133 |
# create the DOM
|
134 |
self.qgs = minidom.parse(unicode(self.projectFileName)) |
135 |
return True |
136 |
except:
|
137 |
return False |
138 |
|
139 |
# Set the options collected from the GUI
|
140 |
def setOptions(self, msUrl, units, image, mapname, width, height, template, header, footer, dump, force, antialias, partials, exportLayersOnly, fontsPath, symbolsPath): |
141 |
if msUrl.encode('utf-8') != "": |
142 |
self.mapServerUrl = msUrl.encode('utf-8') |
143 |
|
144 |
if fontsPath.encode('utf-8') != "": |
145 |
self.fontsPath = fontsPath.encode('utf-8') |
146 |
|
147 |
if symbolsPath.encode('utf-8') != "": |
148 |
self.symbolsPath = symbolsPath.encode('utf-8') |
149 |
|
150 |
if width.encode('utf-8') != "": |
151 |
self.width = width.encode('utf-8') |
152 |
|
153 |
if height.encode('utf-8') != "": |
154 |
self.height = height.encode('utf-8') |
155 |
|
156 |
self.units = units.encode('utf-8') |
157 |
self.imageType = image.encode('utf-8') |
158 |
self.mapName = mapname.encode('utf-8') |
159 |
|
160 |
#self.minimumScale = minscale
|
161 |
#self.maximumScale = maxscale
|
162 |
# TEMPLATE is needed for getFeatureInfo requests in WMS:
|
163 |
# always set someting ...
|
164 |
template = template.encode('utf-8')
|
165 |
if template == "": |
166 |
template = "fooOnlyForWMSGetFeatureInfo"
|
167 |
self.template = template
|
168 |
self.header = header.encode('utf-8') |
169 |
self.footer = footer.encode('utf-8') |
170 |
#print units, image, mapname, width, height, template, header, footer
|
171 |
self.dump = bool2str[dump]
|
172 |
self.force = bool2str[force]
|
173 |
self.antialias = bool2str[antialias]
|
174 |
self.partials = bool2str[partials]
|
175 |
self.exportLayersOnly = exportLayersOnly
|
176 |
|
177 |
# method to check the project file for the exitence of postgis layers
|
178 |
# if so it should be loaded in qgis before exporting, because a connection
|
179 |
# to the database is needed to determine primary keys etc etc
|
180 |
def projectHasPostgisLayers(self): |
181 |
# get the list of maplayer nodes
|
182 |
maplayers = self.qgs.getElementsByTagName("maplayer") |
183 |
for lyr in maplayers: |
184 |
try:
|
185 |
providerString = lyr.getElementsByTagName("provider")[0].childNodes[0].nodeValue.encode('utf-8') |
186 |
except:
|
187 |
print "ERROR getting provider string from layer" |
188 |
# if providerString is null
|
189 |
providerString = ''
|
190 |
if providerString == 'postgres': |
191 |
#print "POSTGIS LAYER !!"
|
192 |
return True |
193 |
return False |
194 |
|
195 |
|
196 |
## All real work happens here by calling methods to write the
|
197 |
## various sections of the map file
|
198 |
def writeMapFile(self): |
199 |
# open the output file
|
200 |
print "creating the map file" |
201 |
self.outFile = open(self.mapFile, 'w') |
202 |
logmsg = "Starting\n"
|
203 |
|
204 |
if self.exportLayersOnly == False: |
205 |
# write the general map and web settings
|
206 |
print " --- python : map section " |
207 |
self.writeMapSection()
|
208 |
logmsg += "Wrote map section\n"
|
209 |
print " --- python : map section done" |
210 |
# write the projection section
|
211 |
print " --- python : proj section " |
212 |
self.writeProjectionSection()
|
213 |
logmsg += "Wrote projection section\n"
|
214 |
print " --- python : proj section done" |
215 |
# write the output format section
|
216 |
print " --- python : outputformat section " |
217 |
self.writeOutputFormat()
|
218 |
logmsg += "Wrote output format section\n"
|
219 |
print " --- python : outputformat section done" |
220 |
# write the legend section
|
221 |
print " --- python : legend section" |
222 |
self.writeLegendSection()
|
223 |
logmsg += "Wrote legend section\n"
|
224 |
print " --- python : legend section done" |
225 |
# write the WEB section
|
226 |
print " --- python : web section " |
227 |
webMsg = self.writeWebSection()
|
228 |
logmsg += "Wrote web section\n"
|
229 |
logmsg += webMsg |
230 |
print " --- python : web section done" |
231 |
|
232 |
# write the LAYER sections
|
233 |
print " --- python : layer section " |
234 |
layersMsg = self.writeMapLayers()
|
235 |
logmsg += "Wrote map layers\n"
|
236 |
logmsg += layersMsg |
237 |
print " --- python : layer section done" |
238 |
|
239 |
if self.exportLayersOnly == False: |
240 |
# we use an external synbol set instead
|
241 |
# write the symbol defs section
|
242 |
# must happen after layers so we can build a symbol queue
|
243 |
#print " --- python : symbol section "
|
244 |
#self.writeSymbolSection()
|
245 |
#logmsg += "Wrote symbol section\n"
|
246 |
#print " --- python : symbol section done"
|
247 |
|
248 |
# END and close the map file
|
249 |
self.outFile.write("END") |
250 |
self.outFile.close()
|
251 |
|
252 |
logmsg += "Map file completed for " + self.projectFileName + "\n" |
253 |
logmsg += "Map file saved as " + self.mapFile + "\n" |
254 |
if self.exportLayersOnly: |
255 |
logmsg += "\n> We only saved the LAYER portion of the map file. \nMerge this into an excisting map file to see it working\n"
|
256 |
else:
|
257 |
logmsg += "\n> If this mapfile is accessible by your mapserver, you\nshould be able to see the capabilities by firing this url:\n" + self.mapServerUrl + "?MAP="+self.mapFile+"&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities\n" |
258 |
logmsg += "\n> if this mapfile is accessible by your mapserver, you\nshould be able to see a map by firing this url:\n" + self.mapServerUrl + "?MAP="+self.mapFile+"&SERVICE=WMS&LAYERS=ALL&MODE=MAP\n" |
259 |
return logmsg
|
260 |
|
261 |
# Write the general parts of the map section
|
262 |
def writeMapSection(self): |
263 |
self.outFile.write("# Map file created from QGIS project file " + str(self.projectFileName).encode('utf-8') + "\n") |
264 |
self.outFile.write("# Edit this file to customize for your map interface\n") |
265 |
self.outFile.write("# (Created with PyQgis MapServer Export plugin)\n") |
266 |
self.outFile.write("MAP\n") |
267 |
self.outFile.write(" NAME \"" + self.mapName + "\"\n") |
268 |
self.outFile.write(" # Map image size\n") |
269 |
if self.width == '' or self.height == '': |
270 |
self.outFile.write(" SIZE 0 0\n") |
271 |
else:
|
272 |
self.outFile.write(" SIZE " + str(self.width) + " " + str(self.height) + "\n") |
273 |
|
274 |
self.outFile.write(" UNITS %s\n" % (self.units)) |
275 |
self.outFile.write("\n") |
276 |
# extents
|
277 |
self.outFile.write(self.getExtentString()) |
278 |
|
279 |
self.outFile.write(" FONTSET '" + self.fontsPath + "'\n") |
280 |
# use of external symbol set
|
281 |
self.outFile.write(" SYMBOLSET '" + self.symbolsPath + "'\n") |
282 |
|
283 |
def getExtentString(self): |
284 |
stringToAddTo = ""
|
285 |
|
286 |
xmin = self.qgs.getElementsByTagName("xmin") |
287 |
stringToAddTo += " EXTENT "
|
288 |
stringToAddTo += xmin[0].childNodes[0].nodeValue.encode('utf-8') |
289 |
stringToAddTo += " "
|
290 |
|
291 |
ymin = self.qgs.getElementsByTagName("ymin") |
292 |
stringToAddTo += ymin[0].childNodes[0].nodeValue.encode('utf-8') |
293 |
stringToAddTo += " "
|
294 |
|
295 |
xmax = self.qgs.getElementsByTagName("xmax") |
296 |
stringToAddTo += xmax[0].childNodes[0].nodeValue.encode('utf-8') |
297 |
stringToAddTo += " "
|
298 |
|
299 |
ymax = self.qgs.getElementsByTagName("ymax") |
300 |
stringToAddTo += ymax[0].childNodes[0].nodeValue.encode('utf-8') |
301 |
stringToAddTo += "\n"
|
302 |
|
303 |
return stringToAddTo
|
304 |
|
305 |
# Write the OUTPUTFORMAT section
|
306 |
def writeOutputFormat(self): |
307 |
self.outFile.write(" # Background color for the map canvas -- change as desired\n") |
308 |
self.outFile.write(" IMAGECOLOR 255 255 255\n") |
309 |
self.outFile.write(" IMAGEQUALITY 95\n") |
310 |
self.outFile.write(" IMAGETYPE " + self.imageType + "\n") |
311 |
self.outFile.write("\n") |
312 |
self.outFile.write(" OUTPUTFORMAT\n") |
313 |
self.outFile.write(" NAME " + self.imageType + "\n") |
314 |
if self.imageType == 'agg': |
315 |
self.outFile.write(" DRIVER AGG/PNG\n") |
316 |
self.outFile.write(" IMAGEMODE RGB\n") |
317 |
else:
|
318 |
self.outFile.write(" DRIVER 'GD/" + self.imageType.upper() + "'\n") |
319 |
self.outFile.write(" MIMETYPE 'image/" + lower(self.imageType) + "'\n") |
320 |
if self.imageType.lower() != "gif": |
321 |
self.outFile.write(" IMAGEMODE RGBA\n") |
322 |
self.outFile.write(" EXTENSION '" + lower(self.imageType) + "'\n") |
323 |
self.outFile.write(" END\n") |
324 |
|
325 |
|
326 |
# Write Projection section
|
327 |
def writeProjectionSection(self): |
328 |
# Need to get the destination srs from one of the map layers since
|
329 |
# the project file doesn't contain the epsg id or proj4 text for
|
330 |
# the map apart from that defined in each layer
|
331 |
|
332 |
self.outFile.write(" PROJECTION\n") |
333 |
|
334 |
# Get the proj4 text from the first map layer's destination SRS
|
335 |
destsrs = self.qgs.getElementsByTagName("destinationsrs")[0] |
336 |
proj4Text = destsrs.getElementsByTagName("proj4")[0].childNodes[0].nodeValue.encode('utf-8') |
337 |
# the proj4 text string needs to be reformatted to make mapserver happy
|
338 |
self.outFile.write(self.formatProj4(proj4Text)) |
339 |
|
340 |
self.outFile.write(" END\n\n") |
341 |
|
342 |
# Write the LEGEND section
|
343 |
def writeLegendSection(self): |
344 |
self.outFile.write(" # Legend\n") |
345 |
self.outFile.write(" LEGEND\n") |
346 |
self.outFile.write(" IMAGECOLOR 255 255 255\n") |
347 |
self.outFile.write(" STATUS ON\n") |
348 |
self.outFile.write(" KEYSIZE 18 12\n") |
349 |
self.outFile.write(" LABEL\n") |
350 |
self.outFile.write(" TYPE BITMAP\n") |
351 |
self.outFile.write(" SIZE MEDIUM\n") |
352 |
self.outFile.write(" COLOR 0 0 89\n") |
353 |
self.outFile.write(" END\n") |
354 |
self.outFile.write(" END\n\n") |
355 |
|
356 |
# groups are ignored as of yet
|
357 |
self.legendlayerfileNodesById = {}
|
358 |
for legendlayerfileNode in self.qgs.getElementsByTagName("legend")[0].getElementsByTagName("legendlayerfile"): |
359 |
key = legendlayerfileNode.getAttribute("layerid").encode("utf-8") |
360 |
if (key != ""): |
361 |
self.legendlayerfileNodesById[key] = legendlayerfileNode
|
362 |
|
363 |
# Write the symbol definitions
|
364 |
def writeSymbolSection(self): |
365 |
for symbol in self.symbolQueue.keys(): |
366 |
self.outFile.write( self.symbolQueue[symbol] ) |
367 |
self.outFile.write( "\n" ) |
368 |
|
369 |
# Write the WEB section of the map file
|
370 |
def writeWebSection(self): |
371 |
resultMsg = ""
|
372 |
self.outFile.write(" # Web interface definition. Only the template parameter\n") |
373 |
self.outFile.write(" # is required to display a map. See MapServer documentation\n") |
374 |
self.outFile.write(" WEB\n") |
375 |
self.outFile.write(" # Set IMAGEPATH to the path where MapServer should\n") |
376 |
self.outFile.write(" # write its output.\n") |
377 |
self.outFile.write(" IMAGEPATH '/tmp/'\n") |
378 |
self.outFile.write("\n") |
379 |
self.outFile.write(" # Set IMAGEURL to the url that points to IMAGEPATH\n") |
380 |
self.outFile.write(" # as defined in your web server configuration\n") |
381 |
self.outFile.write(" IMAGEURL '/tmp/'\n") |
382 |
self.outFile.write("\n") |
383 |
|
384 |
destsrs = self.qgs.getElementsByTagName("destinationsrs")[0] |
385 |
try:
|
386 |
epsg = destsrs.getElementsByTagName("srid")[0].childNodes[0].nodeValue.encode("utf-8") |
387 |
except:
|
388 |
# default to epsg
|
389 |
epsg="4326"
|
390 |
self.outFile.write(" # WMS server settings\n") |
391 |
self.outFile.write(" METADATA\n") |
392 |
self.outFile.write(" 'ows_title' '" + self.mapName + "'\n") |
393 |
# if mapserverurl is still defaults.mapServerUrl, give warning
|
394 |
if defaults.mapServerUrl==self.mapServerUrl: |
395 |
resultMsg += " ! MapServer url still default value: '" + defaults.mapServerUrl + \
|
396 |
"'?\n Be sure there is a valid mapserverurl in the 'ows_onlineresource'.\n"
|
397 |
self.outFile.write(" 'ows_onlineresource' '" + self.mapServerUrl + "?" + "map" + "=" + self.mapFile + "'\n") |
398 |
self.outFile.write(" 'ows_srs' 'EPSG:" + epsg + "'\n") |
399 |
self.outFile.write(" END\n\n") |
400 |
|
401 |
self.outFile.write(" #Scale range at which web interface will operate\n") |
402 |
if self.minimumScale != "": |
403 |
self.outFile.write(" MINSCALE " + self.minimumScale + "\n") |
404 |
if self.maximumScale != "": |
405 |
self.outFile.write(" MAXSCALE " + self.maximumScale + "\n") |
406 |
|
407 |
self.outFile.write(" # Template and header/footer settings\n") |
408 |
self.outFile.write(" # Only the template parameter is required to display a map. See MapServer documentation\n") |
409 |
|
410 |
if self.template != "": |
411 |
self.outFile.write(" TEMPLATE '" + self.template + "'\n") |
412 |
if self.header != "": |
413 |
self.outFile.write(" HEADER '" + self.header + "'\n") |
414 |
if self.footer != "": |
415 |
self.outFile.write(" FOOTER '" + self.footer + "'\n") |
416 |
self.outFile.write(" END\n\n") |
417 |
return resultMsg
|
418 |
|
419 |
# Write the map layers - we have to defer writing to disk so we
|
420 |
# can invert the order of the layes, since they are opposite in QGIS
|
421 |
# compared to mapserver
|
422 |
def writeMapLayers(self): |
423 |
resultMsg = ''
|
424 |
# get the layers from the legend to be able to determine the order later
|
425 |
legend_layers = self.qgs.getElementsByTagName("legendlayerfile") |
426 |
self.layer_order = list() |
427 |
for legend_layer in legend_layers: |
428 |
self.layer_order.append(legend_layer.getAttribute("layerid").encode('utf-8')) |
429 |
# get the list of maplayer nodes
|
430 |
maplayers = self.qgs.getElementsByTagName("maplayer") |
431 |
print "Processing ", len(maplayers), " layers" |
432 |
count = 0
|
433 |
layer_list = dict()
|
434 |
layer_names = [] |
435 |
for lyr in maplayers: |
436 |
count += 1
|
437 |
print "Processing layer ", count |
438 |
# The attributes of the maplayer tag contain the scale dependent settings,
|
439 |
# visibility, and layer type
|
440 |
layer_def = " LAYER\n"
|
441 |
# store name of the layer - replace space with underscore for wms compliance
|
442 |
layer_name = lyr.getElementsByTagName("layername")[0].childNodes[0].nodeValue.encode('utf-8').replace("\"", "").replace(" ","_") |
443 |
# layername is not unique in qgis, store id of layer
|
444 |
layer_id = lyr.getElementsByTagName("id")[0].childNodes[0].nodeValue.encode('utf-8') |
445 |
# first check to see if there is a name
|
446 |
if len(layer_name) > 0: |
447 |
# WMS layernames should be unique, so
|
448 |
# if the layer_name already excists in our layer_list of names:
|
449 |
if layer_name in layer_names: |
450 |
# we give it the old name plus number
|
451 |
layer_name = layer_name + str(count)
|
452 |
else:
|
453 |
# if no name for the layer, manufacture one
|
454 |
layer_name = 'layer' + str(count) |
455 |
# store the name to be able to check for double names
|
456 |
layer_names.append(layer_name) |
457 |
layer_def += " NAME '%s'\n" % layer_name
|
458 |
|
459 |
if lyr.getAttribute("type").encode('utf-8') == 'vector': |
460 |
layer_def += " TYPE " + lyr.getAttribute("geometry").encode('utf-8').upper() + "\n" |
461 |
elif lyr.getAttribute("type").encode('utf-8') == 'raster': |
462 |
layer_def += " TYPE " + lyr.getAttribute("type").encode('utf-8').upper() + "\n" |
463 |
|
464 |
# Use (global) default value from the gui
|
465 |
layer_def += " DUMP " + self.dump + "\n" |
466 |
# id dump = true: add TEMPLATE to be able to use getFeatureInfoRequests
|
467 |
if self.dump=="true": |
468 |
layer_def += " TEMPLATE fooOnlyForWMSGetFeatureInfo\n"
|
469 |
|
470 |
# Set min/max scales
|
471 |
if lyr.getAttribute('hasScaleBasedVisibilityFlag').encode('utf-8') == 1: |
472 |
layer_def += " MINSCALE " + lyr.getAttribute('minimumScale').encode('utf-8') + "\n" |
473 |
layer_def += " MAXSCALE " + lyr.getAttribute('maximumScale').encode('utf-8') + "\n" |
474 |
|
475 |
layer_def += self.getExtentString()
|
476 |
|
477 |
# data
|
478 |
dataString = lyr.getElementsByTagName("datasource")[0].childNodes[0].nodeValue.encode('utf-8') |
479 |
|
480 |
# test if it is a postgis, grass or WMS layer
|
481 |
# is there a better way to do this? probably.
|
482 |
try:
|
483 |
providerString = lyr.getElementsByTagName("provider")[0].childNodes[0].nodeValue.encode('utf-8') |
484 |
except:
|
485 |
# if providerString is null
|
486 |
providerString = ''
|
487 |
|
488 |
if providerString == 'postgres': |
489 |
# it's a postgis layer
|
490 |
uri = QgsDataSourceURI(dataString) |
491 |
layer_def += " CONNECTIONTYPE postgis\n"
|
492 |
connectionInfo = str(uri.connectionInfo())
|
493 |
# if connectionInfo does NOT contain a password, warn user:
|
494 |
if connectionInfo.find("password")<0: |
495 |
resultMsg += " ! No password in connection string for postgres layer '" + layer_name + \
|
496 |
"' \n Add it, or make sure mapserver can connect to postgres.\n"
|
497 |
layer_def += " CONNECTION \"" + connectionInfo + "\"\n" |
498 |
# EvdP: it seems that the uri.geometryColumn() is quoted automatically by PostGIS.
|
499 |
# To prevent double quoting, we don't quote here.
|
500 |
# Now we are unable to process uri.geometryColumn()s with special characters (uppercase... etc.)
|
501 |
#layer_def += " DATA '\"" + uri.geometryColumn() + "\" FROM " + uri.quotedTablename() + "'\n"
|
502 |
#layer_def += " DATA '" + uri.geometryColumn() + " FROM " + uri.quotedTablename() + "'\n"
|
503 |
layer_id = lyr.getElementsByTagName("id")[0].childNodes[0].nodeValue.encode("utf-8") |
504 |
uniqueId = self.getPrimaryKey(layer_id, uri.table())
|
505 |
# %tablename% is returned when no uniqueId is found: inform user
|
506 |
if uniqueId.find("%") >= 0: |
507 |
resultMsg += " ! No primary key found for postgres layer '" + layer_name + \
|
508 |
"' \n Make sure you edit the mapfile and change the DATA-string \n containing '" + uniqueId + "'\n" |
509 |
epsg = self.getEpsg(lyr)
|
510 |
|
511 |
layer_def += " DATA '" + uri.geometryColumn() + " FROM " + uri.quotedTablename() + " USING UNIQUE " + uniqueId + " USING srid=" + epsg + "'\n" |
512 |
# don't write the filter keyword if there isn't one
|
513 |
if uri.sql() != "": |
514 |
layer_def += " FILTER ( " + uri.sql() + " )\n" |
515 |
|
516 |
elif providerString == 'wms' and lyr.getAttribute("type").encode('utf-8').upper() == 'RASTER': |
517 |
# it's a WMS layer
|
518 |
layer_def += " CONNECTIONTYPE WMS\n"
|
519 |
layer_def += " CONNECTION '" + dataString + "'\n" |
520 |
rasterProp = lyr.getElementsByTagName("rasterproperties")[0] |
521 |
# loop thru wmsSubLayers
|
522 |
wmsSubLayers = rasterProp.getElementsByTagName('wmsSublayer')
|
523 |
wmsNames = [] |
524 |
wmsStyles = [] |
525 |
for wmsLayer in wmsSubLayers: |
526 |
wmsNames.append( wmsLayer.getElementsByTagName('name')[0].childNodes[0].nodeValue.encode('utf-8').replace("\"", "") ) |
527 |
try:
|
528 |
wmsStyles.append( wmsLayer.getElementsByTagName('style')[0].childNodes[0].nodeValue.encode('utf-8') ) |
529 |
except:
|
530 |
wmsStyles.append( '' )
|
531 |
# Create necesssary wms metadata
|
532 |
format = rasterProp.getElementsByTagName('wmsFormat')[0].childNodes[0].nodeValue.encode('utf-8') |
533 |
layer_def += " METADATA\n"
|
534 |
layer_def += " 'ows_name' '" + ','.join(wmsNames) + "'\n" |
535 |
layer_def += " 'wms_server_version' '1.1.1'\n"
|
536 |
try:
|
537 |
#ct = lyr.getElementsByTagName('coordinatetransform')[0]
|
538 |
#srs = ct.getElementsByTagName('sourcesrs')[0].getElementsByTagName('spatialrefsys')[0]
|
539 |
#epsg = srs.getElementsByTagName('epsg')[0].childNodes[0].nodeValue.encode('utf-8')
|
540 |
#layer_def += " 'wms_srs' 'EPSG:4326 EPSG:" + epsg + "'\n"
|
541 |
layer_def += " 'ows_srs' 'EPSG:" + self.getEpsg(lyr) + "'\n" |
542 |
# TODO: add epsg to all METADATA tags??
|
543 |
except:
|
544 |
print "ERROR while trying to write ows_srs METADATA" |
545 |
pass
|
546 |
layer_def += " 'wms_format' '" + format + "'\n" |
547 |
layer_def += " 'wms_style' '" + ','.join(wmsStyles) + "'\n" |
548 |
layer_def += " END\n"
|
549 |
|
550 |
else:
|
551 |
# its a standard ogr, gdal or grass layer
|
552 |
layer_def += " DATA '" + dataString + "'\n" |
553 |
|
554 |
# WMS settings for all layers
|
555 |
layer_def += " METADATA\n"
|
556 |
layer_def += " 'ows_title' '" + layer_name + "'\n" |
557 |
layer_def += " END\n"
|
558 |
|
559 |
layer_def += " STATUS OFF\n"
|
560 |
|
561 |
# turn status in MapServer on or off based on visibility in QGis:
|
562 |
# layer_id = lyr.getElementsByTagName("id")[0].childNodes[0].nodeValue.encode("utf-8")
|
563 |
# legendLayerNode = self.legendlayerfileNodesById[layer_id]
|
564 |
# if legendLayerNode.getAttribute("visible").encode("utf-8") == "1":
|
565 |
# layer_def += " STATUS ON\n"
|
566 |
# else:
|
567 |
# layer_def += " STATUS OFF\n"
|
568 |
|
569 |
opacity = int ( 100.0 * |
570 |
float(lyr.getElementsByTagName("transparencyLevelInt")[0].childNodes[0].nodeValue.encode('utf-8')) / 255.0 ) |
571 |
layer_def += " TRANSPARENCY " + str(opacity) + "\n" |
572 |
|
573 |
layer_def += " PROJECTION\n"
|
574 |
|
575 |
# Get the destination srs for this layer and use it to create the projection section
|
576 |
destsrs = self.qgs.getElementsByTagName("destinationsrs")[0] |
577 |
proj4Text = destsrs.getElementsByTagName("proj4")[0].childNodes[0].nodeValue.encode('utf-8') |
578 |
# TODO: you would think: take DATA-srs here, but often projected
|
579 |
# shapefiles do not contain srs data ... If we want to do this
|
580 |
# we should take make the map-srs the qgs-project srs first
|
581 |
# instead of taking the srs of the first layer
|
582 |
# Get the data srs for this layer and use it to create the projection section
|
583 |
# datasrs = lyr.getElementsByTagName("srs")[0]
|
584 |
# proj4Text = datasrs.getElementsByTagName("proj4")[0].childNodes[0].nodeValue.encode('utf-8')
|
585 |
|
586 |
# the proj4 text string needs to be reformatted to make mapserver happy
|
587 |
layer_def += self.formatProj4(proj4Text)
|
588 |
layer_def += " END\n"
|
589 |
scaleDependent = lyr.getAttribute("hasScaleBasedVisibilityFlag").encode('utf-8') |
590 |
if scaleDependent == '1': |
591 |
# get the min and max scale settings
|
592 |
minscale = lyr.getAttribute("minimumScale").encode('utf-8') |
593 |
maxscale = lyr.getAttribute("maximumScale").encode('utf-8') |
594 |
if minscale > '': |
595 |
layer_def += " MINSCALE " + minscale + "\n" |
596 |
if maxscale > '': |
597 |
layer_def += " MAXSCALE " + maxscale + "\n" |
598 |
# Check for label field (ie LABELITEM) and label status
|
599 |
try:
|
600 |
labelElements = lyr.getElementsByTagName("label")
|
601 |
labelOn = '0'
|
602 |
labelField = None
|
603 |
# there are actually 3 different label-element in a layer element:
|
604 |
for element in labelElements: |
605 |
labelParent = element.parentNode.localName |
606 |
if labelParent == 'maplayer': |
607 |
labelOn = element.childNodes[0].nodeValue.encode('utf-8') |
608 |
if labelParent == 'labelattributes': |
609 |
labelField = element.getAttribute('fieldname').encode('utf-8') |
610 |
if labelField != '' and labelField is not None and labelOn == "1" and labelOn is not None: |
611 |
layer_def += " LABELITEM '" + labelField + "'\n" |
612 |
except:
|
613 |
# no labels
|
614 |
pass
|
615 |
|
616 |
# write the CLASS section for rendering
|
617 |
# First see if there is a single symbol renderer
|
618 |
if lyr.getElementsByTagName("singlesymbol").length > 0: |
619 |
layer_def += self.simpleRenderer(lyr, lyr.getElementsByTagName("singlesymbol")[0].getElementsByTagName('symbol')[0] ) |
620 |
elif lyr.getElementsByTagName("graduatedsymbol").length > 0: |
621 |
layer_def += self.graduatedRenderer(lyr, lyr.getElementsByTagName("graduatedsymbol")[0].getElementsByTagName('symbol')[0] ) |
622 |
elif lyr.getElementsByTagName("continuoussymbol").length > 0: |
623 |
layer_def += self.continuousRenderer(lyr, lyr.getElementsByTagName("continuoussymbol")[0] ) |
624 |
elif lyr.getElementsByTagName("uniquevalue").length > 0: |
625 |
layer_def += self.uniqueRenderer(lyr, lyr.getElementsByTagName("uniquevalue")[0].getElementsByTagName('symbol')[0] ) |
626 |
|
627 |
# end of LAYER
|
628 |
layer_def += " END\n\n"
|
629 |
|
630 |
# add the layer to the list with layer_id as key
|
631 |
layer_list[layer_id] = layer_def |
632 |
# all layers have been processed, reverse the layer_order and write
|
633 |
# output layer_def's in order as they appear in legend (as seen by user)
|
634 |
self.layer_order.reverse()
|
635 |
for layerid in self.layer_order: |
636 |
self.outFile.write(layer_list[layerid])
|
637 |
return resultMsg
|
638 |
|
639 |
|
640 |
def getEpsg(self, lyr): |
641 |
try:
|
642 |
srs = lyr.getElementsByTagName('srs')[0].getElementsByTagName('spatialrefsys')[0] |
643 |
return srs.getElementsByTagName('srid')[0].childNodes[0].nodeValue.encode('utf-8') |
644 |
except:
|
645 |
#Use 4326 as a sensible default if the above fails
|
646 |
return "4326" |
647 |
|
648 |
|
649 |
def getPrimaryKey(self, layerId, tableName): |
650 |
"""
|
651 |
Since we have no Python bindings for "src\providers\postgres\qgspostgresprovider.cpp"
|
652 |
we approximate the primary key by finding an integer type field containing "fid" or "id"
|
653 |
This is obviously a lousy solution at best.
|
654 |
|
655 |
This script requires the project you export to be open in QGis!!
|
656 |
|
657 |
This method will return either the primary key of this table, or
|
658 |
the string %tablename% in case we're not able to find it...
|
659 |
"""
|
660 |
|
661 |
mlr = QgsMapLayerRegistry.instance() |
662 |
layers = mlr.mapLayers() |
663 |
if not QString(layerId) in layers: |
664 |
# layerId of this postgis layer NOT in the layerlist...
|
665 |
# probably the project is not loaded in qgis
|
666 |
#raise Exception("ERROR: layer not found in project layers.... \nThis happens with postgis layers in a project which \nis not loaded in QGis.\nDid you load this project into QGis? \nIf not please load project first, and then export it to mapserver.")
|
667 |
return str("%" + tableName + "_id%") |
668 |
|
669 |
layer = layers[QString(layerId)] |
670 |
dataProvider = layer.dataProvider() |
671 |
fields = dataProvider.fields() |
672 |
|
673 |
intTypes = [QVariant.Int, QVariant.LongLong, QVariant.UInt, QVariant.ULongLong] |
674 |
|
675 |
integerFields = [] |
676 |
for id, field in fields.iteritems(): |
677 |
if field.type() in intTypes: |
678 |
integerFields.append(id)
|
679 |
|
680 |
# fid end
|
681 |
fidIntegerFields = [] |
682 |
for id in integerFields: |
683 |
if fields[id].name().endsWith("fid"): |
684 |
fidIntegerFields.append(id)
|
685 |
|
686 |
if len(fidIntegerFields) == 1: |
687 |
return str(fields[fidIntegerFields[0]].name()) |
688 |
|
689 |
# fid start
|
690 |
fidIntegerFields[:] = [] |
691 |
for id in integerFields: |
692 |
if fields[id].name().startsWith("fid"): |
693 |
fidIntegerFields.append(id)
|
694 |
|
695 |
if len(fidIntegerFields) == 1: |
696 |
return str(fields[fidIntegerFields[0]].name()) |
697 |
|
698 |
# id end
|
699 |
idIntegerFields = [] |
700 |
for id in integerFields: |
701 |
if fields[id].name().endsWith("id"): |
702 |
idIntegerFields.append(id)
|
703 |
|
704 |
if len(idIntegerFields) == 1: |
705 |
return str(fields[idIntegerFields[0]].name()) |
706 |
|
707 |
# id start
|
708 |
idIntegerFields[:] = [] |
709 |
for id in integerFields: |
710 |
if fields[id].name().startsWith("id"): |
711 |
idIntegerFields.append(id)
|
712 |
|
713 |
if len(idIntegerFields) == 1: |
714 |
return str(fields[idIntegerFields[0]].name()) |
715 |
|
716 |
# if we arrive here we have ambiguous or no primary keys
|
717 |
#print "Error: Could not find primary key from field type and field name information.\n"
|
718 |
|
719 |
# using a mapfile pre-processor, the proper id field can be substituted in the following:
|
720 |
return str("%" + tableName + "_id%") |
721 |
|
722 |
|
723 |
# Get RGB code from a XML color node, returning string like '45 124 255'
|
724 |
def getRgbFromNode(self, symbolNode, elementName): |
725 |
if symbolNode == None or symbolNode.getElementsByTagName(elementName).length == 0: |
726 |
return '' |
727 |
colorNode = symbolNode.getElementsByTagName(elementName)[0]
|
728 |
return colorNode.getAttribute('red').encode('utf-8') + ' ' + colorNode.getAttribute('green').encode('utf-8') + ' ' + colorNode.getAttribute('blue').encode('utf-8') |
729 |
|
730 |
|
731 |
# Get a size (multiplied by symbolSizeMultiplier) from given sizeNode's child with elementName
|
732 |
def getSizeStringFromNode(self, symbolNode, elementName): |
733 |
if symbolNode == None or symbolNode.getElementsByTagName(elementName).length == 0: |
734 |
return '' |
735 |
size = float(symbolNode.getElementsByTagName(elementName)[0].childNodes[0].nodeValue.encode('utf-8')) |
736 |
symbolSize = size * symbolSizeMultiplier |
737 |
return str(symbolSize) |
738 |
|
739 |
|
740 |
def getSymbolProperty(self, symbolNode, elementName): |
741 |
if symbolNode == None or symbolNode.getElementsByTagName(elementName).length == 0: |
742 |
return '' |
743 |
return str(symbolNode.getElementsByTagName(elementName)[0].childNodes[0].nodeValue.encode('utf-8')) |
744 |
|
745 |
|
746 |
def writeClassStyleContent(self, symbolNode, geometry): |
747 |
outlinecolor = self.getRgbFromNode(symbolNode, 'outlinecolor') |
748 |
fillcolor = self.getRgbFromNode(symbolNode, 'fillcolor') |
749 |
pointsize = self.getSizeStringFromNode(symbolNode, 'pointsize') |
750 |
outlinewidth = self.getSizeStringFromNode(symbolNode, 'outlinewidth') |
751 |
fillpattern = self.getSymbolProperty(symbolNode, 'fillpattern'); |
752 |
|
753 |
class_def = " STYLE\n"
|
754 |
# for POINT by SYMBOL and SIZE
|
755 |
if geometry == 'POINT': |
756 |
# use the point symbol map to lookup the mapserver symbol type
|
757 |
symbol = self.msSymbol( geometry, symbolNode )
|
758 |
class_def += " SYMBOL " + symbol + " \n" |
759 |
class_def += " SIZE " + pointsize + " \n" |
760 |
# for LINE and POLYGON size define by WIDTH
|
761 |
else:
|
762 |
class_def += " WIDTH " + outlinewidth + " \n" |
763 |
|
764 |
# for LINE defined by COLOR from outlinecolor
|
765 |
if geometry == 'LINE': |
766 |
class_def += " COLOR " + outlinecolor + "\n" |
767 |
# for POLYGON and POINT defined by COLOR from fillcolor and OUTLINECOLOR from outlinecolor
|
768 |
else:
|
769 |
class_def += " OUTLINECOLOR " + outlinecolor + "\n" |
770 |
if 'NoBrush' != fillpattern: |
771 |
class_def += " COLOR " + fillcolor + "\n" |
772 |
|
773 |
class_def += " END\n"
|
774 |
return class_def
|
775 |
|
776 |
|
777 |
# Simple renderer ouput
|
778 |
# We need the layer node and symbol node
|
779 |
def simpleRenderer(self, layerNode, symbolNode): |
780 |
# get the layers geometry type
|
781 |
geometry = layerNode.getAttribute("geometry").encode('utf-8').upper() |
782 |
|
783 |
class_def = " CLASS\n"
|
784 |
|
785 |
class_def += " NAME '" + layerNode.getElementsByTagName("layername")[0].childNodes[0].nodeValue.encode('utf-8').replace("\"", "") + "' \n" |
786 |
|
787 |
class_def += self.writeClassStyleContent(symbolNode, geometry)
|
788 |
|
789 |
class_def += self.msLabel( layerNode )
|
790 |
|
791 |
# end of CLASS
|
792 |
class_def += " END\n"
|
793 |
|
794 |
return class_def
|
795 |
|
796 |
|
797 |
# Graduated symbol renderer output
|
798 |
def graduatedRenderer(self, layerNode, symbolNode): |
799 |
# get the layers geometry type
|
800 |
geometry = layerNode.getAttribute("geometry").encode('utf-8').upper() |
801 |
|
802 |
# get the renderer field for building up the classes
|
803 |
classField = layerNode.getElementsByTagName('classificationattribute')[0].childNodes[0].nodeValue.encode('utf-8') |
804 |
# write the render item
|
805 |
class_def = " CLASSITEM '" + classField + "'\n" |
806 |
|
807 |
# write the rendering info for each class
|
808 |
classes = layerNode.getElementsByTagName('symbol')
|
809 |
for cls in classes: |
810 |
class_def += " CLASS\n"
|
811 |
|
812 |
lower = cls.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8') |
813 |
upper = cls.getElementsByTagName('uppervalue')[0].childNodes[0].nodeValue.encode('utf-8') |
814 |
|
815 |
# If there's a label use it, otherwise autogenerate one
|
816 |
try:
|
817 |
label = cls.getElementsByTagName('label')[0].childNodes[0].nodeValue.encode('utf-8') |
818 |
class_def += " NAME '" + label + "'\n" |
819 |
except:
|
820 |
class_def += " NAME '" + lower + " < " + classField + " < " + upper + "'\n" |
821 |
|
822 |
class_def += " EXPRESSION ( ([" + classField + "] >= " + lower + ") AND ([" + classField + "] <= " + upper + ") )\n" |
823 |
|
824 |
class_def += self.writeClassStyleContent(cls, geometry)
|
825 |
|
826 |
# label
|
827 |
class_def += self.msLabel( layerNode )
|
828 |
|
829 |
# end of CLASS
|
830 |
class_def += " END\n"
|
831 |
|
832 |
return class_def
|
833 |
|
834 |
|
835 |
# Continuous symbol renderer output
|
836 |
def continuousRenderer(self, layerNode, symbolNode): |
837 |
# get the layers geometry type
|
838 |
geometry = layerNode.getAttribute("geometry").encode('utf-8').upper() |
839 |
|
840 |
# get the renderer field for building up the classes
|
841 |
classField = layerNode.getElementsByTagName('classificationattribute')[0].childNodes[0].nodeValue.encode('utf-8') |
842 |
|
843 |
# write the rendering info for each class
|
844 |
class_def = " CLASS\n"
|
845 |
|
846 |
# Class name irrelevant for color ramps since mapserver can't render their legend
|
847 |
#self.outFile.write(" NAME '" + classField + "'\n")
|
848 |
|
849 |
# color
|
850 |
lower = symbolNode.getElementsByTagName('lowestsymbol')[0].getElementsByTagName('symbol')[0] |
851 |
upper = symbolNode.getElementsByTagName('highestsymbol')[0].getElementsByTagName('symbol')[0] |
852 |
lowerColor = lower.getElementsByTagName('outlinecolor')[0] |
853 |
upperColor = upper.getElementsByTagName('outlinecolor')[0] |
854 |
|
855 |
# outline color
|
856 |
outlineNode = lower.getElementsByTagName('outlinecolor')[0] |
857 |
|
858 |
class_def += " STYLE\n"
|
859 |
|
860 |
# The first and last color of the ramp ( r g b r g b )
|
861 |
class_def += " COLORRANGE " + lowerColor.getAttribute('red').encode('utf-8') + " " + lowerColor.getAttribute('green').encode('utf-8') + " " + lowerColor.getAttribute('blue').encode('utf-8') + " " + upperColor.getAttribute('red').encode('utf-8') + " " + upperColor.getAttribute('green').encode('utf-8') + " " + upperColor.getAttribute('blue').encode('utf-8') + "\n" |
862 |
|
863 |
# The range of values over which to ramp the colors
|
864 |
class_def += " DATARANGE " + lower.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8') + ' ' + upper.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8') + '\n' |
865 |
|
866 |
class_def += " RANGEITEM '" + classField + "'\n" |
867 |
|
868 |
|
869 |
# upper and lower have same size
|
870 |
outlinecolor = self.getRgbFromNode(upper, 'outlinecolor') |
871 |
fillcolor = self.getRgbFromNode(upper, 'fillcolor') |
872 |
outlinewidth = self.getSizeStringFromNode(upper, 'outlinewidth') |
873 |
|
874 |
size = float(lower.getElementsByTagName('pointsize')[0].childNodes[0].nodeValue.encode('utf-8')) |
875 |
pointsize = 2 * size * symbolSizeMultiplier
|
876 |
|
877 |
|
878 |
# for POINT by SYMBOL and SIZE
|
879 |
if geometry == 'POINT': |
880 |
# use the point symbol map to lookup the mapserver symbol type
|
881 |
symbol = self.msSymbol( geometry, symbolNode )
|
882 |
class_def += " SYMBOL " + symbol + " \n" |
883 |
class_def += " SIZE " + str(pointsize) + " \n" |
884 |
# for LINE and POLYGON size define by WIDTH
|
885 |
else:
|
886 |
class_def += " WIDTH " + outlinewidth + " \n" |
887 |
|
888 |
# for LINE defined by COLOR from outlinecolor
|
889 |
if geometry == 'LINE': |
890 |
class_def += " COLOR " + outlinecolor + "\n" |
891 |
# for POLYGON and POINT defined by COLOR from fillcolor and OUTLINECOLOR from outlinecolor
|
892 |
else:
|
893 |
class_def += " COLOR " + fillcolor + "\n" |
894 |
|
895 |
class_def += " END\n"
|
896 |
|
897 |
# only outlines for polygons
|
898 |
outline = symbolNode.getElementsByTagName('polygonoutline')
|
899 |
if geometry == 'POLYGON' and outline is not None and '1' == outline[0].childNodes[0].nodeValue.encode('utf-8'): |
900 |
class_def += " STYLE\n"
|
901 |
class_def += " WIDTH 1 " + "\n" |
902 |
class_def += " OUTLINECOLOR 0 0 0" + "\n" |
903 |
class_def += " END\n"
|
904 |
|
905 |
# label
|
906 |
class_def += self.msLabel( layerNode )
|
907 |
|
908 |
# end of CLASS
|
909 |
class_def += " END\n"
|
910 |
|
911 |
return class_def
|
912 |
|
913 |
|
914 |
# Unique value renderer output
|
915 |
def uniqueRenderer(self, layerNode, symbolNode): |
916 |
# get the renderer field for building up the classes
|
917 |
classField = layerNode.getElementsByTagName('classificationattribute')[0].childNodes[0].nodeValue.encode('utf-8') |
918 |
|
919 |
# get the layers geometry type
|
920 |
geometry = layerNode.getAttribute("geometry").encode('utf-8').upper() |
921 |
|
922 |
# write the render item
|
923 |
class_def = " CLASSITEM '" + classField + "'\n" |
924 |
|
925 |
# write the rendering info for each class
|
926 |
classes = layerNode.getElementsByTagName('symbol')
|
927 |
for cls in classes: |
928 |
class_def += " CLASS\n"
|
929 |
|
930 |
try:
|
931 |
lower = cls.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8') |
932 |
except IndexError: |
933 |
# set to blank in the case where the field used for rendering has no value
|
934 |
lower = ""
|
935 |
|
936 |
# If there's a label use it, otherwise autogenerate one
|
937 |
try:
|
938 |
label = cls.getElementsByTagName('label')[0].childNodes[0].nodeValue.encode('utf-8') |
939 |
class_def += ' NAME "' + label + '"\n' |
940 |
except:
|
941 |
class_def += ' NAME "' + classField + ' = ' + lower + '" \n' |
942 |
|
943 |
class_def += ' EXPRESSION "' + lower + '" \n' |
944 |
|
945 |
class_def += self.writeClassStyleContent(cls, geometry)
|
946 |
|
947 |
# label
|
948 |
class_def += self.msLabel( layerNode )
|
949 |
|
950 |
# end of CLASS
|
951 |
class_def += " END\n"
|
952 |
|
953 |
return class_def
|
954 |
|
955 |
|
956 |
# Utility method to format a proj4 text string into mapserver format
|
957 |
def formatProj4(self, proj4text): |
958 |
parms = proj4text.split(" ")
|
959 |
ret = ""
|
960 |
for p in parms: |
961 |
p = p.replace("+","") |
962 |
ret = ret + " '" + p + "'\n" |
963 |
return ret
|
964 |
|
965 |
|
966 |
def getProj4(self, proj4text): |
967 |
"""Returns the proj4 string as a dictionary with key value pairs."""
|
968 |
parms = proj4text.split(" ")
|
969 |
ret = {} |
970 |
for p in parms: |
971 |
p = p.replace("+","") |
972 |
keyValue = p.split("=")
|
973 |
|
974 |
key = keyValue[0]
|
975 |
|
976 |
value = ""
|
977 |
try: value = keyValue[1] |
978 |
except: value = "" |
979 |
|
980 |
if key != "": |
981 |
ret[key] = value |
982 |
return ret
|
983 |
|
984 |
|
985 |
# Determines the symbol name and adds it to the symbol queue
|
986 |
def msSymbol(self, geometry, symbolNode): |
987 |
# contains the same markup for a layer regardless of type
|
988 |
# so we infer a symbol type based on the geometry
|
989 |
symbolName = ''
|
990 |
symbol = '0'
|
991 |
|
992 |
if geometry == 'POLYGON': |
993 |
#symbol = '0'
|
994 |
pass
|
995 |
elif geometry == 'LINE': |
996 |
#symbol = '0'
|
997 |
pass
|
998 |
elif geometry == 'POINT': |
999 |
try:
|
1000 |
symbolName = qgis2map_symbol[symbolNode.getElementsByTagName('pointsymbol')[0].childNodes[0].nodeValue.encode('utf-8')] |
1001 |
except:
|
1002 |
symbolName = "circle"
|
1003 |
# make sure it's double quoted
|
1004 |
symbol = "\"" + symbolName + "\"" |
1005 |
|
1006 |
# a symbol set in an external symbol.txt file is used; see comment on top of this file
|
1007 |
# if symbolName == 'CIRCLE':
|
1008 |
# self.symbolQueue['CIRCLE'] = """
|
1009 |
# #Circle symbol
|
1010 |
# SYMBOL
|
1011 |
# NAME 'CIRCLE'
|
1012 |
# TYPE ellipse
|
1013 |
# FILLED true
|
1014 |
# POINTS
|
1015 |
# 1 1
|
1016 |
# END
|
1017 |
# END """
|
1018 |
#
|
1019 |
# if symbolName == 'TRIANGLE':
|
1020 |
# self.symbolQueue['TRIANGLE'] = """
|
1021 |
# SYMBOL
|
1022 |
# NAME "TRIANGLE"
|
1023 |
# TYPE vector
|
1024 |
# FILLED true
|
1025 |
# POINTS
|
1026 |
# 0 1
|
1027 |
# .5 0
|
1028 |
# 1 1
|
1029 |
# 0 1
|
1030 |
# END
|
1031 |
# END """
|
1032 |
|
1033 |
return symbol
|
1034 |
|
1035 |
# Label block creation
|
1036 |
def msLabel(self, layerNode): |
1037 |
# currently a very basic bitmap font
|
1038 |
labelNode = layerNode.getElementsByTagName('labelattributes')[0] |
1039 |
#labelField = labelNode.getElementsByTagName('label')[0].getAttribute('field').encode('utf-8')
|
1040 |
# why was the attribute 'field' and not 'fieldname'?
|
1041 |
labelField = labelNode.getElementsByTagName('label')[0].getAttribute('fieldname').encode('utf-8') |
1042 |
if labelField != '' and labelField is not None: |
1043 |
labelBlock = " LABEL \n"
|
1044 |
|
1045 |
# see comment at 'qgis2ms_fontset'
|
1046 |
fontQgis = labelNode.getElementsByTagName('family')[0].getAttribute('name').encode('utf-8') |
1047 |
fontMs = ""
|
1048 |
try:
|
1049 |
fontMs = qgis2map_fontset[fontQgis] |
1050 |
except:
|
1051 |
# we default to the first font in the fontset, if any are present
|
1052 |
if len(qgis2map_fontset) > 0: |
1053 |
try:
|
1054 |
fontMs = qgis2map_fontset["MS Shell Dialog 2"]
|
1055 |
except:
|
1056 |
sortedKeys = qgis2map_fontset.keys() |
1057 |
sortedKeys.sort() |
1058 |
fontMs = qgis2map_fontset[sortedKeys[0]]
|
1059 |
else:
|
1060 |
fontMs = ""
|
1061 |
|
1062 |
bold = bool(int(labelNode.getElementsByTagName('bold')[0].getAttribute('on').encode("utf-8"))) |
1063 |
italic = bool(int(labelNode.getElementsByTagName('italic')[0].getAttribute('on').encode("utf-8"))) |
1064 |
|
1065 |
# "-bold" and "-italic" must correspond with the fontset file
|
1066 |
# font can be both bold and italic
|
1067 |
labelBlock += " FONT " + fontMs
|
1068 |
if bold: labelBlock += "-bold" |
1069 |
if italic: labelBlock += "-italic" |
1070 |
labelBlock += "\n"
|
1071 |
|
1072 |
labelBlock += " TYPE truetype\n"
|
1073 |
|
1074 |
size = self.getFieldName(labelNode, 'size') |
1075 |
if size == "": |
1076 |
sizeNode = labelNode.getElementsByTagName('size')[0] |
1077 |
units = sizeNode.getAttribute("units").encode("utf-8") |
1078 |
sizeValue = int(sizeNode.getAttribute("value").encode("utf-8")) |
1079 |
# we must convert to px for use in the mapfile
|
1080 |
sizePx = 11 # default |
1081 |
sizePx = sizeValue |
1082 |
#if units == "pt": sizePx = int(sizeValue / 0.75)
|
1083 |
# TODO: find appropriate conversion metric from map units to pixels
|
1084 |
#elif unit == "mu":
|
1085 |
#proj4Elem = labelNode.parentNode.getElementsByTagName("proj4")[0].childNodes[0]
|
1086 |
#proj4str = proj4Elem.nodeValue.encode('utf-8')
|
1087 |
#proj4Dict = self.getProj4(proj4str)
|
1088 |
#for i,j in proj4Dict.iteritems():
|
1089 |
#labelBlock += str(i) + ":: " + str(j) + "\n"
|
1090 |
#
|
1091 |
#sizePx = ????? proj4Dict["units"] ??
|
1092 |
# non-used unit types:
|
1093 |
#elif unit == "em": sizePx = int(size * 16.0)
|
1094 |
#elif unit == "px": sizePx = size
|
1095 |
size = str(sizePx)
|
1096 |
labelBlock += " SIZE " + size + "\n" |
1097 |
|
1098 |
color = self.getFieldName(labelNode, 'color') |
1099 |
if color == "": |
1100 |
colorNode = labelNode.getElementsByTagName('color')[0] |
1101 |
r = int(colorNode.getAttribute("red")) |
1102 |
g = int(colorNode.getAttribute("green")) |
1103 |
b = int(colorNode.getAttribute("blue")) |
1104 |
color = str(r) + " " + str(g) + " " + str(b) |
1105 |
labelBlock += " COLOR " + color + "\n" |
1106 |
|
1107 |
# Include label angle if specified
|
1108 |
# Note that angles only work for truetype fonts
|
1109 |
angle = self.getFieldName(labelNode, 'angle') |
1110 |
if angle == "": |
1111 |
angle = labelNode.getElementsByTagName('angle')[0].getAttribute('value').encode('utf-8') |
1112 |
labelBlock += " ANGLE " + angle + "\n" |
1113 |
|
1114 |
# Include label buffer if specified
|
1115 |
# Note that the buffer has different meaning in qgis vs mapserver
|
1116 |
# mapserver just adds blank space around the label while
|
1117 |
# qgis uses a fill color around the label
|
1118 |
# Note that buffer only works for truetype fonts
|
1119 |
buffer = labelNode.getElementsByTagName('buffersize')[0].getAttribute('value').encode('utf-8') |
1120 |
bufferon = labelNode.getElementsByTagName('bufferenabled')[0].getAttribute('on').encode('utf-8') |
1121 |
buffercolor= self.getRgbFromNode(labelNode, 'buffercolor') |
1122 |
print buffercolor
|
1123 |
|
1124 |
if bufferon is not None and '1' == bufferon: |
1125 |
# not sure if we should use this BUFFER
|
1126 |
#labelBlock += " BUFFER " + buffer + "\n"
|
1127 |
labelBlock += " BACKGROUNDCOLOR " + buffercolor + "\n" |
1128 |
|
1129 |
# alignment in QGis corresponds to position in MapServer
|
1130 |
alignment = labelNode.getElementsByTagName('alignment')[0].getAttribute('value').encode('utf-8') |
1131 |
try:
|
1132 |
labelBlock += " POSITION " + qgis2map_aligment2position[alignment] + "\n" |
1133 |
except:
|
1134 |
# default to center if we encounter a nonsensical value
|
1135 |
labelBlock += " POSITION cc\n"
|
1136 |
|
1137 |
#values from the gui:
|
1138 |
labelBlock += " FORCE " + self.force + "\n" |
1139 |
labelBlock += " ANTIALIAS " + self.antialias + "\n" |
1140 |
labelBlock += " PARTIALS " + self.partials + "\n" |
1141 |
|
1142 |
labelBlock += " END \n"
|
1143 |
return labelBlock
|
1144 |
else:
|
1145 |
return '' |
1146 |
|
1147 |
|
1148 |
def getFieldName(self, parentNode, nodeName): |
1149 |
""" Returns the fieldname-attribute-value of a nodeName with a given parentNode
|
1150 |
as a string surrounded by brackets ('[' and ']') or
|
1151 |
an empty string if the fieldname-attribute does not exist."""
|
1152 |
try:
|
1153 |
fieldname = parentNode.getElementsByTagName(nodeName)[0].getAttribute('fieldname').encode('utf-8') |
1154 |
if fieldname != "": |
1155 |
return "[" + fieldname + "]" |
1156 |
else:
|
1157 |
return "" |
1158 |
except:
|
1159 |
return "" |
1160 |
|
1161 |
|