# -*- coding:latin1 -*-
# Copyright (C) 2004 by Intevation GmbH
# Authors:
# Jan Schüngel <jschuengel@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""
Classes to represent '.map'-file Objects.

The following Classes, which are implemented in
mapscript are not implemented yet in this extension:

 DBFInfo, errorObj, fontSetObj, graticuleObj, imageObj, itemObj,
 labelCacheMemberObj, labelCacheObj,
 markerCacheMembet, msTiledSHPLayerInfo, queryMapObj,
 referenzMapObj, resultCacheMemberObj, resultCacheObj,
 shapefileObj, shapeObj, VectorObj

the following are only used to create a necessary object. They are not
realy created as a MF_Object.

 lineObj, pointObj
"""

__version__ = "$Revision: 2864 $"
# $Source$
# $Id: mapfile.py 2864 2008-08-08 23:39:11Z elachuni $


# ##################################################
#
# import necessary modules from python and/or thuban
#
# ##################################################

import os

from Thuban.Model.color import Color, Transparent 

from Thuban.Model.classification import ClassGroupDefault, \
                                        ClassGroupSingleton, ClassGroupRange

import mapscript
from mapscript import layerObj, classObj, colorObj, styleObj, rectObj, symbolObj, \
                      pointObj, lineObj
                      
from Thuban.Model.layer import RasterLayer

# ###################################
#
# Definition of dictionaries
#
# the dictonaries are like in mapscript and are used to make it
# easear to unterstand the key from mapscript for the settings
#
# ###################################

shp_type = { 0:'point',
             1:'line',
             2:'polygon',
             3:'raster',
             4:'annotation',
             5:'circle',
             6:'query'}

unit_type = { 0:"inches",
              1:"feet",
              2:"miles",
              3:"meters",
              4:"kilometers",
              5:"dd"}

legend_status_type = { 0:"OFF",
                       1:"ON",
                       3:"embed" } 
                       # 2 = Default but is not allowed here
		       
scalebar_status_type = { 0:"OFF",
                         1:"ON",
                         3:"embed" } 
                         # 2 = Default but is not allowed here
			 
scalebar_style_type = { 0:"0",
                        1:"1" }

scalebar_position_type = { mapscript.MS_UL:"ul",
                           mapscript.MS_LR:"lr",
                           mapscript.MS_UR:"ur",
                           mapscript.MS_LL:"ll",
                           mapscript.MS_UC:"uc",
                           mapscript.MS_LC:"lc"}

layer_status_type = { 0:"OFF",
                      1:"ON",
                      2:"default"}

layer_connection_type = { mapscript.MS_INLINE:"inline",
                          mapscript.MS_SHAPEFILE:"shapefile",
                          mapscript.MS_TILED_SHAPEFILE:"tiled shapefile",
                          mapscript.MS_SDE:"sde",
                          mapscript.MS_OGR:"ogr",
                          mapscript.MS_POSTGIS:"postgis",
                          mapscript.MS_WMS:"wms",
                          mapscript.MS_ORACLESPATIAL:"oracle spatial",
                          mapscript.MS_WFS:"wfs",
                          mapscript.MS_GRATICULE:"graticule",
                          mapscript.MS_MYGIS:"mygis",
                          mapscript.MS_RASTER:"raster"}

legend_position_type = { 0:"ul",
                         1:"lr",
                         2:"ur",
                         3:"ll",
                         6:"uc",
                         7:"lc"}

label_size_type = { 0:"tiny",
                    1:"small",
                    2:"medium",
                    3:"large",
                    4:"giant" }

#TODO: build in truetype (0:"truetype") support
label_font_type = { 1:"bitmap" }

label_position_type = { 0:"ul",
                        1:"lr",
                        2:"ur",
                        3:"ll",
                        4:"cr",
                        5:"cl",
                        6:"uc",
                        7:"lc",
                        8:"cc",
                       10:"auto"}


# ##################################################
#
# Class Definition 
#
# ##################################################

# ##################################################
# General Classes that are not all explicitly defined through
# a mapfile, but rather some helper-classes.

class MF_Rectangle:
    """
    Represents an rectanle with the bottom left
    and top right corner.
    """
    def __init__(self,mf_rect):
        self._rect = mf_rect
    
    def get_minx(self):
        return self._rect.minx

    def get_miny(self):
        return self._rect.miny

    def get_maxx(self):
        return self._rect.maxx

    def get_maxy(self):
        return self._rect.maxy
    
    def get_rect(self):
        return (self._rect.minx,self._rect.miny,self._rect.maxx,self._rect.maxy)

    def set_rect(self, minx, miny, maxx, maxy):
        self._rect.minx = minx
        self._rect.miny = miny
        self._rect.maxx = maxx
        self._rect.maxy = maxy

class MF_Color:
    """
    The corresponding MapScript object contains also the
    attribute pen which defines the stroke of the feature.
    But this actually has nothing to do with the color and
    therefore is not support here.
    
    It needs to be discussed with the MapServer developers
    whether pen would better be moved to another class.
    
    The hex color definition which is also supported by
    mapscript and Thuban is not supported as it does
    not add any capability.
    
    color is definied as RGB 0..255
    """    
    def __init__(self, mf_color):
        self._color = mf_color
        self._tbc_red = (float(self.get_red())/255)
        self._tbc_green = (float(self.get_green())/255)
        self._tbc_blue = (float(self.get_blue())/255)
        self._thubancolor = Color(self._tbc_red,
				  self._tbc_green,
				  self._tbc_blue)

    # TODO : Check if it is necessary to use rgb colors alone
    # or whether it is sufficient to only use the Thuban Color.
    # In some it is necessary as red == -1 indicates that no color
    # is set.
    def get_red(self):
        return self._color.red

    def get_green(self):
        return self._color.green

    def get_blue(self):
        return self._color.blue
        
    def set_rgbcolor(self, red, green, blue):
        self._color.red = red
        self._color.green = green
        self._color.blue = blue
        
        self._tbc_red = (float(self.get_red())/255)
        self._tbc_green = (float(self.get_green())/255)
        self._tbc_blue = (float(self.get_blue())/255)
        self._thubancolor = Color(self._tbc_red,
				  self._tbc_green, 
				  self._tbc_blue)
        
    def get_mfcolor(self):
        return self._color

    def get_thubancolor(self):
        return self._thubancolor     

    def set_thubancolor(self, thuban_color):
        if thuban_color != Transparent:
            self._color.red = int(thuban_color.red * 255)
            self._color.green = int(thuban_color.green * 255)
            self._color.blue = int(thuban_color.blue * 255)
            self._thubancolor = thuban_color


class MF_Metadata:
    """
    Metadata is not a Object in mapscript witch can be used
    by ease. Only the infos can get with the functions
    "getFirstMetaDataKey", "getNextMetaDataKey" and "getMetaData".
    To get some special Metadata you need a key. So there is a special 
    function which create a list of the metadatakeys.
    """
    def __init__(self, mapobj):
        self.mapobj = mapobj

    def remove_allmetadata(self):
        keylist = self.get_metadatakeys()
        if keylist:
            for key in keylist:
                self.mapobj.removeMetaData(key)

    def get_metadatakeys(self):
        keylist = []
        try:
            metafkey =self.mapobj.getFirstMetaDataKey()
            keylist.append(metafkey)
        except:
            return None
        else:
            if metafkey:
                while metafkey:
                    metafkey = self.mapobj.getNextMetaDataKey(metafkey)
                    if metafkey:
                        keylist.append(metafkey)
                return keylist

    def get_metadata(self):
        keylist = self.get_metadatakeys()
        metadatalist = []
        if keylist:
            for key in keylist:
                metadatalist.append([key,self.mapobj.getMetaData(key)])
            return metadatalist
        else:
            return None
    
    def get_metadatabykey(self, key):
        return self.mapobj.getMetaData(key)

    def remove_metadatabykey(self, key):
        self.mapobj.removeMetaData(key)
    
    def add_metadata(self, key, data):
        self.mapobj.setMetaData(key,data)

# ################################################
# Classes for MapServer Objects as they are
# explicitly defined in a mapfile

class MF_Outputformat:
    """
    The Outputformat defines which and how the image is
    created by the mapserver.
    
    The following settings are used:
    name
    
    The following settings are not used:
    mimetye, driver, extension, renderer, imagemode, transparent,
    bands, numfrotmatoptions, formatoptions, refcount, inmapfile
    setExtension(), setMimetype(), setOption(), getOption()
    """
    def __init__(self, mf_outputformat):
        self._outputformat = mf_outputformat
    
    def get_name(self):
        return self._outputformat.name
    

class MF_Symbol:
    """
    defines a single symbol which is used in the Symbolset
    
    the following settings are used:
    name, type, filled,
    
    the following settings are not used:
    sizex, sizey, points, numpoints, stylelength,
    style, imagepath, transparent, transparentcolor, character, antialias,
    font, gap, position, linecap, linejoin, linejoinmaxsize, setPoints(),
    getPoints(), setStyle()
    """
    def __init__(self, mf_symbol = "newone"):
        # create a circle Object like shown in Thuban
        # because Thuban don't support other symbols
	
	# TODO: include the options to create a symbol, but
	#      first implement a methode to edit Symbols in Thuban
        if mf_symbol == "newone":
            mf_symbol = symbolObj("")
            newpoint = pointObj()
            newpoint.x = 1
            newpoint.y = 1
            newline = lineObj()
            newline.add(newpoint)
            mf_symbol.setPoints(newline)
            mf_symbol.type = mapscript.MS_SYMBOL_ELLIPSE
        
        self._symbol = mf_symbol
    
    def get_symbolObj(self):
        return self._symbol
    
    def get_name(self):
        return self._symbol.name
    
    def set_name(self, new_name):
        self._symbol.name = new_name
    
    def get_type(self):
        return self._symbol.type
    
    def set_type(self, new_type):
        # TODO include a function to set the type by a string
        self._symbol.type = new_type
    
    def get_filled(self):
        return self._symbol.filled
    
    def set_filled(self, new_filled):
        if new_filled:
            self._symbol.filled = 1
        else:
            self._symbol.filled = 0


class MF_SymbolSet:
    """
    defines a set of symbols, may be there can only be one
    
    the following settings are used:
    numsymbols,
    appendSymbol()
    
    filename, imagecachesize, symbol, getSymbol(),
    getSymbolByName(), index(), removeSymbol(),
    save()    
    """
    def __init__(self, mf_symbolset):
        self._symbolset = mf_symbolset
        
        # Initial Symbol List
        self._symbols = []
        self._i = 1
        while self._i < self._symbolset.numsymbols:
            self._symbols.append(MF_Symbol(self._symbolset.getSymbol(self._i)))
            self._i += 1

    def add_symbol(self, new_symbol):
        self._symbolset.appendSymbol(new_symbol.get_symbolObj())
        self._symbols.append(new_symbol)
        # the save function must be run to set the symbols to the
        # mapfile. I don't know why this ist so but it must be.
	# the file is empty then an we can delete it
        self._symbolset.save("tempsymbol")
        os.remove("tempsymbol")

    def get_symbol(self, symbolnr):
        if symbolnr < self._symbolset.numsymbols:
            return self._symbols[symbolnr-1]
        else:
            return None


class MF_Class:
    """
    The following parameters and functions, which the mapscript style obj
    contains, are used:
    styles, numstyles, name, status, keyimage, layer,
    getExpressionString(), setExpression(), getMetaData(), getFirstMetaDataKey(),
    getNextMetaDataKey(), getStyle()

    The following parameters and functions are not used:
    label, title, template, type, minscale, maxscale, debug,
    setExpression(), setText(), setMetaData(), drawLegendIcon(),
    createLegendIcon(), insertStyle(), removeStyle(), moveStyleUp(),
    moveStyleDown()
    """
    def __init__(self, mf_class, map):
        """
        Initialized a class from them given mapscript Class Object
	with a list of the included styles.
	Metadata Object will be created from the Metadata informations
	wich are holt as a List i think.
        """
        self._clazz = mf_class
        self.map = map
        self._styles = []
        self._numstyles = mf_class.numstyles
        for i in range(0,self._numstyles,1):
            self._styles.append(MF_Style(mf_class.getStyle(i)), self.map)
        
        if self._clazz.getExpressionString() == '"(null)"':
            self._expression = None
        else:
            self._expression = self._clazz.getExpressionString()
        
        self.metadata = MF_Metadata(self._clazz)

    def get_styles(self):
        return self._styles
    
    def get_name(self):
        return self._clazz.name
        
    def get_keyimage(self):
        return self._clazz.keyimage
        
    def get_expressionstring(self):
        return self._expression
    
    def set_name(self, newname):
        self._clazz.name = newname
    
    def set_expressionstring(self, newstring):
        self._clazz.setExpression(newstring)
        self._expression = self._clazz.getExpressionString()
    
    def get_status(self):
        if self._clazz.status == 1:
            return True
        else:
            return False
    
    def set_status(self, new_status):
        if new_status:
            self._clazz.status = 1
        else:
            self._clazz.status = 0

    def add_thubanstyle(self, tb_style, type="default"):
        """
        added a thuban style object to the mapobject
        """
        if type == "line":
            new_styleobj = MF_Style(styleObj(self._clazz), self.map)
            new_styleobj.set_color(tb_style.GetLineColor())
            new_styleobj.set_width(tb_style.GetLineWidth())
        elif type == "point":
            # set a default symbol to show circles not only a small dot
            # symbol "circle" must create before
            # TODO: create a Symbol (more see MF_SymbolSet)
            # first the default symbol circle will be created and the size 8
            new_styleobj = MF_Style(styleObj(self._clazz), self.map)
            if tb_style.GetFill() == Transparent:
                new_styleobj.set_symbolname('circle')
                if tb_style.GetLineColor() != Transparent:
                    new_styleobj.set_color(tb_style.GetLineColor())
            else:
                new_styleobj.set_symbolname('circle_filled')
                new_styleobj.set_color(tb_style.GetFill())
                if tb_style.GetLineColor() != Transparent:
                    new_styleobj.set_linecolor(tb_style.GetLineColor())
            new_styleobj.set_size(9)
        else:
    # Suppose this is a polygon.  We'll need two styles for applying
    # the background color and outline width, as explained in
    # http://mapserver.gis.umn.edu/docs/faq/faqsection_view?section=Map%20Output
            if tb_style.GetFill() != Transparent:
                new_styleobj = MF_Style(styleObj(self._clazz), self.map)
                new_styleobj.set_color(tb_style.GetFill())
    # And a second style.
    # The order here matters (first the background, then the
            if tb_style.GetLineColor() != Transparent:
                new_styleobj = MF_Style(styleObj(self._clazz), self.map)
                new_styleobj.set_linecolor(tb_style.GetLineColor())
                new_styleobj.set_width(tb_style.GetLineWidth())



class MF_Layer:
    """
    The following parameters and functions, which the mapscript style obj
    contains, are used:

    classitem, numclasses, name, data, type
    getClass(), getProjection(), getExtent(), getMetaData(),
    getFirstMetaDataKey(), getNextMetaDataKey(), status, 


    The following paramters and functions are not used:
    index, map, header, footer, template, groupe, tolerance,
    toleranceunits, symbolscale, minscale, maxscale, labelminscale
    labelmaxscale, sizeunits, maxfeatures, offsite, transform, labelcache
    postlabelcache, labelitem, labelsizeitem, labelangleitem, labelitemindex
    labelsizeitemindex, labelangleitemindex, tileitem, tileindex, units
    numitems, filteritem, styleitem, requires
    labelrequires, transparency, dump, debug, numprocessing, numjoins,
    removeClass(), open(), close(), getShape(), getNumResults(), getResult()
    getItem(), promote(), demote(), draw(), drawQuery(), queryByAttributes()
    queryByPoint(), queryByRect(), queryByFeatures(), queryByShape(),
    setFilter(), setFilterString(), setWKTProjection(), setProjection()
    addFeature(), getNumFeatures(), setMetaData(), removeMetaData(),
    getWMSFeatureInfoURL(), executeWFSGetFeature(), applySLD(), applySLDURL()
    enerateSLD(), moveClassUp(), moveClassDown(), setProcessing(),
    getProcessing(), clearProcessing()   
    """

    def __init__(self, mf_layer, map):
        """
	Creates the Layer Object from the mapscript Layer Object.
	the class objects in the layer object will be stored in
	an array. The metadata are created as a new object.
        """
        self._mf_layer = mf_layer
        self.map = map
        # Create Classes 
        # there could be more then 1 
        self._classes = []
        for i in range (self._mf_layer.numclasses):
            self._classes.append(MF_Class(self._mf_layer.getClass(i), self.map))
            
        self._projection = MF_Projection(self._mf_layer.getProjection())
        
        # Create Metadata
        self._metadata = MF_Metadata(self._mf_layer)

    def get_index(self):
        return self._mf_layer.index

    def get_name(self):
        return self._mf_layer.name
    
    def get_data(self):
        return self._mf_layer.data

    def get_connection(self):
        return self._mf_layer.connnection

    def get_connectiontype(self):
        return self._mf_layer.connectiontype

    def get_classes(self):
        return self._classes
    
    def set_classes(self, new_classes):
        self._classes = new_classes
       
    def get_metadata(self):
        return self._metadata

    def set_metadata(self, new_metadata):
        self._metadata = new_metadata
    
    def get_type(self):
        return shp_type[self._mf_layer.type]

    def get_classitem(self):
        return self._mf_layer.classitem

    def get_projection(self):
        return self._projection
    
    def get_status(self):
        # returns a integer value
        # 0 = off, 1 = on, 2 = default(always on)
        if self._mf_layer.status == 0:
            return False
        else:
            return True
    
    def get_group(self):
        return self._mf_layer.group

    def set_group(self, new_group):
        self._mf_layer.group = new_group
    
    def set_name(self, newname):
        self._mf_layer.name = newname 
    
    def set_data(self, newdata, type="shape"):
        if type == "shape":
            self._mf_layer.data = newdata[:-4]
        else:
            self._mf_layer.data = newdata

    def set_connection (self, newconnection):
        self._mf_layer.connection = newconnection

    def set_connectiontype (self, newtype):
        self._mf_layer.connectiontype = newtype

    def set_status(self, newstatus):
        # status can set to true or false from thuban.
        # but mapserver supports the default value
        self._mf_layer.status = newstatus
    
    def set_classitem(self, tb_field):
        self._mf_layer.classitem = tb_field
    
    def set_type(self, tb_type):
        # if type = arc its a in shapetype line
        if tb_type == "arc":
            self._mf_layer.type = 1
        if tb_type == "raster":
            self._mf_layer.type = 3
        if shp_type.has_key(tb_type):
            self._mf_layer.type = tb_type
        else:
            for shp_paar_nr in shp_type:
               if shp_type[shp_paar_nr] == tb_type:
                   self._mf_layer.type = shp_paar_nr
                   return
    
    def set_projection(self, newprojection):
        self._mfnewprojstring = ""
        if newprojection:
            self._newparams = newprojection.GetAllParameters()
            for field in self._newparams:
                self._mfnewprojstring = self._mfnewprojstring+ "," + field
            self._mf_layer.setProjection(self._mfnewprojstring[1:])
            self._projection.set_projection(newprojection)
    
    def add_thubanclass(self, tb_class, type=""):
        """
        Add a thuban class object
        """ 
        new_class = MF_Class(classObj(self._mf_layer), self.map)
        self._classes.append(new_class)
        # set the class name to the Label form thuban if given,
        # else set it to the value
        if tb_class.GetLabel() != "":
            new_class.set_name(tb_class.GetLabel())
        else:
            if isinstance(tb_class, ClassGroupDefault):
                new_class.set_name("default")
            elif isinstance(tb_class, ClassGroupSingleton):
                new_class.set_name(str(tb_class.GetValue()))
            else:
                new_class.set_name(None)
        if self.get_type() == "line":
            new_class.add_thubanstyle(tb_class.GetProperties(), type="line")
        elif self.get_type() == "point":
            new_class.add_thubanstyle(tb_class.GetProperties(), type="point")
        else:
            new_class.add_thubanstyle(tb_class.GetProperties())
        if (type == "default"):
            return
        # removed the following two lines to check if the expressionstring
        # is needed for points, because if expressionstring is a range type,
        # no expressionstring in the default group is allowed
        elif (tb_class.Matches("DEFAULT")):
            return
           # new_class.set_expressionstring('/./')
        else:
            #check which type of expression 
            if isinstance(tb_class, ClassGroupRange):
                # get the needed infos from the Range-String
                self._range_begin = tb_class.GetRange()[0]
                self._range_min = str(tb_class.GetMin())
                self._range_max = str(tb_class.GetMax())
                self._range_end = tb_class.GetRange()[len(tb_class.GetRange())-1]
                self._range_umn = ""
                self._range_classitem = self.get_classitem()
                # generate the operator
                if self._range_begin == "[":
                    self._range_op1 = ">="
                elif self._range_begin == "]":
                    self._range_op1 = ">"
                else:
                    print "error in Thuban class properties"
                #build op1 string for the lower limit
                self._range_op1 = "[" + self._range_classitem + "] " + \
                                 self._range_op1 + " " +\
                                 self._range_min
                # build op2 string for the upper limit
                if self._range_end == "[":
                    self._range_op2 = "<"
                elif self._range_end == "]":
                    self._range_op2 = "<="
                else:
                    print "error in Thuban class properties"

                self._range_op2 = "[" + self._range_classitem + "] " + \
                                 self._range_op2 + " " +\
                                 self._range_max
                # we only need AND here at the moment, becaus of the limits 
                # in thuban
                self._range_combine = "AND"
                # check if the one limit is set to inf and then
                # remove the second expression becaus is not needed.
                if self._range_min == "-inf":
                    self._range_combine = ""
                    self._range_op1 = ""
                elif self._range_max == "inf":
                    self._range_combine = ""
                    self._range_op2 = ""
                # build the expression together
                self._range_umn = "(" + self._range_umn + \
                                 self._range_op1 + " " +\
                                 self._range_combine + \
                                 self._range_op2 + " )"
            
                #set the expression to the mapscript
                new_class.set_expressionstring(self._range_umn)
            else:
                new_class.set_expressionstring(str(tb_class.GetValue()))
        new_class.set_status(tb_class.IsVisible())

    def remove_allclasses(self):
        for i in range(len(self.get_classes())):
            self._mf_layer.removeClass(i)
        self.set_classes([])

class MF_Scalebar:
    """
    Represent the scalebar for a map

    The following settings are used:
    label, color, imagecolor, style, intervals, units,
    status, position, height, width 
    
    The following settings are (not) used:
    backgroundcolor,outlinecolor, postlabelcache
    """
    def __init__(self, mf_scalebar):
        self._scalebar = mf_scalebar
        self._color = MF_Color(self._scalebar.color)
        self._imagecolor = MF_Color(self._scalebar.imagecolor)
        self._label = MF_Label(self._scalebar.label)
    
    def get_label(self):
        return self._label
    
    def get_color(self):
        return self._color
    
    def get_imagecolor(self):
        return self._imagecolor
    
    def get_style(self):
        return self._scalebar.style
    
    def set_style(self, new_style):
        self._scalebar.style = new_style
    
    def get_size(self):
        #returns the size
        return (self._scalebar.width, self._scalebar.height)

    def set_size(self, new_width, new_height):
        self._scalebar.width = new_width
        self._scalebar.height = new_height
    
    def get_intervals(self):
        return self._scalebar.intervals
    
    def set_intervals(self, new_intervals):
        self._scalebar.intervals = new_intervals

    def get_units(self):
        #returns the unittype
        return unit_type[self._scalebar.units]

    def set_units(self, units):
        if unit_type.has_key(units):
            self._scalebar.units = units
        else:
            for unit_paar_nr in unit_type:
               if unit_type[unit_paar_nr] == units:
                   self._scalebar.units = unit_paar_nr
        
    def get_status(self, mode="integer"):
        if mode == "string":
            return scalebar_status_type[self._scalebar.status]
        else:
            return self._scalebar.status
    
    def set_status(self, new_status):
        if scalebar_status_type.has_key(new_status):
            self._scalebar.status = new_status
        else:
            for scalebar_status_type_nr in scalebar_status_type:
                if scalebar_status_type[scalebar_status_type_nr] == new_status:
                    self._scalebar.status = scalebar_status_type_nr
    
    def get_position(self, mode="integer"):
        if mode == "string":
            return scalebar_position_type[self._scalebar.position]
        else:
            return self._scalebar.position
    
    def set_position(self, new_position):
        if scalebar_position_type.has_key(new_position):
            self._scalebar.position = new_position
        else:
            for scalebar_position_type_nr in legend_position_type:
                if scalebar_position_type[scalebar_position_type_nr] \
                == new_position:
                    self._scalebar.position = scalebar_position_type_nr


class MF_Map:
    """
    The following parameters and functions, which the mapscript style obj
    contains, are used:

    name, numlayers, extent, shapepath, imagecolor, imagetype, units, getLayer,
    status, getProjection, getMetaData, getFirstMetaDataKey, getNextMetaDataKey,
    save(), setExtent(), height, width, setProjection(), setImageType(),

    The following parameters and functions are not used:
    maxsize, layers, symbolset, fontset, labelcache,
    transparent, interlace, imagequality, cellsize, debug, datapattern,
    templatepattern, configoptions
    zoomPoint(), zoomRectangle(), zoomScale(), getLayerOrder(), setLayerOrder(),
    clone(), removeLayer(), getLayerByName(), getSymbolByName(),
    prepareQuery(), prepareImage(), setOutputFormat(), draw(),
    drawQuery(), drawLegend(), drawScalebar(), embedLegend(), drawLabelCache(),
    nextLabel(), queryByPoint(), queryByRecht(), queryByFeatures(),
    queryByShape(), setWKTProjection(), saveQuery(), saveQueryASGML(),
    setMetaData(), removeMetaData(), setSymbolSet(), getNumSymbols(),
    setFontSet(), saveMapContext(), loadMapContext(), moveLayerUp(),
    moveLayerDown(), getLayersDrawingOrder(), setLayersDrawingOrder(),
    setConfigOption(), getConfigOption(), applyConfigOptions(), applySLD(),
    applySLDURL(), gernerateSLD(), procecssTemplate(), processLegemdTemplate(), processQueryTemplate(),
    getOutputFormatByName(), appendOutputFormat(), removeOutputFormat(),
    """
    def __init__(self, mf_map):
        """
	Create the map object from the mapfile mapobject which is given.

	All layers in the mapfile will be written to an array.
	"""
        self._mf_map = mf_map
        self._extent = MF_Rectangle(self._mf_map.extent)
        self._imagecolor = MF_Color(self._mf_map.imagecolor)
        self._web = MF_Web(self._mf_map.web)
        self._legend = MF_Legend(self._mf_map.legend)
        self._scalebar = MF_Scalebar(self._mf_map.scalebar)
        
        # TODO: generate the list dynamical by alle supported formats.
        # At the moment outputformat only get by name, but in a next
        # version there may be a function to get the outputformat by id
        # then there is no need to define the formattypes here
        image_types = ['gif', 'png', 'png24', 'jpeg', 'wbmp', \
	               'swf', 'pdf', 'imagemap'] 
        self._alloutputformats = []
        self._imagetype = self._mf_map.imagetype
        # create a temp imagtype, because the function getOutputFormatByName()
        # set the imagetype to the received OutputFormat        
        for fmtname in image_types:
            theformat = self._mf_map.getOutputFormatByName(fmtname)
            if theformat:
                self._alloutputformats.append(MF_Outputformat(theformat)) 
        self._mf_map.setImageType(self._imagetype)

        self._outputformat = MF_Outputformat(self._mf_map.outputformat)

        # symbols
        self._symbolset = MF_SymbolSet(self._mf_map.symbolset)

        # if the map name is not set it will return a MS string.
        if self._mf_map.name != "MS":
            self._name = self._mf_map.name
        else:
            self._name = None
            
        self._projection = MF_Projection(self._mf_map.getProjection())

        # Initial Layer List
        self._layers = []
        self._i = 0
        while self._i < self._mf_map.numlayers:
            self._layers.append(MF_Layer(self._mf_map.getLayer(self._i)), self)
            self._i += 1

        # Shapepath if not set, shapepath will be empty
        if self._mf_map.shapepath:
            self._shapepath = self._mf_map.shapepath
        else:
            self._shapepath = ""

        # Create Metadata
        self._metadata = MF_Metadata(self._mf_map)
    
    def create_new_layer(self):
        """
        the new layer must create inside the mapobj, because mapscript
        need the mapscript object as parameter for layerObj
        """
        new_layer = MF_Layer(layerObj(self._mf_map), self)
        self._layers.append(new_layer)
        # the new created layer must remove from the mapobject
        # because all layer will create new in export.
        #self._mf_map.removeLayer(self._mf_map.numlayers-1)
        return new_layer  
    
    def get_mappath(self):
        return self._mf_map.mappath
    
    def set_mappath(self, new_mappath):
        self._mf_map.mappath = new_mappath
    
    def get_outputformat(self):
        return self._outputformat
    
    def get_alloutputformats(self):
        return self._alloutputformats

    def get_imagetype(self):
        return self._mf_map.imagetype
    
    def set_imagetype(self, new_imagetype):
        self._mf_map.setImageType(new_imagetype)
    
    def get_symbolset(self):
        return self._symbolset
    
    def get_status(self):
        if self._mf_map.status == 1:
            return True
        else:
            return False
    
    def set_status(self, new_status):
        if new_status:
            self._mf_map.status = 1
        else:
            self._mf_map.status = 0

    def get_scalebar(self):
        return self._scalebar
    
    def get_web(self):
        return self._web
    
    def get_legend(self):
        return self._legend
    
    def get_extent(self):
        return self._extent
        
    def get_layers(self):
        return self._layers
    
    def get_numlayers(self):
        return self._mf_map.numlayers
    
    def get_projection(self):
        return self._projection
    
    def get_name(self):
        return self._name
    
    def get_shapepath(self):
        # where are the shape files located.
        return self._shapepath 
    
    def set_shapepath(self, new_shapepath):
        # where are the shape files located..
        self._shapepath = new_shapepath
    
    def get_imagetype(self):
        return self._mf_map.imagetype

    def get_layerorder(self):
        # shows the order of layer as list
        return self._mf_map.getLayerOrder()
    
    def set_layerorder(self, new_order):
        self._mf_map.setLayerOrder(new_order)
    
    def get_size(self):
        #returns the size
        return (self._mf_map.width, self._mf_map.height)
    
    def get_units(self):
        #returns the unittype
        return unit_type[self._mf_map.units]
    
    def get_imagecolor(self):
        return self._imagecolor
    
    def set_name(self, newname):
        # whitespace musst be replaced, either no
        # mapfile will be shown in the mapserver
        if newname:
            newname = newname.replace(" ","_")
        self._name = newname
        self._mf_map.name = newname
    
    def set_extent(self, newextent):
        """ Set the map's extent.  The map's size should already have been
            set when you call this function, so this function will fail
            if not.  Setting the size after the extent produces undesired
            results anyway. """
        width, height = self.get_size()
        if width <= 0 or height <= 0:
            raise mapscript.MapServerError, \
                  "No size set before calling set_extent"
        if newextent:
            self._newrect = MF_Rectangle(rectObj(*newextent))
            self._mf_map.setExtent(*newextent)
    
    def set_size(self, newwidth, newheight):
        self._mf_map.width = newwidth
        self._mf_map.height = newheight
    
    def set_projection(self, projection):
        self._mfnewprojstring = ""
        self._newparams = projection.GetAllParameters()
        for field in self._newparams:
            self._mfnewprojstring = self._mfnewprojstring+ "," + field
        self._mf_map.setProjection(self._mfnewprojstring[1:])
        self._projection.set_projection(projection)
    
    def set_units(self, units):
        if unit_type.has_key(units):
            self._mf_map.units = units
        else:
            for unit_paar_nr in unit_type:
               if unit_type[unit_paar_nr] == units:
                   self._mf_map.units = unit_paar_nr
    
    def get_metadata(self):
        return self._metadata
    
    def add_thubanlayer(self, tb_layer):
        """
        Add a thuban layer
        """
        # this import statement placed here, because if it is placed at the
        # beginning of this file, it produced the following error:
        # NameError: global name 'AnnotationLayer' is not defined
        # don't know why this error is produced and why it works
        # if it is placed here instead of the beginning.
        from Extensions.umn_mapserver.mf_import import AnnotationLayer
        from Thuban.Model.postgisdb import PostGISShapeStore
        if hasattr(tb_layer,"extension_umn_layerobj"):
            #print tb_layer.extension_umn_layerobj
            #new_layer = MF_Layer(layerObj(self._mf_map), self)
            new_layer = tb_layer.extension_umn_layerobj
        else:
            new_layer = MF_Layer(layerObj(self._mf_map), self)
            self._layers.append(new_layer)
            tb_layer.extension_umn_layerobj = new_layer
        new_layer.remove_allclasses()
        # init a list to set the layerorder
        new_layer.get_index()
        new_layer.set_name(tb_layer.Title()) 
        # TODO: implement relative pathnames
        # yet only absolute pathnames in the LayerObj are set 
        if isinstance(tb_layer, RasterLayer ):
            new_layer.set_data(tb_layer.GetImageFilename(), type="raster")
            new_layer.set_type("raster")
            new_layer.set_status(tb_layer.Visible())
        elif isinstance(tb_layer, AnnotationLayer):
            new_layer.set_type("annotation")
            new_layer.set_status(tb_layer.Visible())
            new_layer.set_data(tb_layer.ShapeStore().FileName())
        else:
            if isinstance (tb_layer.ShapeStore(), PostGISShapeStore):
                data = "%s from %s" % (tb_layer.ShapeStore().geometry_column,
                                       tb_layer.ShapeStore().tablename)
                new_layer.set_data (data, type="postgis")
                params = []
                for name in ("host", "port", "dbname", "user", "password"):
                    val = getattr(tb_layer.ShapeStore().db, name)
                    if val:
                        params.append("%s=%s" % (name, val))
                new_layer.set_connection (" ".join(params))
                new_layer.set_connectiontype (mapscript.MS_POSTGIS)
            else:
                new_layer.set_data(tb_layer.ShapeStore().FileName())
            new_layer.set_status(tb_layer.Visible())
            new_layer.set_type(tb_layer.ShapeType())
            if tb_layer.GetClassificationColumn():
                new_layer.set_classitem(tb_layer.GetClassificationColumn())
            if tb_layer.GetProjection():
                new_layer.set_projection(tb_layer.GetProjection())
            if tb_layer.GetClassification().GetNumGroups() > 0:
                singletonexists = False
                for group in range(0, \
		                tb_layer.GetClassification().GetNumGroups(), 1):
                    if isinstance(tb_layer.GetClassification().GetGroup(group), \
                                    ClassGroupSingleton):
                        singletonexists = True
                    new_layer.add_thubanclass( \
		                   tb_layer.GetClassification().GetGroup(group))
                new_layer.add_thubanclass( \
		                 tb_layer.GetClassification().GetDefaultGroup())
                # remove the classitem if one singleton exists
                if singletonexists == False:
                    new_layer.set_classitem(None)
            else:
                new_layer.add_thubanclass( \
		               tb_layer.GetClassification().GetDefaultGroup(), \
		               type="default")
        # set the projection to the layer.
        # if the layer has its own definition use it, 
	   # else use the main projection
        if tb_layer.GetProjection():
            new_layer.set_projection(tb_layer.GetProjection())
        else:
            new_layer.set_projection(self._projection.get_projection())
    
    def remove_layer(self, delnr):
        if delnr < len(self._layers):
            # if a layer is removed, the links for the mapscript layer and 
            # the metadata must set new
            # TODO: All other object in a layer obj must set a new, e.g proj.
            for ll in range(len(self._layers)-1, delnr, -1):
                self._layers[ll]._mf_layer = self._layers[ll-1]._mf_layer
                self._layers[ll].set_metadata(self._layers[ll-1].get_metadata())
               
            self._mf_map.removeLayer(delnr)
            self._layers.pop(delnr)
    
    def save_map(self, filepath):
        # save the Map
        # maybe an own saver can implement here
        self._mf_map.save(filepath)


class MF_Web:
    """
    Save the Web settings
    
    The following parametes are used:
    imagepath, imageurl, queryformat,
    
    The following parameters are not used:
    log, map, template, header, footer, empty, error, extent,
    minscale, maxscale, mintemplate, maxtemplate
    """
    def __init__(self, mf_web):
        self._mf_web = mf_web
    
    def get_imagepath(self):
        return self._mf_web.imagepath

    def set_imagepath(self, new_imagepath):
        self._mf_web.imagepath = new_imagepath
    
    def get_imageurl(self):
        return self._mf_web.imageurl
    
    def get_template(self):
        return self._mf_web.template
    
    def set_template(self, new_template):
        self._mf_web.template = new_template

    def set_imageurl(self, new_imageurl):
        self._mf_web.imageurl = new_imageurl
    
    def get_queryformat(self):
        return self._mf_web.queryformat

    def set_queryformat(self, new_queryformat):
        self._mf_web.imagepath = new_queryformat


class MF_Label:
    """
    The following parameters from mapscript are used:
    type, color, size, offsetx, offsety, partials, force, buffer,
    minfeaturesize, mindistance,
    
    The following parameters are not used:    
    font, outlinecolor, shadowcolor, shadowsizex, shadowsizey, 
    backgroundcolor, backgroundshadowcolor, backgroundshadowsizex,
    backgroundshadowsizey, sizescaled, minsize, maxsize, position, angle,
    autoangle, antialias, wrap, autominfeaturesize,
    """
    def __init__(self, mf_label):
        """
        Create a legend obj from the existing mapfile
        """
        self._label = mf_label
        self._color = MF_Color(self._label.color)
    
    def get_size(self):
        return self._label.size
    
    def set_size(self, new_size):
        if label_size_type.has_key(new_size):
            self._label.size = new_size
        for label_size_type_nr in label_size_type:
                if label_size_type[label_size_type_nr] == new_size:
                    self._label.size = label_size_type_nr
                else:
                    self._label.size = new_size
    
    def get_color(self):
        return self._color
    
    def get_partials(self):
        if self._label.partials == 1:
            return True
        else:
            return False
    
    def set_partials(self, new_partials):
        # if partials = True
        if new_partials:
            self._label.partials = 1
        elif new_partials == False:
            self._label.partials = 0
        else:
            print "must be boolean"
    
    def get_buffer(self):
        return self._label.buffer
    
    def set_buffer(self, new_buffer):
        self._label.buffer = new_buffer
    
    def get_mindistance(self):
        return self._label.mindistance
    
    def set_mindistance(self, new_mindistance):
        self._label.mindistance = new_mindistance
    
    def get_minfeaturesize(self):
        return self._label.minfeaturesize
    
    def set_minfeaturesize(self, new_minfeaturesize):
        self._label.minfeaturesize = new_minfeaturesize
    
    def get_position(self, mode="integer"):
        if mode == "string":
            return label_position_type[self._label.position]
        else:
            return self._label.position
    
    def set_position(self, new_position):
        if label_position_type.has_key(new_position):
            self._label.position = new_position
        else:
            for label_position_type_nr in label_position_type:
                if label_position_type[label_position_type_nr] == new_position:
                    self._label.position = label_position_type_nr
    
    def get_force(self):
        if self._label.force == 1:
            return True
        else:
            return False
    
    def set_force(self, new_force):
        if new_force:
            self._label.force = 1
        else:
            self._label.force = 0
    
    def get_type(self):
        return label_font_type[self._label.type]
    
    def set_type(self, new_type):
        if label_font_type.has_key(new_type):
            self._label.type = new_type
        else:
            for label_font_type_nr in label_font_type:
                if label_font_type[label_font_type_nr] == new_type:
                    self._label.type = label_font_type_nr

    def get_offset(self):
        return (self._label.offsetx, self._label.offsety)
    
    def set_offset(self, new_offsetx, new_offsety):
        self._label.offsetx = new_offsetx
        self._label.offsety = new_offsety


class MF_Legend:
    """
    The following parameters are (not) used:
    imagecolor, label, keysizex, keysizey, status, position,
    
    The following parameters are not used:
    keyspacingx, keyspacingy,
    outlinecolor, height, width, postlabelcache, template, map
    """
    def __init__(self, mf_legend):
        """
        Create a legend obj from the existing mapfile
        """
        self._mf_legend = mf_legend
        self._imagecolor = MF_Color(self._mf_legend.imagecolor)
        self._label = MF_Label(self._mf_legend.label)
        
    def get_imagecolor(self):
        return self._imagecolor
    
    def get_label(self):
        return self._label
    
    def get_keysize(self):
        return (self._mf_legend.keysizex, self._mf_legend.keysizey)
    
    def set_keysize(self, new_keysizex, new_keysizey):
        self._mf_legend.keysizex = new_keysizex
        self._mf_legend.keysizey = new_keysizey
    
    def get_keyspacing(self):
        return (self._mf_legend.keyspacingx, self._mf_legend.keyspacingy)
    
    def set_keyspacing(self, new_keyspacingx, new_keyspacingy):
        self._mf_legend.keyspacingx = new_keyspacingx
        self._mf_legend.keyspacingy = new_keyspacingy
    
    def get_status(self, mode="integer"):
        if mode == "string":
            return legend_status_type[self._mf_legend.status]
        else:
            return self._mf_legend.status
    
    def set_status(self, new_status):
        if legend_status_type.has_key(new_status):
            self._mf_legend.status = new_status
        else:
            for legend_status_type_nr in legend_status_type:
                if legend_status_type[legend_status_type_nr] == new_status:
                    self._mf_legend.status = legend_status_type_nr
    
    def get_position(self, mode="integer"):
        if mode == "string":
            return legend_position_type[self._mf_legend.position]
        else:
            return self._mf_legend.position
    
    def set_position(self, new_position):
        if legend_position_type.has_key(new_position):
            self._mf_legend.position = new_position
        else:
            for legend_position_type_nr in legend_position_type:
                if legend_position_type[legend_position_type_nr]== new_position:
                    self._mf_legend.position = legend_position_type_nr

class MF_Projection:
    """
    The following parameter, which the mapscript style obj contains is used:

    numargs
    """
    
    def __init__(self, mf_projection):
        """
        Create a projection object from the given mapscript projection
        object. If it is a epsg code the extracted from the string
        (e.g."init=epsg:xxxx"), else the projection parameters will
        be splitted and an array with the parameters will be creaded.
        """
        self._mfprojstring = mf_projection
        self._projstring = self._mfprojstring
        self._epsgcode = None
        self._params = None
        if self._mfprojstring:
            if self._mfprojstring.find("init=epsg:") != -1:
                self._initcode, self._epsgcode = self._mfprojstring.split(':')
            else:  
                self._params = []  
                self._params = [p.strip() for p in self._mfprojstring.split("+")]
                if self._params[0] == "":
                    self._params.remove("")

    def epsg_code_to_projection(self, epsg):
        """
        Find the projection for the given epsg code.
    
        Copied from Extension/wms/layer.py
    
        epsg -- EPSG code as string
        """
        #Needed only for this function
        from Thuban.Model.resource import get_system_proj_file, EPSG_PROJ_FILE,\
                                          EPSG_DEPRECATED_PROJ_FILE

        proj_file, warnings = get_system_proj_file(EPSG_PROJ_FILE)

        for proj in proj_file.GetProjections():
            if proj.EPSGCode() == epsg:
                return proj
            
        proj_file, warnings = get_system_proj_file(EPSG_DEPRECATED_PROJ_FILE)
        for proj in proj_file.GetProjections():
            if proj.EPSGCode() == epsg:
                return proj
        return None
    
    def get_params(self):
        #Parameter from the mf_projectionstring as Array
        return self._params
        
    def get_epsgcode(self):
        # returnes the epsg number
        return self._epsgcode
        
    def get_epsgproj(self):
        # get an epsg projectionobject
        return self.epsg_code_to_projection(self._epsgcode)
    
    def get_projection(self):
        return self._projstring
    
    def set_projection(self, newprojection):
        self._projstring = newprojection
        self._params = newprojection.GetAllParameters()
        self._mfnewprojstring = ""
        for field in self._params:
            self._mfnewprojstring = self._mfnewprojstring+ "+" + field
        self._mfprojstring = self._mfnewprojstring


class MF_Style:
    """
    The following parameters, which the mapscript style obj
    contains, are used:
    color, backgroundcolor, outlinecolor, size, symbolname, width

    The following are not used:
    symbol, sizescaled, minsize, maxsize, offsetx, offsety,
    antialias
    """
 
    def __init__(self, mf_style, map):
        """
	Create a style object from the given mapscript style object.
	The color Object from the color and the outlinecolor parameter
	will be created. if the color (red, green or blue) is -1 there
	is no definition in the mapfile and so there is no color object,
	it will set to 'None'.
        """
        self._style = mf_style
        self.map = map
        if self._style.color.red == -1:
            self._color = None
        else:
            self._color = MF_Color(self._style.color)
        if self._style.outlinecolor.red == -1:
            self._outlinecolor = None
        else:
            self._outlinecolor = MF_Color(self._style.outlinecolor)
    
    def get_color(self):
        return self._color
    
    def get_width(self):
        return self._style.width
    
    def get_outlinecolor(self):
        return self._outlinecolor
   
    def get_size(self):
        return self._style.size
    
    def set_linecolor(self, tb_color):
        self._color = tb_color
        new_linecolor = MF_Color(colorObj())
        new_linecolor.set_thubancolor(tb_color)
        self._outlinecolor = new_linecolor
        self._style.outlinecolor = new_linecolor.get_mfcolor()
    
    def set_color(self, tb_color):
        self._color = tb_color
        new_color = MF_Color(colorObj())
        new_color.set_thubancolor(tb_color)
        self._color = new_color
        self._style.color = new_color.get_mfcolor()

    def set_size(self, newsize):
        self._style.size = newsize

    def set_width(self, newwidth):
        self._style.width = newwidth
    
    def set_symbolname(self, newsymbol):
        # its possible to use stringnames instead of numbers
        self._style.setSymbolByName (self.map._mf_map, newsymbol)

