// © 2004-2008, Applied Geographics, Inc.  All rights reserved.

function GraphicsType() {}

GraphicsType.None = 0;
GraphicsType.VML = 1;
GraphicsType.Canvas = 2;

function ClickMode() {}

ClickMode.Click = 0;
ClickMode.ZoomIn = 1;
ClickMode.Pan = 2;
ClickMode.DrawBox = 3;
ClickMode.DrawLine = 4;
ClickMode.DrawPolygon = 5;
ClickMode.DrawCircle = 6;
ClickMode.MeasureLength = 7;
ClickMode.MeasureArea = 8;
ClickMode.DrawLength = 9;
ClickMode.DrawArea = 10;

function MeasureUnits() {}

MeasureUnits.Feet = 0;
MeasureUnits.Meters = 1;
MeasureUnits.Both = 2;

function WheelMode() {}

WheelMode.Off = 0;
WheelMode.PanZoomOnly = 1;
WheelMode.On = 2;

function Map(id, fullExtent, extent, units, measureUnits, zoomFactor, wheelMode, postBack) {
  this.id = id;
  this.element = document.getElementById(id);
  
  this.element.strokeWidth = 2;
  this.element.strokeColor = "gray";
  this.element.fillColor = "white";
  this.element.fillOpacity = 0.5;

  this.element.onmapclick = null;
  this.element.onmapdblclick = null;
  this.element.onmapdrawbox = null;
  this.element.onmapdrawcircle = null;
  this.element.onmapdrawline = null;
  this.element.onmapdrawpolygon = null;
  this.element.onmapdrawlength = null;
  this.element.onmapdrawarea = null;
  this.element.onmapextentchange = null;
  this.element.onmapimageloaded = null;
  this.element.onmapmousedown = null;
  this.element.onmapmousemove = null;
  this.element.onmapmouseup = null;
  this.element.onmapmouseout = null;
  this.element.onmaprefresh = null;
  
  this.canvas = document.getElementById(id + "_canvas");
  this.image = document.getElementById(id + "_image");
  this.content = document.getElementById(id + "_content");
  this.tip = document.getElementById(id + "_tip");
  
  this.fullExtent = fullExtent;
  this.extent = extent;
  this.zoomFactor = zoomFactor;
  this.initialImageWidth = 0;
  this.initialImageHeight = 0;
  
  this.anchor = null;
  this.lastPoint = null;
  this.isMouseDown = false;
  this.isRefreshing = false;
  
  this.graphicsType = GraphicsType.None;
  this.units = units;
  this.measureUnits = measureUnits;
  this.graphic = null;
  this.text = null;
  this.imageShape = null;
  this.mapShape = null;
  
  this.wheelTimerHandle = null;
  this.wheelLevel = 0;
  this.wheelZoomFactor = 1.18920711500273;
  this.wheelMode = wheelMode;
  
  this.panPercentX = 0;
  this.panPercentY = 0;
  this.animateHandle = null;
  this.animatePositions = null;

  this.eventHandlers = [];
  this.postBack = postBack;
  
  WebEventHandler.implement(this);
  
  this.clear = function() {
    this.image.style.visibility = "hidden";
  };

  this.clearGraphics = function() {
    this.resetGraphics();
    
    while (this.content.childNodes.length > 0) {
      this.content.removeChild(this.content.lastChild);
    }
  };

  this.createGraphics = function() {
    var clickMode = this.element.getClickMode();
    
    if (this.graphicsType != GraphicsType.None) {
      switch (this.graphicsType) {
        case GraphicsType.VML:
          if (clickMode == ClickMode.DrawCircle) {
            this.graphic = document.createElement("v:oval");
            this.graphic.style.position = "absolute";
          }
          else {
            this.graphic = document.createElement("v:shape");
            this.graphic.style.position = "absolute";
            this.graphic.style.left = "0px";
            this.graphic.style.top = "0px";
            this.graphic.style.width = "1000";
            this.graphic.style.height = "1000";
            this.graphic.coordorigin = "0 0";
            this.graphic.coordsize = "1000 1000";
          }

          this.graphic.strokecolor = this.element.strokeColor;
          this.graphic.strokeweight = this.element.strokeWidth + "px";

          var fill = document.createElement("v:fill");
          fill.color = this.element.fillColor;
          fill.opacity = clickMode == ClickMode.DrawLine || clickMode == ClickMode.MeasureLength || clickMode == ClickMode.DrawLength ? 0 : this.element.fillOpacity;
          this.graphic.appendChild(fill);
          break; 
          
        case GraphicsType.Canvas:
          this.graphic = document.createElement("canvas");
          this.graphic.width = this.getWidth();
          this.graphic.height = this.getHeight();
          
          var context = this.graphic.getContext("2d");
          context.strokeStyle = this.element.strokeColor;
          context.lineWidth = parseInt(this.element.strokeWidth);
          context.fillStyle = this.element.fillColor;
          break; 
      }
          
      this.text = document.createElement("div");
      this.text.style.position = "absolute";
      this.text.style.textAlign = "center";
      this.text.style.fontFamily = "Verdana";
      this.text.style.fontSize = "11px";
      this.text.style.color = "#404040";
      this.text.style.width = "100px";
      this.text.style.height = "43px";
      this.text.style.visibility = "hidden";
      
      this.content.appendChild(this.graphic);
      this.content.appendChild(this.text);
    }
  };
  
  this.doubleClick = function(e) {
    if (this.isRefreshing) {
      return;
    }
    
    var p = this.toMapPoint(this.toPoint(e));
    var clickMode = this.getClickMode();
    
    this.dispatchEvent("mapdblclick", e, p);
    
    if (clickMode == ClickMode.DrawLine || clickMode == ClickMode.DrawPolygon || clickMode == ClickMode.DrawLength || clickMode == ClickMode.DrawArea) {
      this.mapShape.points.length -= 1;
      
      if (this.mapShape.isValid()) {
        
        switch (clickMode) {
          case ClickMode.DrawLine:
            this.dispatchEvent("mapdrawline", e, this.mapShape);
            break;
          
          case ClickMode.DrawLength:
            this.dispatchEvent("mapdrawlength", e, this.mapShape);
            break;
          
          case ClickMode.DrawPolygon:
          case ClickMode.DrawArea:
            var i = this.mapShape.points.length;
            this.mapShape.points[i] = new Point(this.mapShape.points[0].x, this.mapShape.points[0].y);
            this.dispatchEvent(clickMode == ClickMode.DrawPolygon ? "mapdrawpolygon" : "mapdrawarea", e, this.mapShape);
            break;
        }
      
        if (this.postBack && !this.getClientClick()) {
          this.returnData("Shape", this.mapShape.toString());
          this.postBack();
        }
        
        this.resetGraphics();
      }
    }

    if (clickMode == ClickMode.MeasureLength || clickMode == ClickMode.MeasureArea) {
      this.clearGraphics();
    }
  };

  this.getClickMode = function() {
    return parseInt(document.getElementById(this.id + ".ClickMode").value);
  };

  this.getClientClick = function() {
    return eval(document.getElementById(this.id + ".ClientClick").value);
  };

  this.getExtent = function() {
    return this.extent.clone();
  };

  this.getFullExtent = function() {
    return this.fullExtent.clone();
  };
  
  this.getHeight = function() {
    return this.initialImageHeight > 0 ? this.initialImageHeight : document.all ? this.image.height : this.image.clientHeight;
  };

  this.getPixelSize = function() {
    return this.getVisibleExtent().getWidth() / this.getWidth();
  };

  this.getVisibleExtent = function() {
    var box = this.extent.clone();
    box.reaspect(this.getWidth() / this.getHeight());
    return box;
  };

  this.getWidth = function() {
    return this.initialImageWidth > 0 ? this.initialImageWidth : document.all ? this.image.width : this.image.clientWidth;
  };

  this.getZoomLevel = function() {
    var e = this.extent.clone();
    e.reaspect(this.getWidth() / this.getHeight());
    var f = this.fullExtent.clone();
    f.reaspect(this.getWidth() / this.getHeight());
    
    return (Math.log(f.getWidth() / e.getWidth()) / Math.log(this.zoomFactor)) + 1;
  };

  this.hideTip = function() {
    this.tip.style.visibility = "hidden";
  };

  this.imageLoaded = function() {
    this.image.style.left = "0px";
    this.image.style.top = "0px";
    
    if (this.initialImageWidth > 0) {
      this.image.style.width = this.initialImageWidth + "px";
      this.image.style.height = this.initialImageHeight + "px";
      this.initialImageWidth = 0;
      this.initialImageHeight = 0;
    }
    
    this.image.style.visibility = "visible";
    this.clearGraphics();
    
    this.dispatchEvent("mapimageloaded");
    
    this.isRefreshing = false;
  };
  
  this.initializeGraphics = function() {
    if (document.all) {
      this.graphicsType = GraphicsType.VML;
      var id = "__vmlStyle";
      
      if (!document.getElementById(id)) {
        document.namespaces.add("v", "urn:schemas-microsoft-com:vml");
        
        var style = document.createElement("style");
        style.id = id;
        document.getElementsByTagName("head")[0].appendChild(style);
        
        style = document.styleSheets[document.styleSheets.length - 1];
        style.addRule("v\\:*", "BEHAVIOR: url(#default#VML)");
      }
    }
    else {
      if (document.createElement("canvas").getContext) {
        this.graphicsType = GraphicsType.Canvas;
      }
    }
  };
  
  this.labelShape = function() {
    var text = null;
    var c = null;
    
    var metersPerFoot = 0.3048;
    var convert = 1 / (this.units == Units.Feet ? 1 : metersPerFoot);
    
    var inFeet = this.measureUnits == MeasureUnits.Feet || this.measureUnits == MeasureUnits.Both;
    var inMeters = this.measureUnits == MeasureUnits.Meters || this.measureUnits == MeasureUnits.Both;
    
    var clickMode = this.getClickMode();
    var text = [];
    
    switch (clickMode) {
      case ClickMode.MeasureLength:
      case ClickMode.DrawLength:
        var d = this.mapShape.getLength() * convert;

        if (inFeet) {
          if (d < 5280) {
            text.push(Math.round(d) + " ft");
          }
          else {
            text.push((d / 5280).toFixed(1) + " mi");
          }
        }
        
        if (inMeters) {
          d *= metersPerFoot;
          
          if (d < 1000) {
            text.push(Math.round(d) + " m");
          }
          else {
            text.push((d / 1000).toFixed(1) + " km");
          }
        }
        break;
      
      case ClickMode.MeasureArea:
      case ClickMode.DrawArea:
        c = this.imageShape.getCentroid();
        
        if (c != null) {
          var a = this.mapShape.getArea() * convert * convert;
          var acres = a / 43560;
          
          if (inFeet) {
            if (a <= 2787840) {
              text.push(Math.round(a) + " sq ft");
            }
            else {
              text.push((a / 27878400).toFixed(2) + " sq mi");
            }
          }
          
          if (inMeters) {
            a *= metersPerFoot * metersPerFoot;
              
            if (a <= 100000) {
              text.push(Math.round(a) + " sq m");
            }
            else {
              text.push((a / 1000000).toFixed(2) + " sq km");
            }
          }
          
          if (inFeet) {
            text.push(acres.toFixed(2) + " acres");
          }
        }
        break;
    }
    
    if (this.graphicsType != GraphicsType.None) {
      if (text.length == 0) {
        this.text.style.visibility = "hidden";
      }
      else {
        while (this.text.childNodes.length > 0) {
          this.text.removeChild(this.text.lastChild);
        }
        
        for (var i = 0; i < text.length; ++i) {
          this.text.appendChild(document.createTextNode(text[i]));
          
          if (i < text.length - 1) {
            this.text.appendChild(document.createElement("br"));
          }
        }
        
        if (clickMode == ClickMode.MeasureLength || clickMode == ClickMode.DrawLength) {
          if (this.imageShape.points.length == 2) {
            var height = text.length * 14;
            this.text.style.left = (this.imageShape.points[0].x - 50) + "px";
            this.text.style.top = (this.imageShape.points[0].y + (this.imageShape.points[1].y < this.imageShape.points[0].y ? 3 : -3 - height)) + "px";
            this.text.style.visibility = "visible";
          }
        }
        else {
          this.text.style.left = c.x - 50;
          this.text.style.top = c.y - (text.length * 7);
          this.text.style.visibility = "visible";
        }
      }
    }        
  };

  this.loadImage = function(image) {
    this.image.style.visibility = "hidden";
    this.image.src = image.src ? image.src : image;
  };

  this.mouseDown = function(e) {
    if (this.isRefreshing) {
      return;
    }

    this.hideTip();
    
    var ip = this.toPoint(e);
    var mp = this.toMapPoint(ip);
    
    this.dispatchEvent("mapmousedown", e, mp);

    if (this.isMouseDown) {
      return;
    }
    
    var clickMode = this.getClickMode();
    
    switch (clickMode) {
      case ClickMode.Click:
        this.dispatchEvent("mapclick", e, mp);
        
        if (this.postBack && !this.getClientClick()) {
          this.returnData("Shape", mp.toString());
          this.postBack();
        }
        break;
        
      case ClickMode.Pan:
        this.anchor = ip;
        this.isMouseDown = true;
        break;
        
      case ClickMode.ZoomIn:
      case ClickMode.DrawBox:
        if (this.graphicsType != GraphicsType.None) {
          this.anchor = ip;
          this.isMouseDown = true;
          
          if (this.graphic == null) {
            this.createGraphics();
            this.imageShape = new Polygon();
          }
          
          for (var i = 0; i <= 4; ++i) {
            this.imageShape.points[i] = ip;
          }
          
          this.redrawShape();
        }
        break;
              
      case ClickMode.DrawLine:
      case ClickMode.DrawPolygon:
      case ClickMode.MeasureLength:
      case ClickMode.MeasureArea:
      case ClickMode.DrawLength:
      case ClickMode.DrawArea:
        if (this.graphicsType != GraphicsType.None) {
          if (this.graphic == null) {
            this.createGraphics();
            
            if (clickMode == ClickMode.DrawLine || clickMode == ClickMode.MeasureLength || clickMode == ClickMode.DrawLength) {
              this.imageShape = new Line();
              this.mapShape = new Line();
            }
            else {
              this.imageShape = new Polygon();
              this.mapShape = new Polygon();
            }
          }
          
          var i = this.imageShape.points.length == 0 ? 0 : this.imageShape.points.length - 1;
          this.imageShape.points[i] = ip;
          this.mapShape.points[i] = mp;
          ++i;
          this.imageShape.points[i] = ip;
          this.mapShape.points[i] = mp;
          
          this.redrawShape();

          if (clickMode == ClickMode.MeasureArea || clickMode == ClickMode.DrawArea) {
            this.labelShape();
          }
        }
        break;

      case ClickMode.DrawCircle:
        if (this.graphicsType != GraphicsType.None) {
          this.createGraphics();
          this.imageShape = new Polygon();
          this.mapShape = new Polygon();
          this.anchor = ip;
          this.isMouseDown = true;
        }
        break;
    }
  };

  this.mouseMove = function(e) {            
    if (this.isRefreshing) {
      return;
    }
    
    var ip = this.toPoint(e);
    this.lastPoint = ip;
    
    var mouseNowUp = (e.which ? e.which : e.button) == 0;
    var mp = this.toMapPoint(ip);
    
    this.dispatchEvent("mapmousemove", e, mp);
    
    var clickMode = this.getClickMode();
    
    switch (clickMode) {
      case ClickMode.Pan:
        if (this.isMouseDown) {
          if (mouseNowUp) {
            this.isMouseDown = false;
            this.image.style.left = "0px";
            this.image.style.top = "0px";
          }
          else {
            this.image.style.left = ip.x - this.anchor.x + "px";
            this.image.style.top = ip.y - this.anchor.y + "px";
          }
        }
        break;
        
      case ClickMode.ZoomIn:
      case ClickMode.DrawBox:
        if (this.isMouseDown && this.graphic != null) {
          if (mouseNowUp) {
            this.isMouseDown = false;
            this.clearGraphics();
          }
          else {
            this.imageShape.points[1] = new Point(this.anchor.x, ip.y);
            this.imageShape.points[2] = ip;
            this.imageShape.points[3] = new Point(ip.x, this.anchor.y);
            
            this.redrawShape();
          }
        }
        break;
        
      case ClickMode.DrawLine:
      case ClickMode.DrawPolygon:
      case ClickMode.MeasureLength:
      case ClickMode.MeasureArea:
      case ClickMode.DrawLength:
      case ClickMode.DrawArea:
        if (this.graphic != null) {
          var i = this.imageShape.points.length - 1;
          this.imageShape.points[i] = ip;
          this.mapShape.points[i] = mp;
          
          this.redrawShape();

          if (clickMode == ClickMode.MeasureLength || clickMode == ClickMode.MeasureArea || clickMode == ClickMode.DrawLength || clickMode == ClickMode.DrawArea) {
            this.labelShape();
          }
        }
        break;

      case ClickMode.DrawCircle:
        if (this.graphic != null && this.isMouseDown) {
          if (mouseNowUp) {
            this.isMouseDown = false;
            this.clearGraphics();
          }
          else {
					  var d = ip.distanceTo(this.anchor);

            switch (this.graphicsType) {
              case GraphicsType.VML:
					      d = parseInt(d);
					      this.graphic.style.left = this.anchor.x - d;
					      this.graphic.style.top = this.anchor.y - d;
					      this.graphic.style.width = d * 2;
					      this.graphic.style.height = d * 2;
					      break;
  					    
					    case GraphicsType.Canvas:
                var context = this.graphic.getContext("2d");
                context.clearRect(0, 0, this.graphic.width, this.graphic.height);

                context.beginPath();
                context.arc(this.anchor.x, this.anchor.y, d, 0, 360, false);
                context.globalAlpha = this.element.fillOpacity;
                context.fill();
                context.globalAlpha = 1;
                context.stroke();
					      break;
					  }
          }
        }
        break;
    }
  };

  this.mouseUp = function(e) {
    if (this.isRefreshing) {
      return;
    }
    
    var p = this.toPoint(e);
    var mp = this.toMapPoint(p);
    
    this.dispatchEvent("mapmouseup", e, mp);
    
    if (!this.isMouseDown) {
      return;
    }
    
    this.isMouseDown = false;
    var clickMode = this.getClickMode();
    
    switch (clickMode) {
      case ClickMode.Pan:
        x = parseInt(this.getWidth() / 2) + (this.anchor.x - p.x);
        y = parseInt(this.getHeight() / 2) + (this.anchor.y - p.y);
        p = this.toMapPoint(new Point(x, y));
        
        dx = this.extent.getWidth() / 2;
        dy = this.extent.getHeight() / 2;
        
        this.extent.minx = p.x - dx;
        this.extent.miny = p.y - dy;
        this.extent.maxx = p.x + dx;
        this.extent.maxy = p.y + dy;
            
        this.refresh(true);
        break;
        
      case ClickMode.ZoomIn:
      case ClickMode.DrawBox:
        var p1 = this.toMapPoint(this.anchor);
        var p2 = this.toMapPoint(p);
        var box = new Box(p1.x, p1.y, p2.x, p2.y);

        if (clickMode == ClickMode.ZoomIn) {
          this.clearGraphics();

          var minSize = this.getPixelSize() * 6;
          
          if (box.getWidth() < minSize && box.getHeight() < minSize) {
            this.extent.setCenter(box.getCenter());
            this.extent.scaleBy(0.5);
          }
          else {
            this.extent = box;
          }
          
          this.refresh(true);
        }
        else {
          this.dispatchEvent("mapdrawbox", e, box);
          
          if (this.postBack && !this.getClientClick()) {
            this.returnData("Shape", box.toString());
            this.postBack();
          }
          
          this.resetGraphics();
        }
        break;

      case ClickMode.DrawCircle:
        if (this.graphic != null) {
          var d = p.distanceTo(this.anchor);
          var n = 72;

          for (var i = 0; i <= n; ++i) {
            var a = (i * 360 / n) * (Math.PI / 180);
            var x = this.anchor.x + Math.cos(a) * d;
            var y = this.anchor.y + Math.sin(a) * d;
            this.mapShape.points[i] = this.toMapPoint(new Point(x, y));
          }

          this.dispatchEvent("mapdrawcircle", e, this.mapShape);
          
          if (this.postBack && !this.getClientClick()) {
            this.returnData("Shape", this.mapShape.toString());
            this.postBack();
          }

          this.resetGraphics();
        }
        break;
    }
  };

  this.mouseWheel = function(e) {
    var clickMode = this.getClickMode();
    
    if (this.isRefreshing || this.wheelMode == WheelMode.Off || (this.wheelMode == WheelMode.PanZoomOnly && 
        clickMode != ClickMode.Pan && clickMode != ClickMode.ZoomIn)) {
      return;
    }
    
	  if (!e) {
	    e = window.event;
	  }
  	
	  var delta = 0;
  	
	  if (e.wheelDelta) {
		  delta = e.wheelDelta / 120; 
  		
		  if (window.opera) {
		    delta = -delta;
		  }
	  }
	  else {
	    if (e.detail) {
		    delta = -e.detail / 3;
	    }
	  }
  	
	  if (delta) {
		  if (this.wheelTimerHandle == null) {
			  this.anchor = this.toPoint(e);
			  this.initialImageWidth = this.getWidth();
			  this.initialImageHeight = this.getHeight();
		  }
		  else {
			  window.clearTimeout(this.wheelTimerHandle);
			  this.wheelTimerHandle = null;
		  }
  		
		  this.wheelLevel += delta;
		  var scale = Math.pow(this.wheelZoomFactor, this.wheelLevel);

		  this.image.style.left = parseInt(this.anchor.x - (this.anchor.x * scale)) + "px";
		  this.image.style.top = parseInt(this.anchor.y - (this.anchor.y * scale)) + "px";
		  this.image.style.width = parseInt(this.initialImageWidth * scale) + "px";
		  this.image.style.height = parseInt(this.initialImageHeight * scale) + "px";
  		
		  var target = this;
		  this.wheelTimerHandle = window.setTimeout(function() {
		    target.mouseWheelFinish();
		  }, 1000);
	  }
  };

  this.mouseWheelFinish = function() {
    this.wheelTimerHandle = null;
    
    if (this.wheelLevel != 0) {
		  var p = this.toMapPoint(this.anchor);
		  var scale = Math.pow(this.wheelZoomFactor, -this.wheelLevel);
  		
		  this.extent.minx = p.x + ((this.extent.minx - p.x) * scale);
		  this.extent.miny = p.y + ((this.extent.miny - p.y) * scale);
		  this.extent.maxx = p.x + ((this.extent.maxx - p.x) * scale);
		  this.extent.maxy = p.y + ((this.extent.maxy - p.y) * scale);
  		
		  this.wheelLevel = 0;

      this.refresh(true);
    }
  }

  this.mouseOut = function(e) {
    if (this.isRefreshing) {
      return;
    }

    this.dispatchEvent("mapmouseout");
  };

  this.pan = function(percentx, percenty) {
    this.panPercentX = percentx;
    this.panPercentY = percenty;
    
    var dx = this.getWidth() * -percentx;
    var dy = this.getHeight() * percenty;
    
    var total = 500;
    var interval = 25;
    
    this.animatePositions = [];
    
    for (var i = interval; i <= total; i += interval) {
      var d = (Math.cos(i * Math.PI / total) + 1) / 2;
      this.animatePositions.push([parseInt(dx * d), parseInt(dy * d)]);
    }
    
    var target = this;
    this.animateHandle = setInterval(function () { target.panAnimate() }, interval);
  };

  this.panAnimate = function() {
    var p = this.animatePositions.pop();
    this.image.style.left = p[0] + "px";
    this.image.style.top = p[1] + "px";
    
    if (this.animatePositions.length == 0) {
      clearInterval(this.animateHandle);
      this.panFinish();
    }
  };

  this.panFinish = function() {
    var ext = this.getVisibleExtent();
    
    var dx = ext.getWidth() * this.panPercentX;
    var dy = ext.getHeight() * this.panPercentY;
    
    this.extent.minx += dx;
    this.extent.miny += dy;
    this.extent.maxx += dx;
    this.extent.maxy += dy;
    
    this.refresh(true);
  };
  
  this.redrawShape = function() {
    var clickMode = this.getClickMode();
    
    switch (this.graphicsType) {
      case GraphicsType.VML:
        var path = "m " + parseInt(this.imageShape.points[0].x) + "," + parseInt(this.imageShape.points[0].y) + " l ";

        for (i = 1; i < this.imageShape.points.length; ++i) {
          path += parseInt(this.imageShape.points[i].x) + "," + parseInt(this.imageShape.points[i].y);
          if (i < this.imageShape.points.length - 1) {
            path += ",";
          }
        }
        
        if (!(clickMode == ClickMode.DrawLine || clickMode == ClickMode.MeasureLength || clickMode == ClickMode.DrawLength)) {
          path += " x";
        }
        
        this.graphic.path = path + " e";
        break;
        
      case GraphicsType.Canvas:
        var context = this.graphic.getContext("2d");
        context.clearRect(0, 0, this.graphic.width, this.graphic.height);

        context.beginPath();
        context.moveTo(this.imageShape.points[0].x, this.imageShape.points[0].y);
        
        for (var i = 1; i < this.imageShape.points.length; ++i) {
          context.lineTo(this.imageShape.points[i].x, this.imageShape.points[i].y);
        }

        if (!(clickMode == ClickMode.DrawLine || clickMode == ClickMode.MeasureLength || clickMode == ClickMode.DrawLength)) {
          context.lineTo(this.imageShape.points[0].x, this.imageShape.points[0].y);
          context.globalAlpha = this.element.fillOpacity;
          context.fill();
          context.globalAlpha = 1;
        }
        
        context.stroke();
        break;
    }
  };
  
  this.refresh = function(extentChanged) {
    this.isRefreshing = true;
    
    if (extentChanged) {
      this.dispatchEvent("mapextentchange");
    }
    
    this.dispatchEvent("maprefresh");
    
    if (this.postBack) {
      if (extentChanged) {
        this.returnData("Extent", this.extent.toString());
      }
      
      this.postBack();
    }
  };

  this.resetGraphics = function() {
    this.text = null;
    this.graphic = null;
    this.imageShape = null;
    this.mapShape = null;
  };

  this.returnData = function(dataType, data) {
    document.getElementById(this.id + "." + dataType).value = data;
  }

  this.setClickMode = function(clickMode) {
    document.getElementById(this.id + ".ClickMode").value = clickMode;
  };

  this.setClientClick = function(clientClick) {
    document.getElementById(this.id + ".ClientClick").value = clientClick + "";
  };

  this.setExtent = function(box) {
    this.extent = box;
    this.refresh(true);
  };
  
  this.setZoomLevel = function(level) {
    level = level < 1 ? 1 : level;
    var c = this.extent.getCenter();
    
    var ext = this.fullExtent.clone();
    ext.scaleBy(1 / Math.pow(this.zoomFactor, level - 1));
    ext.setCenter(c);
    
    this.extent = ext;
    this.refresh(true);
  };

  this.showTip = function(text) {
    if (this.lastPoint == null || this.isMouseDown) {
      return;
    }
    
    this.tip.style.left = (this.getWidth() + 10) + "px";
    this.tip.style.top = (this.getHeight() + 10) + "px";

    while (this.tip.childNodes.length > 0) {
      this.tip.removeChild(this.tip.lastChild);
    }
    
    var line = text.split("\n");
    this.tip.appendChild(document.createTextNode(line[0]));
    var maxLen = line[0].length;
    
    for (var i = 1; i < line.length; ++i) {
      this.tip.appendChild(document.createElement("BR"));
      this.tip.appendChild(document.createTextNode(line[i]));
      maxLen = Math.max(maxLen, line[i].length);
    }
    
    var width = maxLen * 6.25 + 10;
    var height = line.length * 12.5 + 4;
    
    var x = this.lastPoint.x + 5;
    var y = this.lastPoint.y - height - 5;
    
    if (x + width > this.getWidth()) {
      x = this.lastPoint.x - width - 5;
    } 
    
    if (y < 0) {
      y = this.lastPoint.y + 5;
    }
    
    this.tip.style.left = x + "px";
    this.tip.style.top = y + "px";
    this.tip.style.visibility = "visible";
  }

  this.toMapPoint = function(p) {
    var box = this.getVisibleExtent();
    var x = box.minx + (p.x * box.getWidth() / this.getWidth());
    var yOffset = (p.y * box.getHeight() / this.getHeight());
    var y = this.units == Units.Pixels ? box.miny + yOffset : box.maxy - yOffset;
    return new Point(x, y);
  };

  this.toPixelPoint = function(p) {
    var box = this.getVisibleExtent();
    var x = (p.x - box.minx) * this.getWidth() / box.getWidth()
    var yOffset = this.units == Units.Pixels ? p.y - box.miny : box.maxy - p.y;
    var y = yOffset * this.getHeight() / box.getHeight();
    return new Point(x, y);
  };

  this.toPoint = function(e) {
    var x = e.layerX ? e.layerX - 2 : e.offsetX;
    var y = e.layerY ? e.layerY - 2 : e.offsetY;
    
    return new Point(x, y);
  }

  this.zoom = function(scale) {
    this.extent.scaleBy(scale);
    this.refresh(true);
  };

  this.zoomFull = function() {
    this.extent = this.fullExtent.clone();
    this.refresh(true);
  };

  this.initializeGraphics();
  
  var controller = this;
  this.attachEventToElement(this.image, "load", function (e) { controller.imageLoaded(e); });
  this.attachEventToElement(this.canvas, "mousedown", function (e) { controller.mouseDown(e); });
  this.attachEventToElement(this.canvas, "mousemove", function (e) { controller.mouseMove(e); });
  this.attachEventToElement(this.canvas, "mouseup", function (e) { controller.mouseUp(e); });
  this.attachEventToElement(this.canvas, "mouseout", function (e) { controller.mouseOut(e); });
  this.attachEventToElement(this.canvas, "dblclick", function (e) { controller.doubleClick(e); });
  this.attachEventToElement(this.canvas, "mousewheel", function (e) { controller.mouseWheel(e); });
  
  this.element.controller = controller;
  
  this.element.attachEvent = function(eventName, handler) { this.controller.attachEvent(eventName, handler); };
  this.element.clear = function() { this.controller.clear(); };
  this.element.clearGraphics = function() { this.controller.clearGraphics(); };
  this.element.getClickMode = function() { return this.controller.getClickMode(); };
  this.element.getClientClick = function() { return this.controller.getClientClick(); };
  this.element.getExtent = function() { return this.controller.getExtent(); };
  this.element.getHeight = function() { return this.controller.getHeight() };
  this.element.getFullExtent = function() { return this.controller.getFullExtent(); };
  this.element.getPixelSize = function() { return this.controller.getPixelSize(); };
  this.element.getVisibleExtent = function() { return this.controller.getVisibleExtent() };
  this.element.getWidth = function() { return this.controller.getWidth() };
  this.element.getZoomLevel = function() { return this.controller.getZoomLevel() };
  this.element.hideTip = function() { this.controller.hideTip(); };
  this.element.loadImage = function(image) { this.controller.loadImage(image); };
  this.element.pan = function(percentx, percenty) { this.controller.pan(percentx, percenty); };
  this.element.refresh = function(returnExtent) { this.controller.refresh(returnExtent); };
  this.element.setClickMode = function(clickMode) { this.controller.setClickMode(clickMode); };
  this.element.setClientClick = function(clientClick) { this.controller.setClientClick(clientClick); };
  this.element.setExtent = function(box) { return this.controller.setExtent(box); };
  this.element.setZoomLevel = function(level) { this.controller.setZoomLevel(level); };
  this.element.showTip = function(text) { this.controller.showTip(text); };
  this.element.toMapPoint = function(p) { return this.controller.toMapPoint(p); };
  this.element.toPixelPoint = function(p) { return this.controller.toPixelPoint(p); };
  this.element.zoom = function(scale) { this.controller.zoom(scale); };
  this.element.zoomFull = function() { this.controller.zoomFull(); };
  
  return this.element;
}

function Point(x, y) {
  this.x = parseFloat(x);
  this.y = parseFloat(y);

  this.distanceTo = function(p) {
    var dx = p.x - this.x;
    var dy = p.y - this.y;
    return Math.sqrt((dx * dx) + (dy * dy));
  };

  this.toString = function(prec, sep) {
    if (sep == null) {
      sep = ",";
    }
    
    if (prec == null) {
      return this.x + sep + this.y;
    }
    else {
      return (parseInt(this.x / prec) * prec) + sep + (parseInt(this.y / prec) * prec);
    }
  };
}

function Box(x1, y1, x2, y2) {
  var x1 = parseFloat(x1);
  var y1 = parseFloat(y1);
  var x2 = parseFloat(x2);
  var y2 = parseFloat(y2);
  
  this.minx = x1 < x2 ? x1 : x2;
  this.miny = y1 < y2 ? y1 : y2;
  this.maxx = x1 >= x2 ? x1 : x2;
  this.maxy = y1 >= y2 ? y1 : y2;

  this.clone = function() {
    return new Box(this.minx, this.miny, this.maxx, this.maxy);
  };

  this.equals = function(other) {
    return this.minx == other.minx && this.miny == other.miny && 
        this.maxx == other.maxx && this.maxy == other.maxy;
  };

  this.getCenter = function() {
    return new Point((this.minx + this.maxx) / 2, (this.miny + this.maxy) / 2);
  };

  this.getWidth = function() {
    return this.maxx - this.minx;
  };

  this.getHeight = function() {
    return this.maxy - this.miny;
  };

  this.reaspect = function(aspectRatio) {
    var width = this.getWidth();
    var height = this.getHeight();
    
    if (width == 0 || height == 0 || aspectRatio <= 0) 
      return;

    var dx;
    var dy;

    if (width / height > aspectRatio) {
      dx = width / 2;
      dy = dx / aspectRatio;
    }
    else {
      dy = height / 2;
      dx = dy * aspectRatio;
    }

    var c = this.getCenter();

    this.minx = c.x - dx;
    this.miny = c.y - dy;
    this.maxx = c.x + dx;
    this.maxy = c.y + dy;
  };

  this.scaleBy = function(scale) {
    var c = this.getCenter();
    
    var dx = this.getWidth() * scale / 2;
    var dy = this.getHeight() * scale / 2;
    
    this.minx = c.x - dx;
    this.miny = c.y - dy;
    this.maxx = c.x + dx;
    this.maxy = c.y + dy;
  };

  this.setCenter = function(c) {
    var p = this.getCenter();
    var dx = c.x - p.x;
    var dy = c.y - p.y;
    
    this.minx += dx;
    this.miny += dy;
    this.maxx += dx;
    this.maxy += dy;
  };

  this.toString = function() {
    return this.minx + "," + this.miny + "," + this.maxx + "," + this.maxy;
  };
}

function Line() {
  this.points = new Array();

  this.getLength = function() {
    var d = 0;

    for (var i = 1; i < this.points.length; ++i) {
      d += this.points[i - 1].distanceTo(this.points[i]);
    }
    
    return d;
  };

  this.isValid = function() {
    return this.getLength() > 0;
  };

  this.toString = function(prec) {
    var pointList = new Array();

    for (var i = 0; i < this.points.length; ++i) {
      pointList[i] = this.points[i].toString(prec, " ");
    }
    
    return "LINESTRING(" + pointList.join(",") + ")";
  };
}

function Polygon() {
  this.points = new Array();

  this.getArea = function() {
    var a = 0;
    
    for (var i = 1; i <= this.points.length; ++i) {
      var j = i % this.points.length;
      a += (this.points[i - 1].x - this.points[j].x) * (this.points[i - 1].y + this.points[j].y) / 2;
    }
    
    return Math.abs(a);
  };

  this.getCentroid = function() {
    var a = 0;
    var c = new Point(0, 0);
    
    for (var i = 1; i <= this.points.length; ++i) {
      var j = i % this.points.length;
      var n = (this.points[i - 1].x * this.points[j].y) - (this.points[j].x * this.points[i - 1].y);
      a += n;
      c.x += (this.points[i - 1].x + this.points[j].x) * n;
      c.y += (this.points[i - 1].y + this.points[j].y) * n;
    }
    
    if (a == 0) {
      return null;
    }
    
    a *= 3;
    c.x /= a;
    c.y /= a;
    
    return c;
  };

  this.isValid = function() {
    return this.getArea() > 0;
  };

  this.toString = function(prec) {
    var pointList = new Array();

    for (var i = 0; i < this.points.length; ++i) {
      pointList[i] = this.points[i].toString(prec, " ");
    }
    
    return "POLYGON((" + pointList.join(",") + "))";
  };
}

