ms_export.py

mschulz -, 2008-05-26 02:16 PM

Download (31.4 KB)

 
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
#
20
# CHANGES SHOULD NOT BE MADE TO THE writeMapFile METHOD UNLESS YOU
21
# ARE CHANGING THE QgsMapserverExport CLASS AND YOU KNOW WHAT YOU ARE
22
# DOING
23
import sys 
24
import os
25
from string import *
26
from xml.dom import minidom, Node
27

    
28
# symbol map
29
qgisSymbols = {'hard:circle'   : 'CIRCLE',
30
               'hard:triangle' : 'TRIANGLE'}
31

    
32
class Qgis2Map:
33
  def __init__(self, projectFile, mapFile, expLayersOnly=0):
34
    self.project = projectFile
35
    self.mapFile = mapFile
36
    self.expLayersOnly = expLayersOnly
37
    # create the DOM 
38
    self.qgs = minidom.parse(projectFile)
39
    # init the other members that are not set by the constructor
40
    self.units = ''
41
    self.imageType = ''
42
    self.mapName = ''
43
    self.width = ''
44
    self.height = ''
45
    self.minScale = ''
46
    self.maxScale = ''
47
    self.template = ''
48
    self.header = ''
49
    self.footer = ''
50
    self.symbolQueue = {}
51
    
52

    
53
  # Set the options collected from the GUI
54
  def setOptions(self, units, image, mapname, width, height, template, header, footer):
55
    self.units = units
56
    self.imageType = str(image)
57
    self.mapName = mapname
58
    self.width = width
59
    self.height = height
60
    #self.minScale = minscale
61
    #self.maxScale = maxscale
62
    self.template = template
63
    self.header = header
64
    self.footer = footer
65
    print units, image, mapname, width, height, template, header, footer
66

    
67
  ## All real work happens here by calling methods to write the
68
  ## various sections of the map file
69
  def writeMapFile(self):
70
    # open the output file
71
    print "creating the map file"
72
    self.outFile = open(self.mapFile, 'w')
73
    logmsg = ""
74
    # write the general map and web settings
75
    if not self.expLayersOnly:
76
      print " --- python : map section "
77
      self.writeMapSection()
78
      logmsg +=  "Wrote map section\n"
79
      print " --- python : map section done"
80
      # write the projection section
81
      print " --- python : proj section "
82
      self.writeProjectionSection()
83
      logmsg += "Wrote projection section\n"
84
      print " --- python : proj section done"
85
      # write the output format section
86
      print " --- python : outputformat section "
87
      self.writeOutputFormat()
88
      logmsg += "Wrote output format section\n"
89
      print " --- python : outputformat section done"
90
      # write the legend section
91
      print " --- python : legend section"
92
      self.writeLegendSection()
93
      logmsg += "Wrote legend section\n"
94
      print " --- python : legend section done"
95

    
96
      # write the WEB section
97
      print " --- python : web section "
98
      self.writeWebSection()
99
      logmsg += "Wrote web section\n"
100
      print " --- python : web section done"
101

    
102
    # write the LAYER sections
103
    print " --- python : layer section "
104
    self.writeMapLayers()
105
    logmsg += "Wrote map layers\n"
106
    print " --- python : layer section done"
107

    
108
    if not self.expLayersOnly:
109
      # write the symbol defs section
110
      # must happen after layers so we can build a symbol queue
111
      print " --- python : symbol section "
112
      self.writeSymbolSection()
113
      logmsg += "Wrote symbol section\n"
114
      print " --- python : symbol section done"
115
      # END and close the map file
116
      self.outFile.write("END")
117
    
118
    self.outFile.close()
119

    
120
    logmsg += "Map file completed for " + self.project + "\n"
121
    logmsg += "Map file saved as " + self.mapFile + "\n"
122
    return logmsg
123

    
124
  # Write the general parts of the map section
125
  def writeMapSection(self):
126
    self.outFile.write("# Map file created from QGIS project file " + self.project + "\n")
127
    self.outFile.write("# Edit this file to customize for your map interface\n")
128
    self.outFile.write("# (Created with PyQgis MapServer Export plugin)\n")
129
    self.outFile.write("MAP\n")
130
    self.outFile.write("  NAME " + self.mapName + "\n")
131
    self.outFile.write("  # Map image size\n")
132
    self.outFile.write("  SIZE " + self.width + " " + self.height + "\n")
133
    self.outFile.write("  UNITS %s\n" % (self.units))
134
    self.outFile.write("\n")
135
    # extents
136
    xmin = self.qgs.getElementsByTagName("xmin")
137
    self.outFile.write("  EXTENT ")
138
    self.outFile.write(xmin[0].childNodes[0].nodeValue.encode('utf-8'))
139
    self.outFile.write(" ")
140
    ymin = self.qgs.getElementsByTagName("ymin")
141
    self.outFile.write(ymin[0].childNodes[0].nodeValue.encode('utf-8'))
142
    self.outFile.write(" ")
143
    xmax = self.qgs.getElementsByTagName("xmax")
144
    self.outFile.write(xmax[0].childNodes[0].nodeValue.encode('utf-8'))
145
    self.outFile.write(" ")
146
    ymax = self.qgs.getElementsByTagName("ymax")
147
    self.outFile.write(ymax[0].childNodes[0].nodeValue.encode('utf-8'))
148
    self.outFile.write("\n")
149

    
150
  # Write the OUTPUTFORMAT section
151
  def writeOutputFormat(self):
152
    self.outFile.write("  # Background color for the map canvas -- change as desired\n")
153
    self.outFile.write("  IMAGECOLOR 192 192 192\n")
154
    self.outFile.write("  IMAGEQUALITY 95\n")
155
    self.outFile.write("  IMAGETYPE " + self.imageType + "\n")
156
    self.outFile.write("  OUTPUTFORMAT\n")
157
    self.outFile.write("    NAME " + self.imageType + "\n")
158
    self.outFile.write("    DRIVER 'GD/" + self.imageType.upper() + "'\n")
159
    self.outFile.write("    MIMETYPE 'image/" + lower(self.imageType) + "'\n")
160
    self.outFile.write("    #IMAGEMODE PC256\n")
161
    self.outFile.write("    EXTENSION '" + lower(self.imageType) + "'\n")
162
    self.outFile.write("  END\n")
163
    
164

    
165
  # Write Projection section
166
  def writeProjectionSection(self, lyr=None):
167
    # Need to get the destination srs from one of the map layers since
168
    # the project file doesn't contain the epsg id or proj4 text for 
169
    # the map apart from that defined in each layer
170

    
171
    self.outFile.write("  PROJECTION\n")
172
    if lyr:
173
      elem = lyr
174
      tag = "spatialrefsys";
175
    else:
176
      elem = self.qgs
177
      tag = "destinationsrs"
178

    
179
    # Get the proj4 text from the first map layer's destination SRS
180
    destsrs = elem.getElementsByTagName(tag)[0]
181
    # try get  the epsg code
182
    epsgCode = destsrs.getElementsByTagName("epsg")[0].childNodes[0].nodeValue.encode('utf-8')
183
    proj4Text = destsrs.getElementsByTagName("proj4")[0].childNodes[0].nodeValue.encode('utf-8')
184
    # the proj4 text string needs to be reformatted to make mapserver happy
185
    if epsgCode:
186
      self.outFile.write(self.formatEpsg(epsgCode))
187
    else:
188
      self.outFile.write(self.formatProj4(proj4Text))
189

    
190
    self.outFile.write("  END\n\n")
191

    
192
  # Write the LEGEND section
193
  def writeLegendSection(self):
194
    self.outFile.write("  # Legend\n")
195
    self.outFile.write("  LEGEND\n")
196
    self.outFile.write("      IMAGECOLOR 255 255 255\n")
197
    self.outFile.write("    STATUS ON\n")
198
    self.outFile.write("    KEYSIZE 18 12\n")
199
    self.outFile.write("    LABEL\n")
200
    self.outFile.write("      TYPE BITMAP\n")
201
    self.outFile.write("      SIZE MEDIUM\n")
202
    self.outFile.write("      COLOR 0 0 89\n")
203
    self.outFile.write("    END\n")
204
    self.outFile.write("  END\n\n")
205
    
206
  # Write the symbol definitions
207
  def writeSymbolSection(self):
208
    for symbol in self.symbolQueue.keys():
209
      self.outFile.write( self.symbolQueue[symbol] )
210
      self.outFile.write( "\n" )
211

    
212
  # Write the WEB section of the map file
213
  def writeWebSection(self):
214
    self.outFile.write("  # Web interface definition. Only the template parameter\n")
215
    self.outFile.write("  # is required to display a map. See MapServer documentation\n")
216
    self.outFile.write("  WEB\n")
217
    self.outFile.write("    # Set IMAGEPATH to the path where MapServer should\n")
218
    self.outFile.write("    # write its output.\n")
219
    self.outFile.write("    IMAGEPATH '/tmp/'\n")
220
    self.outFile.write("\n")
221
    self.outFile.write("    # Set IMAGEURL to the url that points to IMAGEPATH\n")
222
    self.outFile.write("    # as defined in your web server configuration\n")
223
    self.outFile.write("    IMAGEURL '/tmp/'\n")
224
    self.outFile.write("\n")
225

    
226
    # TODO allow user to configure this
227
    self.outFile.write("    # WMS server settings\n")
228
    self.outFile.write("    METADATA\n")
229
    self.outFile.write("      'wms_title'           '" + self.mapName + "'\n")
230
    self.outFile.write("      'wms_onlineresource'  'http://my.host.com/cgi-bin/mapserv?map=wms.map&'\n")
231
    self.outFile.write("      'wms_srs'             'EPSG:4326'\n")
232
    self.outFile.write("    END\n\n")
233

    
234
    self.outFile.write("    #Scale range at which web interface will operate\n")
235
    if self.minScale != "":
236
      self.outFile.write("    MINSCALE " + self.minScale + "\n") 
237
    if self.maxScale != "":
238
      self.outFile.write("    MAXSCALE " + self.maxScale + "\n") 
239

    
240
    self.outFile.write("    # Template and header/footer settings\n")
241
    self.outFile.write("    # Only the template parameter is required to display a map. See MapServer documentation\n")
242
    
243
    if self.template != "":
244
      self.outFile.write("    TEMPLATE '" + self.template + "'\n")
245
    if self.header != "":
246
      self.outFile.write("    HEADER '" + self.header + "'\n")
247
    if self.footer != "":
248
      self.outFile.write("    FOOTER '" + self.footer + "'\n")
249
    self.outFile.write("  END\n\n")
250

    
251
  def parsePostgisConnection( self, dataString ):
252
    pg = {}
253
    pg['host'] = 'localhost'
254
    pg['dbname'] = 'gisdata'
255
    pg['user'] = ''
256
    pg['password'] = ''
257
    pg['table'] = ''
258
    pg['geom'] = 'the_geom'
259
    
260
    
261
    whereCondition = dataString.split("sql")[1][1:]
262
    cmp = dataString.split("sql")[0].split(" ")
263
    
264
    for c in cmp:
265
      if c[:1] == "(":
266
        pg['geom'] = c[1:][:-1]
267
      else:
268
        kvp = c.split("=")
269
        if (len(kvp) >= 2):
270
          pg[kvp[0]] =  kvp[1]
271
    
272
    connString = 'host=' + pg['host'] + " user=" + pg['user']
273
    
274
    if (len(pg['password'].replace("\'", "")) > 0):
275
      connString += " password=" + pg['password'].replace("'", "")
276
    
277
    connString += " dbname=" + pg['dbname']
278
    connString = connString.replace("\'", "")
279
        
280
    dataString = pg['geom'] + " FROM " + pg['table'].replace("\"", "")
281
    filterString = whereCondition.replace("\"", "")
282
    return (connString, dataString, filterString)
283

    
284
       
285
  # Write the map layers
286
  def writeMapLayers(self):
287
    # get the list of maplayer nodes
288
    maplayers = self.qgs.getElementsByTagName("maplayer")
289
    print "Processing ", len(maplayers), " layers"
290
    count = 0
291
    for lyr in maplayers:
292
      count += 1
293
      print "Processing layer ", count 
294
      # The attributes of the maplayer tag contain the scale dependent settings,
295
      # visibility, and layer type
296

    
297
      self.outFile.write("  LAYER\n")
298
      # write the name of the layer
299
      # first check to see if there is a name
300
      if len(lyr.getElementsByTagName("layername")[0].childNodes) > 0:
301
        self.outFile.write("    NAME '" + lyr.getElementsByTagName("layername")[0].childNodes[0].nodeValue.encode('utf-8').replace("\"", "") + "'\n")
302
      else:
303
        self.outFile.write("    NAME 'LAYER%s'\n" % count)
304
      if lyr.getAttribute("type").encode('utf-8') == 'vector':  
305
        self.outFile.write("    TYPE " + lyr.getAttribute("geometry").encode('utf-8').upper() + "\n")
306
      elif lyr.getAttribute("type").encode('utf-8') == 'raster':  
307
        self.outFile.write("    TYPE " + lyr.getAttribute("type").encode('utf-8').upper() + "\n")
308
 
309
      # Set min/max scales
310
      if lyr.getAttribute('scaleBasedVisibilityFlag').encode('utf-8') == 1:
311
        self.outFile.write("    MINSCALE " + lyr.getAttribute('minScale').encode('utf-8') + "\n")
312
        self.outFile.write("    MAXSCALE " + lyr.getAttribute('maxScale').encode('utf-8') + "\n")
313

    
314
      # data
315
      dataString = lyr.getElementsByTagName("datasource")[0].childNodes[0].nodeValue.encode('utf-8')
316

    
317
      # test if it is a postgis, grass or WMS layer
318
      # is there a better way to do this? probably.
319
      try:
320
        providerString = lyr.getElementsByTagName("provider")[0].childNodes[0].nodeValue.encode('utf-8')
321
      except:
322
        # if providerString is null
323
        providerString = ''
324

    
325
      if providerString == 'postgres':
326
        # it's a postgis layer
327
        (pgConnString, sqlData, sqlFilter) = self.parsePostgisConnection(dataString)
328
        self.outFile.write("    CONNECTIONTYPE postgis\n")
329
        self.outFile.write("    CONNECTION '" + pgConnString + "'\n")
330
        self.outFile.write("    DATA '" + sqlData + "'\n")
331
        if sqlFilter:
332
          self.outFile.write("    FILTER '" + sqlFilter + "'\n")
333

    
334
      elif providerString == 'wms' and lyr.getAttribute("type").encode('utf-8').upper() == 'RASTER':
335
        # it's a WMS layer 
336
        self.outFile.write("    CONNECTIONTYPE WMS\n")
337
        self.outFile.write("    CONNECTION '" + dataString + "'\n")
338
        rasterProp = lyr.getElementsByTagName("rasterproperties")[0]
339
        # loop thru wmsSubLayers  
340
        wmsSubLayers = rasterProp.getElementsByTagName('wmsSublayer')
341
        wmsNames = []
342
        wmsStyles = []
343
        for wmsLayer in wmsSubLayers: 
344
          wmsNames.append( wmsLayer.getElementsByTagName('name')[0].childNodes[0].nodeValue.encode('utf-8').replace("\"", "") )
345
          try: 
346
            wmsStyles.append( wmsLayer.getElementsByTagName('style')[0].childNodes[0].nodeValue.encode('utf-8') )
347
          except:
348
            wmsStyles.append( '' )
349
        # Create necesssary wms metadata
350
        format = rasterProp.getElementsByTagName('wmsFormat')[0].childNodes[0].nodeValue.encode('utf-8')
351
        self.outFile.write("    METADATA\n")
352
        self.outFile.write("      'wms_name' '" + ','.join(wmsNames) + "'\n")
353
        self.outFile.write("      'wms_server_version' '1.1.1'\n")
354
        try:
355
          ct = lyr.getElementsByTagName('coordinatetransform')[0]
356
          srs = ct.getElementsByTagName('sourcesrs')[0].getElementsByTagName('spatialrefsys')[0]
357
          epsg = srs.getElementsByTagName('epsg')[0].childNodes[0].nodeValue.encode('utf-8')
358
          self.outFile.write("      'wms_srs' 'EPSG:4326 EPSG:" + epsg + "'\n")
359
        except:
360
          pass
361
        self.outFile.write("      'wms_format' '" + format + "'\n")
362
        self.outFile.write("      'wms_style' '" + ','.join(wmsStyles) + "'\n")
363
        self.outFile.write("    END\n")
364

    
365
      else: 
366
        # its a standard ogr, gdal or grass layer
367
        self.outFile.write("    DATA '" + dataString + "'\n")
368
      
369
      # WMS settings for all layers
370
      self.outFile.write("    METADATA\n")
371
      if len(lyr.getElementsByTagName("layername")[0].childNodes) > 0:
372
        self.outFile.write("      'wms_title' '" 
373
           + lyr.getElementsByTagName("layername")[0].childNodes[0].nodeValue.encode('utf-8').replace("\"", "") + "'\n")
374
      else:
375
        self.outFile.write("      'wms_title' 'LAYER%s'\n"  % count)
376

    
377
      self.outFile.write("    END\n")
378

    
379
      self.outFile.write("    STATUS ON\n")
380

    
381
      opacity = int ( 100.0 * 
382
           float(lyr.getElementsByTagName("transparencyLevelInt")[0].childNodes[0].nodeValue.encode('utf-8')) / 255.0 ) 
383
      self.outFile.write("    TRANSPARENCY " + str(opacity) + "\n")
384

    
385
      #self.outFile.write("    PROJECTION\n")
386
      ## Get the destination srs for this layer and use it to create
387
      ## the projection section
388
      #destsrs = self.qgs.getElementsByTagName("destinationsrs")[0]  
389
      #epsgCode = destsrs.getElementsByTagName("epsg")[0].childNodes[0].nodeValue.encode('utf-8')
390
      #proj4Text = destsrs.getElementsByTagName("proj4")[0].childNodes[0].nodeValue.encode('utf-8')
391
      ## the proj4 text string needs to be reformatted to make mapserver happy
392
      #if epsgCode:
393
      #  self.outFile.write(self.formatEpsg(epsgCode))
394
      #else:
395
      #  self.outFile.write(self.formatProj4(proj4Text))
396
      #self.outFile.write("    END\n")
397
      
398
      # projection from the layer
399
      self.writeProjectionSection(lyr=lyr)
400
      
401
      scaleDependent = lyr.getAttribute("scaleBasedVisibilityFlag").encode('utf-8')
402
      if scaleDependent == '1':
403
        # get the min and max scale settings
404
        minscale = lyr.getAttribute("minScale").encode('utf-8')
405
        maxscale = lyr.getAttribute("maxScale").encode('utf-8')
406
        if minscale > '':
407
          self.outFile.write("    MINSCALE " + minscale + "\n")
408
        if maxscale > '':
409
          self.outFile.write("    MAXSCALE " + maxscale + "\n")
410

    
411
      
412
      # Check for label field (ie LABELITEM) and label status
413
      try:
414
        labelOn    = lyr.getElementsByTagName(     "label")[0].childNodes[0].nodeValue.encode('utf-8')
415
        labelField = lyr.getElementsByTagName("labelfield")[0].childNodes[0].nodeValue.encode('utf-8')
416
        if labelField != '' and labelField is not None and labelOn == "1":
417
          self.outFile.write("    LABELITEM '" + labelField + "'\n");
418
      except:
419
        # no labels
420
        pass
421
      
422
      # write the CLASS section for rendering
423
      # First see if there is a single symbol renderer
424
      if lyr.getElementsByTagName("singlesymbol").length > 0:
425
        symbolNode = lyr.getElementsByTagName("singlesymbol")[0].getElementsByTagName('symbol')[0] 
426
        self.simpleRenderer(lyr, symbolNode)
427
      elif lyr.getElementsByTagName("graduatedsymbol").length > 0:
428
        self.graduatedRenderer(lyr, lyr.getElementsByTagName("graduatedsymbol")[0].getElementsByTagName('symbol')[0] )
429
      elif lyr.getElementsByTagName("continuoussymbol").length > 0:
430
        self.continuousRenderer(lyr, lyr.getElementsByTagName("continuoussymbol")[0] )
431
      elif lyr.getElementsByTagName("uniquevalue").length > 0:
432
        self.uniqueRenderer(lyr, lyr.getElementsByTagName("uniquevalue")[0].getElementsByTagName('symbol')[0] )
433

    
434
      # end of LAYER
435
      self.outFile.write("  END\n\n")
436

    
437

    
438
  # Simple renderer ouput
439
  # We need the layer node and symbol node
440
  def simpleRenderer(self, layerNode, symbolNode):
441
    # get the layers geometry type
442
    geometry = layerNode.getAttribute("geometry").encode('utf-8').upper()
443

    
444
    self.outFile.write("    CLASS\n")
445

    
446
    self.outFile.write("       NAME '" 
447
         + layerNode.getElementsByTagName("layername")[0].childNodes[0].nodeValue.encode('utf-8').replace("\"", "") + "' \n")
448

    
449
    self.outFile.write("       STYLE\n")
450
    # use the point symbol map to lookup the mapserver symbol type
451
    symbol = self.msSymbol( geometry, symbolNode )
452
    self.outFile.write("         SYMBOL " + symbol + " \n")
453

    
454
    # Symbol size 
455
    if geometry == 'POINT':
456
      self.outFile.write("        SIZE " 
457
          + symbolNode.getElementsByTagName('pointsize')[0].childNodes[0].nodeValue.encode('utf-8')  
458
          + " \n")
459
    if geometry == 'LINE':
460
      self.outFile.write("        WIDTH " 
461
          + symbolNode.getElementsByTagName('outlinewidth')[0].childNodes[0].nodeValue.encode('utf-8')  
462
          + " \n")            
463
          
464
    # outline color - only valid for polygons
465
    if geometry == 'POLYGON':
466
      outlineNode = symbolNode.getElementsByTagName('outlinecolor')[0]
467
      self.outFile.write("         OUTLINECOLOR " 
468
            + outlineNode.getAttribute('red') + ' '
469
            + outlineNode.getAttribute('green') + ' '
470
            + outlineNode.getAttribute('blue')
471
            + "\n")
472
      # color
473
      colorNode = symbolNode.getElementsByTagName('fillcolor')[0]
474
      self.outFile.write("         COLOR " 
475
            + colorNode.getAttribute('red') + ' '
476
            + colorNode.getAttribute('green') + ' '
477
            + colorNode.getAttribute('blue')
478
            + "\n")
479
    else:
480
      colorNode = symbolNode.getElementsByTagName('outlinecolor')[0]
481
      self.outFile.write("         COLOR " 
482
            + colorNode.getAttribute('red') + ' '
483
            + colorNode.getAttribute('green') + ' '
484
            + colorNode.getAttribute('blue')
485
            + "\n")    
486
    
487
    self.outFile.write("       END\n")
488

    
489
    self.outFile.write( self.msLabel( layerNode ) )
490

    
491
    # end of CLASS  
492
    self.outFile.write("    END\n")
493
        
494

    
495
  # Graduated symbol renderer output
496
  def graduatedRenderer(self, layerNode, symbolNode):
497
    # get the layers geometry type
498
    geometry = layerNode.getAttribute("geometry").encode('utf-8').upper()
499

    
500
    # get the renderer field for building up the classes
501
    classField = layerNode.getElementsByTagName('classificationattribute')[0].childNodes[0].nodeValue.encode('utf-8')  
502
    # write the render item
503
    self.outFile.write("    CLASSITEM '" + classField + "'\n")
504

    
505
    # write the rendering info for each class
506
    classes = layerNode.getElementsByTagName('symbol')
507
    for cls in classes:
508
      self.outFile.write("    CLASS\n")
509

    
510
      lower = cls.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8')
511
      upper = cls.getElementsByTagName('uppervalue')[0].childNodes[0].nodeValue.encode('utf-8')
512
    
513
      # If there's a label use it, otherwise autogenerate one
514
      try:
515
        label = cls.getElementsByTagName('label')[0].childNodes[0].nodeValue.encode('utf-8')
516
        self.outFile.write("      NAME '" + label + "'\n") 
517
      except: 
518
        self.outFile.write("      NAME '" + lower + " < " + classField + " < " + upper + "'\n") 
519

    
520
      self.outFile.write("      EXPRESSION ( ([" + classField + "] >= " + lower 
521
                           + ") AND ([" + classField + "] <= " + upper + ") )\n") 
522

    
523
      self.outFile.write("      STYLE\n")
524
      symbol = self.msSymbol( geometry, symbolNode )
525
      self.outFile.write("        SYMBOL " + symbol + "\n")
526

    
527
      # Symbol size 
528
      if geometry == 'POINT':
529
        self.outFile.write("        SIZE " 
530
            + cls.getElementsByTagName('pointsize')[0].childNodes[0].nodeValue.encode('utf-8')  
531
            + " \n")
532

    
533
      if geometry == 'LINE':
534
        self.outFile.write("        WIDTH " 
535
            + cls.getElementsByTagName('outlinewidth')[0].childNodes[0].nodeValue.encode('utf-8')  
536
            + " \n")            
537
            
538
      # outline color - only valid for polygons
539
      if geometry == 'POLYGON':
540
        outlineNode = cls.getElementsByTagName('outlinecolor')[0]
541
        self.outFile.write("         OUTLINECOLOR " 
542
              + outlineNode.getAttribute('red') + ' '
543
              + outlineNode.getAttribute('green') + ' '
544
              + outlineNode.getAttribute('blue')
545
              + "\n")
546
        # color
547
        colorNode = cls.getElementsByTagName('fillcolor')[0]
548
        self.outFile.write("         COLOR " 
549
              + colorNode.getAttribute('red') + ' '
550
              + colorNode.getAttribute('green') + ' '
551
              + colorNode.getAttribute('blue')
552
              + "\n")
553
      else:
554
        colorNode = cls.getElementsByTagName('outlinecolor')[0]
555
        self.outFile.write("         COLOR " 
556
              + colorNode.getAttribute('red') + ' '
557
              + colorNode.getAttribute('green') + ' '
558
              + colorNode.getAttribute('blue')
559
              + "\n")
560
              
561
      self.outFile.write("        END\n")
562

    
563
      # label
564
      self.outFile.write( self.msLabel( layerNode ) )
565

    
566
      # end of CLASS  
567
      self.outFile.write("    END\n")
568

    
569
  # Continuous symbol renderer output
570
  def continuousRenderer(self, layerNode, symbolNode):
571
    # get the layers geometry type
572
    geometry = layerNode.getAttribute("geometry").encode('utf-8').upper()
573

    
574
    # get the renderer field for building up the classes
575
    classField = layerNode.getElementsByTagName('classificationattribute')[0].childNodes[0].nodeValue.encode('utf-8')  
576

    
577
    # write the rendering info for each class
578
    self.outFile.write("    CLASS\n")
579

    
580
    # Class name irrelevant for color ramps since mapserver can't render their legend
581
    #self.outFile.write("      NAME '" + classField + "'\n")
582

    
583
    # color
584
    lower = symbolNode.getElementsByTagName('lowestsymbol')[0].getElementsByTagName('symbol')[0]
585
    upper = symbolNode.getElementsByTagName('highestsymbol')[0].getElementsByTagName('symbol')[0]
586
    lowerColor = lower.getElementsByTagName('fillcolor')[0]
587
    upperColor = upper.getElementsByTagName('fillcolor')[0]
588

    
589
    # outline color
590
    outlineNode = lower.getElementsByTagName('outlinecolor')[0]
591

    
592
    self.outFile.write("      STYLE\n")
593
    
594
    # The first and last color of the ramp ( r g b r g b )
595
    self.outFile.write("        COLORRANGE " 
596
          + lowerColor.getAttribute('red') + " " 
597
          + lowerColor.getAttribute('green') + " " 
598
          + lowerColor.getAttribute('blue') + " " 
599
          + upperColor.getAttribute('red') + " " 
600
          + upperColor.getAttribute('green') + " " 
601
          + upperColor.getAttribute('blue') + "\n")
602

    
603
    # The range of values over which to ramp the colors
604
    self.outFile.write("        DATARANGE "
605
         + lower.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8') + ' '
606
         + upper.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8') + '\n')
607

    
608
    self.outFile.write("        RANGEITEM '" + classField + "'\n")                                        
609
    self.outFile.write("      END\n")
610

    
611
    self.outFile.write("      STYLE\n")
612
    self.outFile.write("        OUTLINECOLOR "
613
          + outlineNode.getAttribute('red') + " " 
614
          + outlineNode.getAttribute('green') + " " 
615
          + outlineNode.getAttribute('blue') + "\n") 
616
    self.outFile.write("      END\n")
617

    
618
    # label
619
    self.outFile.write( self.msLabel( layerNode ))
620

    
621
    # end of CLASS  
622
    self.outFile.write("    END\n")
623
    
624

    
625
  # Unique value renderer output
626
  def uniqueRenderer(self, layerNode, symbolNode):
627
    # get the renderer field for building up the classes
628
    classField = layerNode.getElementsByTagName('classificationattribute')[0].childNodes[0].nodeValue.encode('utf-8')  
629

    
630
    # get the layers geometry type
631
    geometry = layerNode.getAttribute("geometry").encode('utf-8').upper()
632
    
633
    # write the render item
634
    self.outFile.write("    CLASSITEM '" + classField + "'\n")
635

    
636
    # write the rendering info for each class
637
    classes = layerNode.getElementsByTagName('symbol')
638
    for cls in classes:
639
      self.outFile.write("    CLASS\n")
640

    
641
      try:
642
        lower = cls.getElementsByTagName('lowervalue')[0].childNodes[0].nodeValue.encode('utf-8')
643
      except IndexError:
644
        # set to blank in the case where the field used for rendering has no value
645
        lower = ""
646

    
647
      # If there's a label use it, otherwise autogenerate one
648
      try:
649
        label = cls.getElementsByTagName('label')[0].childNodes[0].nodeValue.encode('utf-8')
650
        self.outFile.write("      NAME '" + label + "'\n") 
651
      except:
652
        self.outFile.write("      NAME '" + classField + " = " + lower + "' \n") 
653

    
654
      self.outFile.write("      EXPRESSION '" + lower + "' \n") 
655

    
656
      # Get the symbol name
657
      symbol = self.msSymbol( geometry, symbolNode )  
658
      
659
      self.outFile.write("      STYLE\n")
660
      self.outFile.write("        SYMBOL " + symbol + "\n")
661

    
662
      # Symbol size 
663
      if geometry == 'POINT':
664
        self.outFile.write("        SIZE " 
665
            + cls.getElementsByTagName('pointsize')[0].childNodes[0].nodeValue.encode('utf-8')  
666
            + " \n")
667

    
668
      if geometry == 'LINE':
669
        self.outFile.write("        WIDTH " 
670
            + cls.getElementsByTagName('outlinewidth')[0].childNodes[0].nodeValue.encode('utf-8')  
671
            + " \n")            
672
            
673
      # outline color - only valid for polygons
674
      if geometry == 'POLYGON':
675
        outlineNode = cls.getElementsByTagName('outlinecolor')[0]
676
        self.outFile.write("         OUTLINECOLOR " 
677
              + outlineNode.getAttribute('red') + ' '
678
              + outlineNode.getAttribute('green') + ' '
679
              + outlineNode.getAttribute('blue')
680
              + "\n")
681
        # color
682
        colorNode = cls.getElementsByTagName('fillcolor')[0]
683
        self.outFile.write("         COLOR " 
684
              + colorNode.getAttribute('red') + ' '
685
              + colorNode.getAttribute('green') + ' '
686
              + colorNode.getAttribute('blue')
687
              + "\n")
688
      else:
689
        colorNode = cls.getElementsByTagName('outlinecolor')[0]
690
        self.outFile.write("         COLOR " 
691
              + colorNode.getAttribute('red') + ' '
692
              + colorNode.getAttribute('green') + ' '
693
              + colorNode.getAttribute('blue')
694
              + "\n")
695
              
696
      self.outFile.write("       END\n")
697

    
698
      # label
699
      self.outFile.write( self.msLabel( layerNode ))
700
      
701
      # end of CLASS  
702
      self.outFile.write("    END\n")
703
    
704
  # Utility method to format a proj4 text string into mapserver format
705
  def formatProj4(self, proj4text):
706
    parms = proj4text.split(" ")
707
    ret = ""
708
    for p in parms:
709
      p = p.replace("+","")
710
      ret = ret + "    '" + p + "'\n"
711
    return ret
712

    
713
  # utility method to format epsg code string
714
  def formatEpsg(self, epsgCode):
715
    ret = "    'init=epsg:" + epsgCode + "'\n"
716
    return ret
717

    
718
  # Determines the symbol name and adds it to the symbol queue
719
  def msSymbol(self, geometry, symbolNode):
720
    # contains the same markup for a layer regardless of type
721
    # so we infer a symbol type based on the geometry
722
    symbolName = ''
723
    symbol = '0'
724

    
725
    if geometry == 'POLYGON':
726
      symbol = '0'
727
    elif geometry == 'LINE':
728
      symbol = '0'
729
    elif geometry == 'POINT':
730
      try:
731
        symbolName = qgisSymbols[symbolNode.getElementsByTagName('pointsymbol')[0].childNodes[0].nodeValue.encode('utf-8')]
732
      except:
733
        symbolName = "CIRCLE"
734
      # make sure it's single quoted
735
      symbol = "'" + symbolName + "'"
736

    
737
    if symbolName == 'CIRCLE':
738
      self.symbolQueue['CIRCLE'] = """
739
      #Circle symbol
740
      SYMBOL
741
        NAME 'CIRCLE'
742
        TYPE ellipse
743
        FILLED true
744
        POINTS
745
          1 1
746
        END
747
      END """
748

    
749
    if symbolName == 'TRIANGLE':
750
      self.symbolQueue['TRIANGLE'] = """
751
      SYMBOL
752
        NAME "TRIANGLE"
753
        TYPE vector
754
        FILLED true
755
        POINTS
756
          0 1
757
         .5 0
758
          1 1
759
          0 1
760
        END
761
      END """
762

    
763
    return symbol
764

    
765
  # Label block creation
766
  # TODO field-based parameters, alignment, truetype fonts, sizes
767
  def msLabel(self, layerNode):
768
    # currently a very basic bitmap font
769
    labelNode = layerNode.getElementsByTagName('labelattributes')[0]
770
    labelField = labelNode.getElementsByTagName('label')[0].getAttribute('field').encode('utf-8')
771
    if labelField != '' and labelField is not None:
772
      labelBlock  = "     LABEL \n"
773
     
774
      labelBlock += "      SIZE medium\n"
775
      labelBlock += "      COLOR 0 0 0 \n"
776
 
777
      # Include label angle if specified
778
      # Note that angles only work for truetype fonts which aren't supported yet
779
      angle = labelNode.getElementsByTagName('angle')[0].getAttribute('value').encode('utf-8')
780
      labelBlock += "      ANGLE " + angle + "\n"
781
     
782
      # Include label buffer if specified
783
      # Note that the buffer has different meaning in qgis vs mapserver
784
      # mapserver just adds blank space around the label while
785
      # qgis uses a fill color around the label
786
      # Note that buffer only works for truetype fonts which aren't supported yet
787
      buffer = labelNode.getElementsByTagName('buffersize')[0].getAttribute('value').encode('utf-8')
788
      labelBlock += "      BUFFER " + buffer + "\n"
789

    
790
      labelBlock += "     END \n"
791
      return labelBlock
792
    else:
793
      return ''
794