// © 2003-2007, Applied Geographics, Inc.  All rights reserved.

function GraphicsType() {}

GraphicsType.None = 0;
GraphicsType.VML = 1;
GraphicsType.SVG = 2;

function Units() {}

Units.Degrees = 0;
Units.Feet = 1;
Units.Meters = 2;

function MapClickMode() {}

MapClickMode.Event = 0;
MapClickMode.Pan = 1;
MapClickMode.ZoomIn = 2;
MapClickMode.ZoomOut = 3;
MapClickMode.MoveMarker = 4;
MapClickMode.DynamicPan = 5;
MapClickMode.DynamicZoomIn = 6;
MapClickMode.Box = 7;
MapClickMode.ClientClick = 8;
MapClickMode.ClientBox = 9;
MapClickMode.Polyline = 10;
MapClickMode.Polygon = 11;
MapClickMode.ClientPolyline = 12;
MapClickMode.ClientPolygon = 13;
MapClickMode.MeasureLength = 14;
MapClickMode.MeasureArea = 15;

function DynamicMapControl(id, postBack, enableScrollWheel) {
  this.id = id;
  this.image = document.getElementById(id + "_image");
  this.stretchyBox = document.getElementById(id + "_box");
  this.stretchyBoxColor = "gray";
  this.content = document.getElementById(id + "_content");
  
  this.postBack = postBack;
  this.clickScript = "";
  this.doubleClickScript = "";
  this.boxScript = "";
  this.polylineScript = "";
  this.polygonScript = "";
  this.downScript = "";
  this.moveScript = "";
  this.upScript = "";
  this.outScript = "";
  
  var frame = document.getElementById(id + "_frame");
  this.left = parseInt(frame.style.left);
  this.top = parseInt(frame.style.top);
  this.width = parseInt(this.image.style.width);
  this.height = parseInt(this.image.style.height);
  
  this.hotspots = new Array();
  
  this.anchor = null;
  this.isMouseDown = false;
  this.enableScrollWheel = enableScrollWheel;
    
  this.mapPoint = null;
  this.imagePoint = null;
  this.event = null;
  
  this.graphicsType = GraphicsType.None;
  
  if (document.all) {
    this.graphicsType = GraphicsType.VML;
  }
  
  this.strokeWidth = 2;
  this.strokeColor = "gray";
  this.fillColor = "white";
  this.fillOpacity = 1;
  this.textColor = "black";
  
  this.units = Units.Feet;
  this.coordinates
  this.graphic = null;
  this.text = null;
  this.imageShape = null;
  this.mapShape = null;

  this.postBackStarted = false;
  
  if (enableScrollWheel) {
    this.wheelTimerHandle = null;
    this.wheelLevel = 0;
    this.wheelZoomFactor = 1.18920711500273;

    var canvas = document.getElementById(id + "_canvas");
    
    if (canvas.addEventListener) {
      var target = this;
	    canvas.addEventListener('DOMMouseScroll', function (e) {
	      target.mouseWheel(e);
      }, false);
	  }
	}
}

DynamicMapControl.prototype.addHotSpot = function(h) {
  this.hotspots.push(h);
};

DynamicMapControl.prototype.clearGraphics = function() {
  this.resetGraphics();
  
  while (this.content.childNodes.length > 0) {
    this.content.removeChild(this.content.lastChild);
  }
};

DynamicMapControl.prototype.createGraphics = function() {
  switch (this.graphicsType) {
    case GraphicsType.VML:
      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.strokeColor;
      this.graphic.strokeweight = this.strokeWidth + "px";

      var fill = document.createElement("v:fill");
      fill.color = this.fillColor;
      fill.opacity = this.getClickMode() == MapClickMode.Polygon || this.getClickMode() == MapClickMode.ClientPolygon || this.getClickMode() == MapClickMode.MeasureArea ? this.fillOpacity : 0;
      this.graphic.appendChild(fill);
      
      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 = this.textColor;
      this.text.style.width = "100px";
      this.text.style.height = "43px";
      this.text.style.visibility = "hidden";
      break; 
  }
  
  if (this.graphic != null) {
    this.content.appendChild(this.graphic);
    this.content.appendChild(this.text);
  }
}

DynamicMapControl.prototype.doubleClick = function(e) {
  if (this.postBackStarted) {
    return;
  }

  switch (this.getClickMode()) {
    case MapClickMode.ClientClick:
      var p = this.toPoint(e);
      this.mapPoint = this.toMapPoint(p);
      this.imagePoint = p;
      this.event = e;
      
      eval(this.doubleClickScript);
      break;
  
    case MapClickMode.Polyline:
    case MapClickMode.Polygon:
    case MapClickMode.ClientPolyline:
    case MapClickMode.ClientPolygon:
      if (!this.mapShape.isValid()) {
        this.clearGraphics();
      }
      else {
        if (this.getClickMode() == MapClickMode.Polygon || this.getClickMode() == MapClickMode.ClientPolygon) {
          var i = this.mapShape.points.length - 1;
          this.mapShape.points[i] = new Point(this.mapShape.points[0].x, this.mapShape.points[0].y);
        }
        else {
          this.mapShape.points.pop();
        }
        
        var prefix = this.getHiddenPrefix();
      
        switch (this.getClickMode()) {
          case MapClickMode.Polyline:
            document.getElementById(prefix + "Polyline").value = this.mapShape.toString();
            eval(this.postBack);
            this.postBackStarted = true;
						this.resetGraphics();
            break;
            
          case MapClickMode.Polygon:
            document.getElementById(prefix + "Polygon").value = this.mapShape.toString();
            eval(this.postBack);
            this.postBackStarted = true;
						this.resetGraphics();
            break;
            
          case MapClickMode.ClientPolyline:
            eval(this.polylineScript);
            this.clearGraphics();
            break;
            
          case MapClickMode.ClientPolygon:
            eval(this.polygonScript);
            this.clearGraphics();
            break;
        }
      }
      break;
      
    case MapClickMode.MeasureLength:
    case MapClickMode.MeasureArea:
      this.clearGraphics();
      break;
  }
};

DynamicMapControl.prototype.findHotSpot = function(e) {
  if (this.hotspots.length == 0) {
    return;
  }
  
  var box = document.getElementById(this.id + "_tip");
  var p = this.toPoint(e);
  
  for (var i = 0; i < this.hotspots.length; ++i) {
    var h = this.hotspots[i];
    if (h.location.distanceTo(p) <= h.radius) {
      box.style.left = (this.left + h.location.x + 10) + "px";
      box.style.top = (this.top + h.location.y) + "px";
      box.innerHTML = h.tiptext;
      box.style.zIndex = "10000";
      box.style.visibility = "visible";
      return;
    }
  }
  
  box.style.visibility = "hidden";
};

DynamicMapControl.prototype.getBox = function() {
  var prefix = this.getHiddenPrefix();
  return new Box(document.getElementById(prefix + "Box").value);
};

DynamicMapControl.prototype.getClick = function() {
  var prefix = this.getHiddenPrefix();
  return new Point(document.getElementById(prefix + "Click").value);
};

DynamicMapControl.prototype.getClickMode = function() {
  var prefix = this.getHiddenPrefix();
  return parseInt(document.getElementById(prefix + "ClickMode").value);
};

DynamicMapControl.prototype.getExtent = function() {
  var prefix = this.getHiddenPrefix();
  return new Box(document.getElementById(prefix + "Extent").value);
};

DynamicMapControl.prototype.getHiddenPrefix = function() {
  return "__" + this.id + "_";
};

DynamicMapControl.prototype.getVisibleExtent = function() {
  var box = this.getExtent();
  box.fit(this.width / this.height);
  return box;
};

DynamicMapControl.prototype.labelShape = function() {
  var text = null;
  var c = null;
  var metersPerFoot = 0.3048;
  var convert = 1 / (this.units == Units.Feet ? 1 : metersPerFoot);
  
  switch (this.getClickMode()) {
    case MapClickMode.MeasureLength:
      var d = this.mapShape.getLength() * convert;

      if (d < 5280) {
        text = Math.round(d) + " ft";
      }
      else {
        text = this.realFormat(d / 5280, 1) + " mi";
      }
      
      text += "\n";
      d *= metersPerFoot;
        
      if (d < 1000) {
        text += Math.round(d) + " m";
      }
      else {
        text += this.realFormat(d / 1000, 1) + " km";
      }
      break;
    
    case MapClickMode.MeasureArea:
      c = this.imageShape.getCentroid();
      
      if (c != null) {
        var a = this.mapShape.getArea() * convert * convert;
        var acres = a / 43560;
        
        if (a <= 2787840) {
          text = Math.round(a) + " sq ft";
        }
        else {
          text = this.realFormat(a / 27878400, 2) + " sq mi";
        }
        
        text += "\n";
        a *= metersPerFoot * metersPerFoot;
          
        if (a <= 100000) {
          text += Math.round(a) + " sq m";
        }
        else {
          text += this.realFormat(a / 1000000, 2) + " sq km";
        }
        
        text += "\n";
        text += this.realFormat(acres, 2) + " acres";
      }
      break;
  }
  
  switch (this.graphicsType) {
    case GraphicsType.VML:
      if (text == null) {
        this.text.style.visibility = "hidden";
      }
      else {
        this.text.innerHTML = text.replace(/\n/g, "<br>");
        
        if (this.getClickMode() == MapClickMode.MeasureLength) {
          if (this.imageShape.points.length == 2) {
            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 ? 7 : -33)) + "px";
            this.text.style.visibility = "visible";
          }
        }
        else {
          this.text.style.left = c.x - 50;
          this.text.style.top = c.y - 22;
          this.text.style.visibility = "visible";
        }
      }
      break;
  }        
};

DynamicMapControl.prototype.mouseDown = function(e) {
  if (this.postBackStarted) {
    return;
  }

  if (e.button ? e.button != 1 : e.which != 1) {
    return;
  }
  
  var p = this.toPoint(e);
  this.mapPoint = this.toMapPoint(p);
  this.imagePoint = p;
  this.event = e;
  
  eval(this.downScript);

  if (this.isMouseDown)
    return;
    
  this.setClick(null);
  this.setBox(null);
  
  this.anchor = p;
  
  switch (this.getClickMode()) {
    case MapClickMode.Event:
    case MapClickMode.Pan:
    case MapClickMode.ZoomIn:
    case MapClickMode.ZoomOut:
    case MapClickMode.MoveMarker:
      this.setClick(this.toMapPoint(this.anchor));
      eval(this.postBack);
      this.postBackStarted = true;
      break;
    
    case MapClickMode.DynamicPan:
      this.isMouseDown = true;
      break;
      
    case MapClickMode.DynamicZoomIn:
    case MapClickMode.Box:
    case MapClickMode.ClientBox:
      this.stretchyBox.style.borderColor = this.stretchyBoxColor;
      this.setStretchyBox(this.anchor, this.anchor);
      this.isMouseDown = true;
      break;
    
    case MapClickMode.ClientClick:
      this.setClick(this.toMapPoint(this.anchor));
      eval(this.clickScript);
      break;
    
    case MapClickMode.Polyline:
    case MapClickMode.Polygon:
    case MapClickMode.ClientPolyline:
    case MapClickMode.ClientPolygon:
    case MapClickMode.MeasureLength:
    case MapClickMode.MeasureArea:
      if (this.graphicsType != GraphicsType.None) {
        if (this.graphic == null) {
          this.createGraphics();
          if (this.getClickMode() == MapClickMode.Polyline || this.getClickMode() == MapClickMode.ClientPolyline || this.getClickMode() == MapClickMode.MeasureLength) {
            this.imageShape = new LineString();
            this.mapShape = new LineString();
          }
          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] = this.imagePoint;
        this.mapShape.points[i] = this.mapPoint;
        ++i;
        this.imageShape.points[i] = this.imagePoint;;
        this.mapShape.points[i] = this.mapPoint;
        
        this.redrawShape();

        if (this.getClickMode() == MapClickMode.MeasureArea) {
          this.labelShape();
        }
      }
      break;
  }
};

DynamicMapControl.prototype.mouseMove = function(e) {            
  if (this.postBackStarted) {
    return;
  }

  var p = this.toPoint(e);
  this.mapPoint = this.toMapPoint(p);
  this.imagePoint = p;
  this.event = e;
  
  if (this.moveScript.length > 0) {
    eval(this.moveScript);
  }
  
  var clickMode = this.getClickMode();
  
  if (!this.isMouseDown && !(clickMode == MapClickMode.Polyline || clickMode == MapClickMode.Polygon || clickMode == MapClickMode.ClientPolyline || clickMode == MapClickMode.ClientPolygon || clickMode == MapClickMode.MeasureLength || clickMode == MapClickMode.MeasureArea)) {
    this.findHotSpot(e);
    return;
  }
  
  switch (clickMode) {
    case MapClickMode.DynamicPan:
      this.image.style.left = p.x - this.anchor.x + "px";
      this.image.style.top = p.y - this.anchor.y + "px";
      break;
      
    case MapClickMode.DynamicZoomIn:
    case MapClickMode.Box:
    case MapClickMode.ClientBox:
      this.setStretchyBox(this.anchor, p);
      this.stretchyBox.style.visibility = this.anchor.x == p.x && this.anchor.y == p.y ?
          "hidden" : "visible";
      break;
      
    case MapClickMode.Polyline:
    case MapClickMode.Polygon:
    case MapClickMode.ClientPolyline:
    case MapClickMode.ClientPolygon:
    case MapClickMode.MeasureLength:
    case MapClickMode.MeasureArea:
      if (this.graphic != null) {
        var i = this.imageShape.points.length - 1;
        this.imageShape.points[i] = this.imagePoint;
        this.mapShape.points[i] = this.mapPoint;
        
        this.redrawShape();

        if (clickMode == MapClickMode.MeasureLength || clickMode == MapClickMode.MeasureArea) {
          this.labelShape();
        }
      }
      break;
  }
};

DynamicMapControl.prototype.mouseUp = function(e) {
  if (this.postBackStarted) {
    return;
  }

  var p = this.toPoint(e);
  this.mapPoint = this.toMapPoint(p);
  this.imagePoint = p;
  this.event = e;
  
  eval(this.upScript);
  
  if (!this.isMouseDown)
    return;
  
  this.isMouseDown = false;
  
  switch (this.getClickMode()) {
    case MapClickMode.DynamicPan:
      x = parseInt(this.width / 2) + (this.anchor.x - p.x);
      y = parseInt(this.height / 2) + (this.anchor.y - p.y);
      this.setClick(this.toMapPoint(new Point(x, y)));
      eval(this.postBack);
      this.postBackStarted = true;
      break;

    case MapClickMode.DynamicZoomIn:
      var extent;
      
      var dx = Math.abs(this.anchor.x - p.x);
      var dy = Math.abs(this.anchor.y - p.y);
      
      var p1 = this.toMapPoint(this.anchor);
      var p2 = this.toMapPoint(p);
      
      if (dx <= 10 && dy <= 10) {
        extent = this.getExtent();
        var w = extent.getWidth();
        var h = extent.getHeight();
        
        p.x = (p1.x + p2.x) / 2;
        p.y = (p1.y + p2.y) / 2;
        
        extent.minx = p.x - (w / 4);
        extent.miny = p.y - (h / 4);
        extent.maxx = p.x + (w / 4);
        extent.maxy = p.y + (h / 4);
      }
      else {
        extent = new Box(p1.x, p1.y, p2.x, p2.y);
      }
      
      this.stretchyBox.style.visibility = "hidden";

      this.setBox(extent);
      eval(this.postBack);
      this.postBackStarted = true;
      break;
            
    case MapClickMode.Box:
    case MapClickMode.ClientBox:
      var p1 = this.toMapPoint(this.anchor);
      var p2 = this.toMapPoint(p);
      this.setBox(new Box(p1.x, p1.y, p2.x, p2.y));
        
      this.stretchyBox.style.visibility = "hidden";
      
      if (this.getClickMode() == MapClickMode.Box) {
        eval(this.postBack);
        this.postBackStarted = true;
      }
      else
        eval(this.boxScript);
      break;
  }
};

DynamicMapControl.prototype.mouseWheel = function(e) {
  if (this.postBackStarted || !this.enableScrollWheel) {
    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);
		}
		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.width * scale) + "px";
		this.image.style.height = parseInt(this.height * scale) + "px";
		
		var target = this;
		this.wheelTimerHandle = window.setTimeout(function() {
		  target.mouseWheelFinish();
		}, 1000);
	}
};

DynamicMapControl.prototype.mouseWheelFinish = function() {
  this.wheelTimerHandle = null;
  
  if (this.wheelLevel != 0) {
		var extent = this.getExtent();
		var p = this.toMapPoint(this.anchor);
		var scale = Math.pow(this.wheelZoomFactor, -this.wheelLevel);
		
		var minx = p.x + ((extent.minx - p.x) * scale);
		var miny = p.y + ((extent.miny - p.y) * scale);
		var maxx = p.x + ((extent.maxx - p.x) * scale);
		var maxy = p.y + ((extent.maxy - p.y) * scale);
		
		this.setBox(new Box(minx, miny, maxx, maxy));
		this.postBackStarted = true;
		this.wheelLevel = 0;
		
		eval(this.postBack);
  }
}

DynamicMapControl.prototype.mouseOut = function(e) {
  if (this.postBackStarted) {
    return;
  }

  this.event = e;
  eval(this.outScript);
};

DynamicMapControl.prototype.realFormat = function(n, d) {
  var x = n > 0 ? Math.floor(n) : Math.ceil(n);
  var r = Math.floor(Math.abs(n - x) * Math.pow(10, d));
  return x + "." + (r + "000000").substr(0, d);
};

DynamicMapControl.prototype.redrawShape = function() {
  switch (this.graphicsType) {
    case GraphicsType.VML:
      var path = "m " + this.imageShape.points[0].x + "," + this.imageShape.points[0].y + " l ";

      for (i = 1; i < this.imageShape.points.length; ++i) {
        path += this.imageShape.points[i].x + "," + this.imageShape.points[i].y;
        if (i < this.imageShape.points.length - 1) {
          path += ",";
        }
      }
      
      if (this.getClickMode() == MapClickMode.Polygon || this.getClickMode() == MapClickMode.ClientPolygon || this.getClickMode() == MapClickMode.MeasureArea) {
        path += " x";
      }
      
      this.graphic.path = path + " e";
      break;
  }
};

DynamicMapControl.prototype.resetGraphics = function() {
  this.text = null;
  this.graphic = null;
  this.imageShape = null;
  this.mapShape = null;
};

DynamicMapControl.prototype.setBox = function(box) {
  var prefix = this.getHiddenPrefix();
  document.getElementById(prefix + "Box").value = box != null ? box.toString() : "";
};

DynamicMapControl.prototype.setClick = function(p) {
  var prefix = this.getHiddenPrefix();
  document.getElementById(prefix + "Click").value = p != null ? p.x + "," + p.y : "";
};

DynamicMapControl.prototype.setClickMode = function(clickMode) {
  var prefix = this.getHiddenPrefix();
  document.getElementById(prefix + "ClickMode").value = clickMode;
};

DynamicMapControl.prototype.setStretchyBox = function(p1, p2) {
  var box = new Box(p1.x, p1.y, p2.x, p2.y);
  this.stretchyBox.style.left = box.minx + "px";
  this.stretchyBox.style.top = box.miny + "px";
  this.stretchyBox.style.width = box.getWidth() + "px";
  this.stretchyBox.style.height = box.getHeight() + "px";
};

DynamicMapControl.prototype.toPoint = function(e) {
  var x = e.layerX ? e.layerX - 2 : e.offsetX;
  var y = e.layerY ? e.layerY - 2 : e.offsetY;
  
  var target = e.target ? e.target : e.srcElement;
  if (target == this.stretchyBox) {
    x += parseInt(target.style.left) + (e.layerX ? 1 : 2);
    y += parseInt(target.style.top) + (e.layerY ? 1 : 2);
  }
  
  return new Point(x, y);
};

DynamicMapControl.prototype.toMapPoint = function(p) {
  var box = this.getVisibleExtent();
  var x = box.minx + (p.x * box.getWidth() / this.width);
  var y = box.maxy - (p.y * box.getHeight() / this.height);
  return new Point(x, y);
};

function Point(x, y) {
  if (y == null) {
    var p = x.split(",");
    x = p[0];
    y = p[1];
  }
  
  this.x = parseFloat(x);
  this.y = parseFloat(y);
}

Point.prototype.distanceTo = function(p) {
  var dx = p.x - this.x;
  var dy = p.y - this.y;
  return Math.sqrt((dx * dx) + (dy * dy));
};

Point.prototype.toBasicString = function(prec) {
  if (prec == null) {
    return this.x + " " + this.y;
  }
  else {
    return (parseInt(this.x / prec) * prec) + " " + (parseInt(this.y / prec) * prec);
  }
};

Point.prototype.toString = function(prec) {
  return "POINT(" + this.toBasicString(prec) + ")";
};

function Box(x1, y1, x2, y2) {
  if (y1 == null) {
    var c = x1.split(",");
    x1 = c[0];
    y1 = c[1];
    x2 = c[2];
    y2 = c[3];
  }
  
  x1 = parseFloat(x1);
  y1 = parseFloat(y1);
  x2 = parseFloat(x2);
  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;
}

Box.prototype.clone = function() {
  return new Box(this.minx, this.miny, this.maxx, this.maxy);
};

Box.prototype.equals = function(other) {
  return this.minx == other.minx && this.miny == other.miny && 
      this.maxx == other.maxx && this.maxy == other.maxy;
};

Box.prototype.getCenter = function() {
  return new Point((this.minx + this.maxx) / 2, (this.miny + this.maxy) / 2);
};

Box.prototype.getWidth = function() {
  return this.maxx - this.minx;
};

Box.prototype.getHeight = function() {
  return this.maxy - this.miny;
};

Box.prototype.fit = 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;
};

Box.prototype.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;
};

Box.prototype.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;
};

Box.prototype.toString = function() {
  return this.minx + "," + this.miny + "," + this.maxx + "," + this.maxy;
};

function LineString() {
  this.points = new Array();
}

LineString.prototype.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;
};

LineString.prototype.isValid = function() {
  return this.getLength() > 0;
};

LineString.prototype.toString = function(prec) {
  var pointList = new Array();

  for (var i = 0; i < this.points.length; ++i) {
    pointList[i] = this.points[i].toBasicString(prec);
  }
  
  return "LINESTRING(" + pointList.join(",") + ")";
};

function Polygon() {
  this.points = new Array();
}

Polygon.prototype.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);
};

Polygon.prototype.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;
};

Polygon.prototype.isValid = function() {
  return this.getArea() > 0;
};

Polygon.prototype.toString = function(prec) {
  var pointList = new Array();

  for (var i = 0; i < this.points.length; ++i) {
    pointList[i] = this.points[i].toBasicString(prec);
  }
  
  return "POLYGON((" + pointList.join(",") + "))";
};

function HotSpot(tiptext, x, y, radius) {
  this.tiptext = tiptext;
  this.location = new Point(x, y);
  this.radius = radius;
}

