text: ﻿var geometry = {
    EARTH_RADIUS_KM: 6371
  };
  
  var pi = 3.14159265;
  var pi2 = 6.28318531;
  
  geometry.PI = pi;
  geometry.PI2 = pi2;
  
  geometry.MapDirection = {
    North:     {Name: "North"},
    NorthEast: {Name: "NorthEast"},
    East:      {Name: "East"},
    SouthEast: {Name: "SouthEast"},
    South:     {Name: "South"},
    SouthWest: {Name: "SouthWest"},
    West:      {Name: "West"},
    NorthWest: {Name: "NorthWest"}
  };
  
  // Returns pixel distance between two points
  geometry.distance = function(x1, y1, x2, y2) { return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); };
  
  // Returns pixel distance between two points
  geometry.distancePoints = function(p1, p2) {  return geometry.distance(p1.x(), p1.y(),  p2.x(), p2.y()); };
  
  
  // Converts radians to degrees
  geometry.radToDeg = function(rad){ return (rad / pi) * 180; };
  
  // Converts defrees to rads
  geometry.degToRad = function(deg){ return (deg / 180) * pi; };
  
  
  // Returns azimuth angle (theta) in radians
  geometry.azimuthRad = function(xc, yc, xd, yd) {
    var angle = Math.atan2(yd - yc, xd - xc);
    if (angle < 0) angle = pi2 + angle;
    return angle;
  };
  
  // Returns azimuth angle (theta) in radians for two points: center and destination
  geometry.azimuthRadPoints = function(pc, pd) {  return geometry.azimuthRad(pc.x(), pc.y(),  pd.x(), pd.y()); };
  
  // Returns azimuth angle (theta) in degrees
  geometry.azimuthDeg = function(xc, yc, xd, yd){ return (geometry.azimuthRad(xc, yc, xd, yd) / pi2) * 360; };
  
  // Returns azimuth angle (theta) in degrees for twho points: center and destination
  geometry.azimuthDegPoints = function(pc, pd){ return geometry.azimuthDeg(pc.x(), pc.y(),  pd.x(), pd.y()); };
  
  // Returns azimuth angle (theta) in radix units
  geometry.azimuthOfRadix = function(xc, yc, xd, yd, radix) {
    if (radix < 2) radix = 2;
    var angle = geometry.azimuthRad(xc, yc, xd, yd);
    var half = pi / radix;
    angle = geometry.wrapAngle(angle, half);
    return Math.floor((angle / pi2) * radix);
  };
  
  // Returns azimuth angle (theta) in in radix units for twho points: center and destination
  geometry.azimuthOfRadixPoints = function(pc, pd, radix){ return geometry.azimuthOfRadix(pc.x(), pc.y(),  pd.x(), pd.y(), radix); };
  
  //Returns rectangle from coordinate pairs
  geometry.toRectXY = function(x1,y1, x2,y2) {
    return new geometry.Rectangle( new geometry.Point(x1, y1), new geometry.Point(x2, y2) );
  };
  
  // Returns rectangle from coordinats and dimensions
  geometry.toRectWH = function(x1,y1, w,h) {
    return new geometry.Rectangle( new geometry.Point(x1, y1), new geometry.Point(x1 + w, y1 + h) );
  };
  
    // Returns area of overlap between two rectangles
  geometry.overlapAreaRect = function(rect1, rect2) {
    var tl1 = rect1.topLeft();
    var tl2 = rect2.topLeft();
    return geometry.overlapAreaWH(
      tl1.x(), tl1.y(), rect1.width(), rect1.height(),
      tl2.x(), tl2.y(), rect2.width(), rect2.height()
    );
  };
  
  // Returns area of overlap between two rectangles expressed as top-left/width-height pairs
  geometry.overlapAreaWH = function(x1,y1, w1,h1, x2,y2, w2,h2) {
    var ix;
    var iy;
  
    if (w2>=w1) {
      if (x1<=x2) ix = (x1+w1) - x2;
      else if ((x1+w1)>=(x2+w2)) ix = (x2+w2)-x1;
      else ix = w1;
    } else {
      if (x2<=x1) ix = (x2+w2) - x1;
      else if ((x2+w2)>=(x1+w1)) ix = (x1+w1)-x2;
      else ix = w2;
    }
  
    if (h2>=h1) {
      if (y1<=y2) iy = (y1+h1) - y2;
      else if ((y1+h1)>=(y2+h2)) iy = (y2+h2)-y1;
      else iy = h1;
    } else {
      if (y2<=y1) iy = (y2+h2) - y1;
      else if ((y2+h2)>=(y1+h1)) iy = (y1+h1)-y2;
      else iy = h2;
    }
  
    if (ix<0) ix = 0;
    if (iy<0) iy = 0;
  
    return ix*iy;
  };
  
  // Returns area of overlap between two rectangles expressed as top-left/bottom-right pairs
  geometry.overlapAreaXY = function(left1, top1, right1, bott1, left2, top2, right2, bott2) {
    return geometry.overlapAreaWH(left1, top1, right1-left1, bott1-top1, left2, top2, right2-left2, bott2-top2);
  };
  
  // defines if line has common points with rect
  geometry.lineIntersectsRect = function(rx1, ry1, rx2, ry2, lx1, ly1, lx2, ly2) {
    var a = ly1 - ly2,
        b = lx2 - lx1,
        c = -(a * lx1 + b * ly1);
  
    var r = a * rx1 + b * ry1 + c;
    var sign = r === 0 ? 0 : (r > 0 ? 1 : -1);
  
    r = a * rx1 + b * ry2 + c;
    if ((r === 0 ? 0 : (r > 0 ? 1 : -1)) !== sign) return true;
  
    r = a * rx2 + b * ry1 + c;
    if ((r === 0 ? 0 : (r > 0 ? 1 : -1)) !== sign) return true;
  
    r = a * rx2 + b * ry2 + c;
    if ((r === 0 ? 0 : (r > 0 ? 1 : -1)) !== sign) return true;
  
    return false;
  };
  
  //Returns area of line overlap with rect. Normalizes coordinate direction
  geometry.intersectionLengthRectLineXY = function(rx1, ry1, rx2, ry2, lx1, ly1, lx2, ly2) {
    if (!geometry.lineIntersectsRect(rx1, ry1, rx2, ry2, lx1, ly1, lx2, ly2)) return 0;
  
    var x1, y1, x2, y2;
  
    if (lx1 < rx1) x1 = rx1; else if (lx1 > rx2) x1 = rx2; else x1 = lx1;
    if (ly1 < ry1) y1 = ry1; else if (ly1 > ry2) y1 = ry2; else y1 = ly1;
    if (lx2 < rx1) x2 = rx1; else if (lx2 > rx2) x2 = rx2; else x2 = lx2;
    if (ly2 < ry1) y2 = ry1; else if (ly2 > ry2) y2 = ry2; else y2 = ly2;
  
    return geometry.distance(x1, y1, x2, y2);
  };
  
  // Modifies an angle by delta value ensuring that resulting angle is always between 0 and 2pi
  geometry.wrapAngle = function(angle, delta) {
    delta = delta % pi2;
    if (delta<0) delta = pi2 + delta;
    var result = angle + delta;
    return result % pi2;
  };
  
  //Converts map direction to angular coordinate in radians
  geometry.mapDirectionToAngle = function(direction) {
    switch (direction) {
      case geometry.MapDirection.North: return 4/16 * pi2;
      case geometry.MapDirection.South: return 12/16 * pi2;
      case geometry.MapDirection.East:  return 0.0;
      case geometry.MapDirection.West:  return 8/16 * pi2;
  
      case geometry.MapDirection.NorthEast: return 2/16 * pi2;
      case geometry.MapDirection.NorthWest: return 6/16 * pi2;
      case geometry.MapDirection.SouthEast: return 14/16 * pi2;
      case geometry.MapDirection.SouthWest: return 10/16 * pi2;
  
      default: return 0.0;
      }
  };
  
  // Converts a radian angular coordinate into map direction
  geometry.angleToMapDirection = function(angle) {
    angle = geometry.wrapAngle(angle, 0);
    if ((angle >= 0.0) && (angle < pi2 * 1/16)) return geometry.MapDirection.East;
    else if ((angle >= pi2 * 1/16) && (angle < pi2 * 3/16)) return geometry.MapDirection.NorthEast;
    else if ((angle >= pi2 * 3 / 16) && (angle < pi2 * 5 / 16)) return geometry.MapDirection.North;
    else if ((angle >= pi2 * 5 / 16) && (angle < pi2 * 7 / 16)) return geometry.MapDirection.NorthWest;
    else if ((angle >= pi2 * 7 / 16) && (angle < pi2 * 9 / 16)) return geometry.MapDirection.West;
    else if ((angle >= pi2 * 9 / 16) && (angle < pi2 * 11 / 16)) return geometry.MapDirection.SouthWest;
    else if ((angle >= pi2 * 11 / 16) && (angle < pi2 * 13 / 16)) return geometry.MapDirection.South;
    else if ((angle >= pi2 * 13 / 16) && (angle < pi2 * 15 / 16)) return geometry.MapDirection.SouthEast;
    else return geometry.MapDirection.East;
  };
  
  // Calculates a relative area of an inner rectangle that violates outside perimeter.
  // The area is not 100% geometrically accurate - may be used for relative comparisons only
  geometry.perimeterViolationArea = function(perimeter, inner) {
    var ix = 0;
    var iy = 0;
  
    if (inner.left()<perimeter.left()) ix = perimeter.left() - inner.left();
    else if (inner.right()>perimeter.right()) ix = inner.right() - perimeter.right();
  
    if (inner.top() < perimeter.top()) iy = perimeter.top() - inner.top();
    else if (inner.bottom() > perimeter.bottom()) iy = inner.bottom() - perimeter.bottom();
  
    return (ix * inner.height()) + (iy * inner.width());
  };
  
  // Returns a point of intersection between a ray cast from the center of a rectangle
  // under certain polar coordinate angle and a rectangle side
  geometry.findRayFromRectangleCenterSideIntersection = function(rect, theta) {
    var center = new geometry.Point(rect.left() + rect.width() / 2, rect.top() + rect.height() / 2);
  
    //make ray "infinite" in comparison to rect
    var rayLength = rect.width() > rect.height() ? rect.width() : rect.height();
  
    if (rayLength < 100) rayLength = 100; //safeguard
  
    var rayEnd = new geometry.PolarPoint(rayLength, theta);//create ray "end" point
    var rayEndPoint = rayEnd.toPoint();
  
    //get line incline	aka. y = kx
    var k = (rayEndPoint.x() !== 0)? (rayEndPoint.y()) / (rayEndPoint.x()) : 0;
  
    var x = 0;
    var y = 0;
  
    var lst = [];
  
    //north
    x = center.x() + ((k !== 0) ? ((rect.top() - center.y()) / k) : 0);
    y = rect.top();
    if ((x >= rect.left()) && (x <= rect.right())) lst.push(new geometry.Point(x, y));
  
    //south
    x = center.x() + ((k !== 0) ? ((rect.bottom() - center.y()) / k) : 0);
    y = rect.bottom();
    if ((x >= rect.left()) && (x <= rect.right())) lst.push(new geometry.Point(x, y));
  
    //east
    x = rect.right();
    y = center.y() + k * (rect.right() - center.x());
    if ((y >= rect.top()) && (y <= rect.bottom())) lst.push(new geometry.Point(x, y));
  
    //west
    x = rect.left();
    y = center.y() + k * (rect.left() - center.x());
    if ((y >= rect.top()) && (y <= rect.bottom())) lst.push(new geometry.Point(x, y));
  
    var minPoint = new geometry.Point(1000000, 1000000);
    var minDistance = 1000000;
  
    var re = rayEnd.toPoint(); //rayEnd is relative to absolute 0,0
    re.offset(center.x(), center.y()); // need to make relative to rectangle center
  
    //find closest point
    for(var i in lst) {
      var p = lst[i];
      var dst = geometry.distancePoints(p, re);
      if (dst < minDistance) {
        minPoint = p;
        minDistance = dst;
      }
    }
    return minPoint;
  };
  
  // Calculates coordinates of rectangle given by width/height ('rw'/'rh')
  // which center is rotated by 'angle' (in radians) relatively to point ('cx'/'cy')
  // respecting reatangle size and 'margin'.
  // Returns coordinates of rotated rectangle (geometry.Rectangle object)
  geometry.rotateRectAroundCircle = function (cx, cy, cMargin, rw, rh, angle) {
    var halfRw = rw / 2, halfRh = rh / 2;
    var rcX = cx + Math.cos(angle) * (halfRw + cMargin);
    var rcY = cy + Math.sin(angle) * (halfRh + cMargin);
    return new geometry.toRectXY(rcX - halfRw, rcY - halfRh, rcX + halfRw, rcY + halfRh);
  };
  
  // Returns 2D bounding box (with  for a set of points (array of {x, y})
  geometry.getBBox = function(points) {
    if (!points) return null;
    var length = points.length;
    if (length === 0) return null;
    var minX = Number.POSITIVE_INFINITY, maxX=Number.NEGATIVE_INFINITY, minY=Number.POSITIVE_INFINITY, maxY=Number.NEGATIVE_INFINITY;
    for(var i=0; i<length; i++) {
      var p = points[i];
      var x = p.x, y = p.y;
      if(x < minX) minX = x;
      if(x > maxX) maxX = x;
      if(y < minY) minY = y;
      if(y > maxY) maxY = y;
    }
    return geometry.toRectXY(minX, minY, maxX, maxY);
  };
  
  // Point class represents x,y pair on a cartesian plane
  geometry.Point = function(x, y)
    {
    var fX = x;
    this.x = function(val){
            if (typeof(val)!==tUNDEFINED) fX = val;
            return fX;
        };
  
    var fY = y;
    this.y = function(val){
            if (typeof(val)!==tUNDEFINED) fY = val;
            return fY;
        };
  
    // Changes point coordinates
    this.offset = function(dx, dy) { fX += dx; fY += dy; };
  
  }; // Point
  
  
  // Returns point as polar point relative to the specified center
  geometry.Point.prototype.toPolarPoint = function(center) {
      var dist = geometry.distancePoints(center, this);
      var angle = Math.atan2(this.y() - center.y(), this.x() - center.x());
  
      if (angle < 0)	angle = pi2 + angle;
  
      return new geometry.PolarPoint(dist, angle);
  };
  
  
  //Determines whether the two points contain equivalent coordinates
  geometry.Point.prototype.isEqual = function(other){
    return (this.x() === other.x()) && (this.y() === other.y());
  };
  
  geometry.Point.prototype.toString = function()
  {
    return "(" + this.x().toString() + " , " + this.y().toString() + ")";
  };
  
  
  // Polar Point class represents point with polar coordinates
  geometry.PolarPoint = function(radius, theta)
  {
    var fRadius = radius;
    this.radius = function(val){
          if (typeof(val)!==tUNDEFINED) fRadius = val;
          return fRadius;
    };
  
    var fTheta = checkangle(theta);
    this.theta = function(val){
          if (typeof(val)!==tUNDEFINED) fTheta = checkangle(val);
          return fTheta;
    };
  
    //Returns polar point as simple x,y point
    this.toPoint = function(){
        var x = fRadius * Math.cos(fTheta);
        var y = fRadius * Math.sin(fTheta);
        return new geometry.Point(x, y);
    };
  
    function checkangle(angle){
      if ((angle < 0) || (angle > pi2))
          throw "Invalid polar coordinates angle";
      return angle;
    }
  
  
  };//PolarPoint
  
  geometry.PolarPoint.prototype.isEqual = function(other){
      return this.radius() === other.radius() && this.theta() === other.theta();
  };
  
  geometry.PolarPoint.prototype.toString = function(){
    return "(" + this.radius().toString() + " , " + geometry.radToDeg(this.theta()).toString() + "°)";
  };
  
  // Represents a rectangle
  geometry.Rectangle = function(corner1, corner2)
  {
    var self = this;
  
    var fCorner1 = corner1;
    var fCorner2 = corner2;
  
    this.corner1 = function(val){
        if (typeof(val)!==tUNDEFINED) fCorner1 = val;
        return fCorner1;
    };
  
    this.corner2 = function(val){
        if (typeof(val)!==tUNDEFINED) fCorner2 = val;
        return fCorner2;
    };
  
    // Returns top left corner point per natural axis orientation when x increases from left to right, and y increases from top to bottom
    this.topLeft = function(){
        var lx = fCorner1.x();
        var other = fCorner2.x();
  
        if (other < lx) lx = other;
  
        var ty = fCorner1.y();
        other = fCorner2.y();
  
        if (other < ty) ty = other;
        return new geometry.Point(lx, ty);
      };
  
    // Returns bottom right corner point per natural axis orientation when x increases from left to right, and y increases from top to bottom
    this.bottomRight = function(){
        var rx = fCorner1.x();
        var other = fCorner2.x();
  
        if (other > rx) rx = other;
  
        var by = fCorner1.y();
        other = fCorner2.y();
  
        if (other > by) by = other;
  
        return new geometry.Point(rx, by);
    };
  
    // Return rectangle width
    this.width = function(){  return Math.abs(fCorner1.x() - fCorner2.x()); };
  
    // Return rectangle height
    this.height = function(){  return Math.abs(fCorner1.y() - fCorner2.y()); };
  
    // Returns left-most edge coordinate
    this.left = function() {
      var lx = fCorner1.x();
      var other = fCorner2.x();
  
      if (other < lx) lx = other;
  
      return lx;
    };
  
    // Returns right-most edge coordinate
    this.right = function(){
      var rx = fCorner1.x();
      var other = fCorner2.x();
  
      if (other > rx) rx = other;
  
      return rx;
    };
  
    // Returns top-most edge coordinate
    this.top = function(){
      var ty = fCorner1.y();
      var other = fCorner2.y();
  
      if (other < ty) ty = other;
  
      return ty;
    };
  
    // Returns bottom-most edge coordinate
    this.bottom = function(){
      var by = fCorner1.y();
      var other = fCorner2.y();
  
      if (other > by) by = other;
  
      return by;
    };
  
    // Returns the center point
    this.centerPoint = function(){
      return new geometry.Point(self.left() + self.width() / 2, self.top() + self.height() / 2);
    };
  
    // Returns rectangle square
    this.square = function () { return self.width() * self.height(); };
  
    // Tests if point lies within this reactangle
    this.contains = function (point) {
      var topLeft = self.topLeft(), bottomRight = self.bottomRight();
      var px = point.x(), py = point.y();
      return px >= topLeft.x() && px <= bottomRight.x() && py >= topLeft.y() && py <= bottomRight.y();
    };
  };//Rectangle class
  
  geometry.Rectangle.prototype.isEqual = function(other){
      return this.left() === other.left() && this.top() === other.top() && this.width() === other.width() && this.height() === other.height();
  };
  
  geometry.Rectangle.prototype.toString = function(){
    return "(" + this.left().toString() + "," + this.top().toString() + " ; "+ this.width().toString() + "x" + this.height().toString()+")";
  };
  
  
  /**
  * Calculates callout balloon vertices suitable for contour curve drawing
  *
  * @param {Rectangle} body Balloon body coordinates
  * @param {Point} target Balloon leg attachment point
  * @param {double} legSweep Length of balloon leg attachment breach at balloon body edge, expressed in radians (arc length). A value such as PI/16 yields good results
  * @returns {array} An array of vertex points
  */
  geometry.vectorizeBalloon = function(body, target, legSweep)
  {
    var result = [];
  
    var center = new geometry.Point(body.left() + body.width() / 2, body.top() + body.height() / 2);
    var trg = target.toPolarPoint(center);
  
    var legStart = geometry.findRayFromRectangleCenterSideIntersection(
                                body,
                                geometry.wrapAngle(trg.theta(), -legSweep / 2));
  
    var legEnd = geometry.findRayFromRectangleCenterSideIntersection(
                                body,
                                geometry.wrapAngle(trg.theta(), +legSweep / 2));
  
    //build vertexes
    result.push(new geometry.Point(body.left(), body.top()));
  
    result.push(new geometry.Point(body.right(), body.top()));
  
    result.push(new geometry.Point(body.right(), body.bottom()));
  
    result.push(new geometry.Point(body.left(), body.bottom()));
  
    result.push(legStart);
    result.push(target);
    result.push(legEnd);
  
    //reorder points by azimuth so the curve can close and look like a balloon
    result.sort(function(p1, p2)
                    {
                      var pp1 = p1.toPolarPoint(center);
                      var pp2 = p2.toPolarPoint(center);
  
                      if (pp1.theta() > pp2.theta())
                        return -1;
                      else
                        return +1;
                    }
                );
  
    return result;
  };
  
  
    published.Geometry = geometry;