
// XUserAgent is a copy of the user agent code in maps.10.js

// user agent types
XUserAgent.IE = 1;
XUserAgent.MOZILLA = 2;
XUserAgent.SAFARI = 3;
XUserAgent.OPERA = 4;

// user agent OSes
XUserAgent.WIN = 0;
XUserAgent.NIX = 1;
XUserAgent.MAC = 2;

function XUserAgent(type, version, os) { this.type = type; this.version = version; this.os = os };

XUserAgent.create = function() {
    var userAgent = new XUserAgent(0, 0, null);
    var userAgentName = navigator.userAgent.toLowerCase();
    if (userAgentName.indexOf("opera") != -1) {
        userAgent.type = XUserAgent.OPERA;
        if (userAgentName.indexOf("opera/7") != -1 || userAgentName.indexOf("opera 7") != -1) {
            userAgent.version = 7
        } else if (userAgentName.indexOf("opera/8") != -1 || userAgentName.indexOf("opera 8") != -1) {
            userAgent.version = 8
        }
    } else if (userAgentName.indexOf("msie") != -1 && document.all) {
        userAgent.type = XUserAgent.IE;
        if (userAgentName.indexOf("msie 5")) { userAgent.version = 5 }
    } else if (userAgentName.indexOf("safari") != -1) { userAgent.type = XUserAgent.SAFARI }
    else if (userAgentName.indexOf("mozilla") != -1) { userAgent.type = XUserAgent.MOZILLA }
    if (userAgentName.indexOf("x11;") != -1) { userAgent.os = XUserAgent.NIX }
    else if (userAgentName.indexOf("macintosh") != -1) { userAgent.os = XUserAgent.MAC }

    return userAgent;
};


// XDistance provides convenience functions for converting between various
// measures of distance.

function XDistance(value, units) {
    this.value = value;
    this.units = units;
}

XDistance.M = 0;
XDistance.KM = 1;
XDistance.FT = 2;
XDistance.MI = 3;
XDistance.NM = 4;

XDistance.conversionTable = [
    [1, 1 / 1000, 3.2808399, 0.000621371192, 0.000539956803],
    [1000, 1, 3280.8399, 0.621371192, 0.539956803],
    [0.3048, 0.0003048, 1, 1 / 5280, 0.000164578834],
    [1609.344, 1.609344, 5280, 1, 0.868976242],
    [1852, 1.85200, 6076.11549, 1.15077945, 1]
];

XDistance.prototype.convert = function(toUnits) { return XDistance.convert(this.value, this.units, toUnits) }

XDistance.prototype.toMeters = function() { return XDistance.convert(this.value, this.units, XDistance.M) }
XDistance.prototype.toKilometers = function() { return XDistance.convert(this.value, this.units, XDistance.KM) }
XDistance.prototype.toFeet = function() { return XDistance.convert(this.value, this.units, XDistance.FT) }
XDistance.prototype.toMiles = function() { return XDistance.convert(this.value, this.units, XDistance.MI) }
XDistance.prototype.toNauticalMiles = function() { return XDistance.convert(this.value, this.units, XDistance.NM) }

XDistance.convert = function(value, fromUnits, toUnits) {
    return value * XDistance.conversionTable[fromUnits][toUnits];
}

XDistance.between = function(point1, point2) {
    return XMaps.model.getDistanceAndAngle(point1, point2).distance;
}

XDistance.resolveToMeters = function(distanceOrMeters) {
    if (typeof distanceOrMeters == 'number') {
        return distanceOrMeters
    } else {
        return distanceOrMeters.toMeters()
    }
}


XAngle.RAD = 0;
XAngle.DEG = 1;

XAngle.radToDeg = 180 / Math.PI;
XAngle.degToRad = Math.PI / 180;

XAngle.conversionTable = [
    [1, XAngle.radToDeg],
    [XAngle.degToRad, 1]
];

XAngle.normalizeRadians = function(rad) {
    while (rad < 0) { rad += 2 * Math.PI }
    while (rad > 2 * Math.PI) { rad -= 2 * Math.PI }
    return rad
}

XAngle.normalizeDegrees = function(deg) {
    while (deg < 0) { deg += 360 }
    while (deg > 360) { deg -= 360 }
    return deg
}

XAngle.normalizationTable = [
    XAngle.normalizeRadians,
    XAngle.normalizeDegrees
];

function XAngle(value, units) {
    this.value = value;
    this.units = units;
}

XAngle.prototype.convert = function(toUnits) { return XAngle.convert(this.value, this.units, toUnits) }

XAngle.prototype.toRadians = function() { return XAngle.convert(this.value, this.units, XAngle.RAD) }

XAngle.prototype.toDegrees = function() { return XAngle.convert(this.value, this.units, XAngle.DEG) }

XAngle.prototype.normalize = function() {
    this.value = XAngle.normalizationTable[this.units](this.value);
}

XAngle.convert = function(value, fromUnits, toUnits) {
    if (fromUnits == toUnits) {
        return value;
    } else {
        return value * XAngle.conversionTable[fromUnits][toUnits];
    }
}

XAngle.radiansToDegrees = function(radians) {
    var deg = radians * XAngle.radToDeg;
    while (deg < 0) { deg += 360 }
    while (deg >= 360) { deg -= 360 }
    return deg
}

XAngle.degreesToRadians = function(degrees) {
    var rad = degrees * XAngle.degToRad;
    while (rad < 0) { rad += 2 * Math.PI }
    while (rad >= 2 * Math.PI) { rad -= 2 * Math.PI }
    return rad
}

XAngle.resolveToRadians = function(angleOrRadians) {
    if (typeof angleOrRadians == 'number') {
        return XAngle.normalizeRadians(angleOrRadians)
    } else {
        angleOrRadians.normalize();
        return angleOrRadians.toRadians()
    }
}


function XDistanceAndAngle(distance, angle) {
    this.distance = distance;
    this.angle = angle;
}


// XModel is a model of the world we're mapping.
// The meat of this class is from the source code of Navigate for Palm OS,
// written by Rick Chapman.
// http://fermi.jhuapl.edu/navigate/index.html

function XModel(flatteningFactor, semimajorAxis) {
    this.flatteningFactor = flatteningFactor;
    this.semimajorAxisInMeters = semimajorAxis.toMeters();
}

XModel.prototype.getDistanceAndAngle = function(from, to, distance, angle, distanceAndAngle) {
    distance = distance || new XDistance();
    distance.units = XDistance.M;

    angle = angle || new XAngle();
    angle.units = XAngle.RAD;

    distanceAndAngle = distanceAndAngle || new XDistanceAndAngle();
	distanceAndAngle.distance = distance;
	distanceAndAngle.angle = angle;

    var f = 1 / this.flatteningFactor;
    var ra = this.semimajorAxisInMeters;
    var eps = 5e-14;

    var r = 1 - f;
    var tu1 = r * Math.tan(from.y / XAngle.radToDeg);
    var tu2 = r * Math.tan(to.y / XAngle.radToDeg);
    var cu1 = 1 / Math.sqrt(tu1 * tu1 + 1);
    var su1 = cu1 * tu1;
    var cu2 = 1 / Math.sqrt(tu2 * tu2 + 1);
    var s = cu1 * cu2;
    var baz = s * tu2;
    var faz = baz * tu1;
    var x = (to.x - from.x) / XAngle.radToDeg;

    var d, sx, cx, sy, cy, cz, c2a, c, e, y, sa;
    var count = 0;
	do {
		sx = Math.sin(x);
		cx = Math.cos(x);
		tu1 = cu2 * sx;
		tu2 = baz - su1 * cu2 * cx;
		sy = Math.sqrt(tu1 * tu1 + tu2 * tu2);
		cy = s * cx + faz;
		y = Math.atan2(sy, cy);
		sa = s * sx / sy;
		c2a = -sa * sa + 1;
		cz = faz + faz;
		if (c2a > 0) { cz = -cz / c2a + cy }
		e = cz * cz * 2 - 1;
		c = ((-3 * c2a + 4) * f + 4) * c2a * f / 16;
		d = x;
		x = ((e * cy * c + cz) * sy * c + y) * sa;
		x = (1 - c) * x * f + (to.x - from.x) / XAngle.radToDeg;
	} while (Math.abs(d - x) > eps || count++ == 100);
	faz = Math.atan2(tu1, tu2);
	baz = Math.atan2(cu1 * sx, baz * cx - su1 * cu2) + Math.PI;
	x = Math.sqrt((1 / r / r - 1) * c2a + 1) + 1;
	x = (x - 2) / x;
	c = 1 - x;
	c = (x * x / 4 + 1) / c;
	d = (0.375 * x * x - 1) * x;
	x = e * cy;
	s = 1 - e * e;
	s = ((((sy * sy * 4 - 3) * s * cz * d / 6 - x) * d / 4 + cz) * sy * d + y) * c * ra * r;

	distance.value = s;
	angle.value = XAngle.normalizeRadians(faz);

	return distanceAndAngle;
}

XModel.prototype.getPointAtDistanceAndAngle = function(point, distance, angle, outPoint) {
    var meters = XDistance.resolveToMeters(distance);
    var radians = XAngle.resolveToRadians(angle);

    outPoint = outPoint || new GPoint();

	var f = 1 / this.flatteningFactor;
	var ra = this.semimajorAxisInMeters;
	var eps = 1e-13;
	var r = 1 - f;
	var tu = r * Math.tan(point.y * XAngle.degToRad);
	var sf = Math.sin(radians);
	var cf = Math.cos(radians);
	var baz = Math.atan2(tu, cf) * 2;
	var cu = 1 / Math.sqrt(tu * tu + 1);
	var su = tu * cu;
	var sa = cu * sf;
	var c2a = 1 - sa * sa;
	var x = Math.sqrt((1 / r / r - 1) * c2a + 1) + 1;
	var c, d, y, sy, cy, cz, e;

	x = (x - 2) / x;
	c = 1 - x;
	c = (x * x / 4 + 1) / c;
	d = (0.375 * x * x - 1) * x;
	tu = meters / r / ra / c;
	y = tu;
	var count = 0;
	do {
		sy = Math.sin(y);
		cy = Math.cos(y);
		cz = Math.cos(baz + y);
		e = cz * cz * 2 - 1;
		c = y;
		x = e * cy;
		y = e * e - 1;
		y = (((sy * sy * 4 - 3) * y * cz * d / 6 + x) * d / 4 - cz) * sy * d + tu;
	} while(Math.abs(y - c) > eps || count++ == 100);
	baz = cu * cy * cf - su * sy;
	c = r * Math.sqrt(sa * sa + baz * baz);
	d = su * cy + cu * sy * cf;
	var lat2 = Math.atan2(d, c) / XAngle.degToRad;
	c = cu * cy - su * sy * cf;
	x = Math.atan2(sy * sf, c);
	c = ((-3 * c2a + 4) * f + 4) * c2a * f / 16;
	d = ((e * cy * c + cz) * sy * c + y) * sa;
	var lng2 = (point.x * XAngle.degToRad + x - (1 - c) * d * f) / XAngle.degToRad;
	baz = Math.atan2(sa, baz) + Math.PI;

	outPoint.x = lng2;
	outPoint.y = lat2;

	return outPoint
}

XModel.earth = new XModel(298.257223563, new XDistance(6378137, XDistance.M));


// XMaps is the namespace for functions in GMaps that are not exposed but should be along
// with a bunch of other useful code. Some of these functions are copied from maps.10.js.

function XMaps() {};

XMaps.getElementById = function(id) { return document.getElementById(id) }

XMaps.userAgent = XUserAgent.create();

XMaps.toPixelString = function(pixels) { return Math.round(pixels) + "px" }

XMaps.addClassName = function(element, className) {
    if(element.className) {
        element.className += " " + className
    } else {
        element.className = className
    }
}

XMaps.falseFunction = function() { return false }

XMaps.integerCompare = function(a, b) { return (a < b) ? -1 : ((a > b) * 1); }

XMaps.defaultColor = { red: 0, green: 0, blue: 0xff };

XMaps.parseColor = function(color, defaultColor) {
    try {
        if (color.charAt(0) == "#") {
            color = color.substring(1)
        }
        var red = parseInt(color.substring(0, 2), 16);
        var green = parseInt(color.substring(2, 4), 16);
        var blue = parseInt(color.substring(4, 6), 16);
        return { red: red, green: green, blue: blue };
    } catch (e) {
        if (typeof defaultColor == 'object') {
            return defaultColor
        } else {
            return XMaps.parseColor(defaultColor, XMaps.defaultColor)
        }
    }
}

XMaps.defaultFont = {
    '*': { width: 15, points: [0,-6,2,-2,2,-2,5,-2,5,-2,2,0,2,0,3,4,3,4,0,2,0,2,-3,4,-3,4,-2,0,-2,0,-5,-2,-5,-2,-2,-2,-2,-2,0,-6] },
    ' ': { width: 10, points: [] },
    '+': { width: 11, points: [-3,0,3,0,0,3,0,-3] },
    '-': { width: 11, points: [-3,0,3,0] },
    'A': { width: 13, points: [-4,5,0,-5,0,-5,4,5,4,5,2,0,2,0,-2,0] },
    'B': { width: 13, points: [-4,5,-4,-5,-4,-5,2,-5,2,-5,4,-3,4,-3,4,-2,4,-2,2,0,2,0,0,0,0,0,2,0,2,0,4,2,4,2,4,3,4,3,2,5,2,5,-4,5] },
    'C': { width: 13, points: [4,3,2,5,2,5,-2,5,-2,5,-4,3,-4,3,-4,-3,-4,-3,-2,-5,-2,-5,2,-5,2,-5,4,-3] },
    'D': { width: 13, points: [-4,5,-4,-5,-4,-5,2,-5,2,-5,4,-3,4,-3,4,3,4,3,2,5,2,5,-4,5] },
    'E': { width: 13, points: [4,5,-4,5,-4,5,-4,0,-4,0,2,0,2,0,-4,0,-4,0,-4,-5,-4,-5,4,-5] },
    'F': { width: 13, points: [-4,5,-4,0,-4,0,2,0,2,0,-4,0,-4,0,-4,-5,-4,-5,4,-5] },
    'G': { width: 13, points: [4,-3,2,-5,2,-5,-2,-5,-2,-5,-4,-3,-4,-3,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,2,4,2,2,0] },
    'H': { width: 13, points: [-4,5,-4,-5,-4,-5,-4,0,-4,0,4,0,4,0,4,5,4,5,4,-5] },
    'I': { width: 7, points: [-1,5,1,5,1,5,0,5,0,5,0,-5,0,-5,-1,-5,-1,-5,1,-5] },
    'J': { width: 9, points: [0,-5,2,-5,2,-5,1,-5,1,-5,1,3,1,3,-1,5,-1,5,-2,5] },
    'K': { width: 13, points: [-4,5,-4,-5,-4,-5,-4,0,-4,0,4,5,4,5,-4,0,-4,0,4,-5] },
    'L': { width: 13, points: [4,5,-4,5,-4,5,-4,-5] },
    'M': { width: 15, points: [-5,5,-5,-5,-5,-5,0,0,0,0,5,-5,5,-5,5,5] },
    'N': { width: 13, points: [-4,5,-4,-5,-4,-5,4,5,4,5,4,-5] },
    'O': { width: 13, points: [-4,3,-4,-3,-4,-3,-2,-5,-2,-5,2,-5,2,-5,4,-3,4,-3,4,3,4,3,2,5,2,5,-2,5,-2,5,-4,3] },
    'P': { width: 13, points: [-4,5,-4,-5,-4,-5,2,-5,2,-5,4,-3,4,-3,4,-2,4,-2,2,0,2,0,-4,0] },
    'Q': { width: 13, points: [-4,3,-4,-3,-4,-3,-2,-5,-2,-5,2,-5,2,-5,4,-3,4,-3,4,3,4,3,2,5,2,5,-2,5,-2,5,-4,3,-4,3,-2,5,-2,5,2,5,2,5,1,3,1,3,4,5] },
    'R': { width: 13, points: [-4,5,-4,-5,-4,-5,2,-5,2,-5,4,-3,4,-3,4,-2,4,-2,2,0,2,0,-4,0,-4,0,4,5] },
    'S': { width: 13, points: [3,-4,2,-5,2,-5,-2,-5,-2,-5,-4,-3,-4,-3,-4,-2,-4,-2,-2,0,-2,0,2,0,2,0,4,2,4,2,4,3,4,3,2,5,2,5,-2,5,-2,5,-3,4] },
    'T': { width: 13, points: [-4,-5,4,-5,4,-5,0,-5,0,-5,0,5] },
    'U': { width: 13, points: [-4,-5,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,-5] },
    'V': { width: 13, points: [-4,-5,0,5,0,5,4,-5] },
    'W': { width: 13, points: [-4,-5,-1,5,-1,5,0,3,0,3,2,5,2,5,4,-5] },
    'X': { width: 13, points: [-4,-5,4,5,-4,5,4,-5] },
    'Y': { width: 13, points: [-4,-5,0,0,0,0,4,-5,4,-5,0,0,0,0,0,5] },
    'Z': { width: 13, points: [-4,-5,4,-5,4,-5,-4,5,-4,5,4,5] },
    'a': { width: 13, points: [-2,5,-4,3,-4,3,-4,0,-4,0,-2,-2,-2,-2,2,-2,2,-2,4,0,4,0,4,-2,4,-2,4,5,4,5,4,3,4,3,2,5,2,5,-2,5] },
    'b': { width: 13, points: [-4,-5,-4,5,-4,5,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,0,4,0,2,-2,2,-2,-2,-2,-2,-2,-4,0] },
    'c': { width: 12, points: [3,-1,2,-2,2,-2,-2,-2,-2,-2,-4,0,-4,0,-4,3,-4,3,-2,5,-2,5,2,5,2,5,3,4] },
    'd': { width: 13, points: [4,5,4,3,4,3,2,5,2,5,-2,5,-2,5,-4,3,-4,3,-4,0,-4,0,-2,-2,-2,-2,2,-2,2,-2,4,0,4,0,4,5,4,5,4,-5] },
    'e': { width: 13, points: [3,4,2,5,2,5,-2,5,-2,5,-4,3,-4,3,-4,0,-4,0,-2,-2,-2,-2,2,-2,2,-2,4,0,4,0,-2,0] },
    'f': { width: 9, points: [-2,5,-2,0,-2,0,2,0,2,0,-2,0,-2,0,-2,-3,-2,-3,0,-5,0,-5,2,-5] },
    'g': { width: 13, points: [4,0,2,-2,2,-2,-2,-2,-2,-2,-4,0,-4,0,-4,2,-4,2,-2,4,-2,4,2,4,2,4,4,2,4,2,4,-2,4,-2,4,7,4,7,2,9,2,9,-2,9] },
    'h': { width: 13, points: [-4,-5,-4,5,-4,5,-4,0,-4,0,-2,-2,-2,-2,2,-2,2,-2,4,0,4,0,4,5] },
    'i': { width: 6, points: [-1,-1,0,-1,0,-1,0,5,0,-5,-1,-5] },
    'j': { width: 8, points: [-1,-1,0,-1,0,-1,0,7,0,7,-2,9,0,-5,-1,-5] },
    'k': { width: 13, points: [-4,-5,-4,5,-4,5,-4,1,-4,1,4,-2,4,-2,-4,1,-4,1,4,5] },
    'l': { width: 6, points: [-1,-5,0,-5,0,-5,0,5] },
    'm': { width: 13, points: [-4,5,-4,-2,-4,-2,-4,-1,-4,-1,-3,-2,-3,-2,-1,-2,-1,-2,0,-1,0,-1,0,4,0,4,0,-1,0,-1,1,-2,1,-2,3,-2,3,-2,4,-1,4,-1,4,5] },
    'n': { width: 13, points: [-4,5,-4,-2,-4,-2,-4,0,-4,0,-2,-2,-2,-2,2,-2,2,-2,4,0,4,0,4,5] },
    'o': { width: 13, points: [-4,0,-2,-2,-2,-2,2,-2,2,-2,4,0,4,0,4,3,4,3,2,5,2,5,-2,5,-2,5,-4,3,-4,3,-4,0] },
    'p': { width: 13, points: [-4,-2,-4,9,-4,9,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,0,4,0,2,-2,2,-2,-2,-2,-2,-2,-4,0] },
    'q': { width: 13, points: [4,0,2,-2,2,-2,-2,-2,-2,-2,-4,0,-4,0,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,-2,4,-2,4,9] },
    'r': { width: 13, points: [-4,-2,-4,5,-4,5,-4,0,-4,0,-2,-2,-2,-2,2,-2,2,-2,4,0] },
    's': { width: 13, points: [4,-2,-2,-2,-2,-2,-4,0,-4,0,-3,1,-3,1,2,1,2,1,4,3,4,3,2,5,2,5,-2,5] },
    't': { width: 9, points: [-2,-5,-2,0,-2,0,2,0,2,0,-2,0,-2,0,-2,3,-2,3,0,5,0,5,2,5] },
    'u': { width: 13, points: [-4,-2,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,-2,4,-2,4,5] },
    'v': { width: 13, points: [-4,-2,0,5,0,5,4,-2] },
    'w': { width: 13, points: [-4,-2,-3,5,-3,5,0,0,0,0,3,5,3,5,4,-2] },
    'x': { width: 13, points: [-4,-2,4,5,-4,5,4,-2] },
    'y': { width: 13, points: [-4,-2,0,4,0,4,4,-2,4,-2,-4,9] },
    'z': { width: 13, points: [-4,-2,4,-2,4,-2,-4,5,-4,5,4,5] },
    '.': { width: 6, points: [-1,4,0,5,-1,5,0,4] },
    '>': { width: 13, points: [-4,5,4,0,4,0,-4,-5,-4,-5,-4,5] },
    '#': { width: 13, points: [-4,3,4,3,-4,-3,4,-3,-2,5,-2,-5,2,5,2,-5] },
    '0': { width: 13, points: [-4,3,-4,-3,-4,-3,-2,-5,-2,-5,2,-5,2,-5,4,-3,4,-3,4,3,4,3,2,5,2,5,-2,5,-2,5,-4,3,-4,3,-2,5,-2,5,2,-5] },
    '1': { width: 7, points: [-1,-3,1,-5,1,-5,1,5] },
    '2': { width: 13, points: [-4,-3,-2,-5,-2,-5,2,-5,2,-5,4,-3,4,-3,4,-2,4,-2,-4,4,-4,4,-4,5,-4,5,4,5] },
    '3': { width: 13, points: [-4,-4,-3,-5,-3,-5,2,-5,2,-5,4,-3,4,-3,4,-2,4,-2,2,0,2,0,-2,0,-2,0,2,0,2,0,4,2,4,2,4,3,4,3,2,5,2,5,-3,5,-3,5,-4,4] },
    '4': { width: 13, points: [-4,-5,-4,0,-4,0,4,0,1,-5,1,5] },
    '5': { width: 13, points: [4,-5,-4,-5,-4,-5,-4,-1,-4,-1,2,-1,2,-1,4,0,4,0,4,3,4,3,2,5,2,5,-4,5] },
    '6': { width: 13, points: [3,-5,-1,-5,-1,-5,-4,-2,-4,-2,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,1,4,1,2,-1,2,-1,-3,-1] },
    '7': { width: 13, points: [-4,-5,4,-5,4,-5,4,-2,4,-2,0,1,0,1,0,5] },
    '8': { width: 13, points: [-2,-5,-4,-3,-4,-3,-4,-2,-4,-2,-2,0,-2,0,-4,2,-4,2,-4,3,-4,3,-2,5,-2,5,2,5,2,5,4,3,4,3,4,2,4,2,2,0,2,0,-2,0,-2,0,2,0,2,0,4,-2,4,-2,4,-3,4,-3,2,-5,2,-5,-2,-5] },
    '9': { width: 13, points: [-3,5,2,5,2,5,4,3,4,3,4,-3,4,-3,2,-5,2,-5,-2,-5,-2,-5,-4,-3,-4,-3,-4,-2,-4,-2,-2,0,-2,0,3,0] }
};

XMaps.imageBaseUrl = "http://www.google.com/mapfiles/";

XMaps.getRotationParameters = function(from, to, autoRotate, autoRotateAngle, outRotationParameters) {
    outRotationParameters = outRotationParameters || {};

    var radians = Math.atan2(to.y - from.y, to.x - from.x);
    if (autoRotate) {
        if (typeof autoRotateAngle == 'undefined') {
            autoRotateAngle = outRotationParameters.autoRotateAngle || new XAngle();
            autoRotateAngle.value = 0;
            autoRotateAngle.units = XAngle.RAD;

            while (radians < -Math.PI / 2) {
                radians += Math.PI;
                autoRotateAngle.value = Math.PI
            }
            while (radians > Math.PI / 2) {
                radians -= Math.PI;
                autoRotateAngle.value = -Math.PI
            }
        } else {
            radians += XAngle.resolveToRadians(autoRotateAngle);
        }
    }
    var sin = Math.sin(radians);
    var cos = Math.cos(radians);

    outRotationParameters.cos = cos;
    outRotationParameters.sin = sin;
    outRotationParameters.angle = outRotationParameters.angle || new XAngle(radians, XAngle.RAD);
    outRotationParameters.autoRotateAngle = autoRotateAngle;
    return outRotationParameters;
};

XMaps.model = XModel.earth;

XMaps.combineStyles = function(overridingStyle, baseStyle, outStyle) {
    outStyle = outStyle || {};

    if (baseStyle) {
        for (key in baseStyle) {
            outStyle[key] = baseStyle[key]
        }
    }

    if (overridingStyle) {
        for (key in overridingStyle) {
            outStyle[key] = overridingStyle[key]
        }
    }

    return outStyle
}

XMaps.setTimeout = function (object, method, milliseconds) {
    var result = window.setTimeout(
            function() { method.apply(object) },
            milliseconds);
    return result
}

XMaps.toFixedString = function (value) {
    if (value.toFixed) {
        return value.toFixed(6).toString()
    } else {
        return value.toString()
    }
}


// XPolyline is an extension of GPolyline that allows for longer polylines and
// for patterns on the line.

XPolyline.vectorScalars = [131072, 65536, 32768, 16384, 8192, 4096, 2048, 1024,
        512, 256, 128, 64, 32, 16, 8];
XPolyline.defaultStyle = {
        color: '#0000ff',
        weight: 5,
        opacity: 0.45,
        pattern: null,
        segmentCount: null,
        beginArrow: false,
        endArrow: false,
        arrowsEvery: null,
        zIndex: 9000,
        font: XMaps.defaultFont,
        text: null,
        textEvery: 200,
        textFgStyle: {
            color: '#000000',
            weight: 2,
            opacity: 1,
            pattern: null,
            segmentCount: null,
            arrowsEvery: null,
            zIndex: 9020
        },
        textBgStyle: {
            color: null,
            weight: 7,
            opacity: 1,
            pattern: null,
            segmentCount: null,
            arrowsEvery: null,
            zIndex: 9010
        }
};

XPolyline.tempPoint0 = new GPoint(0, 0);
XPolyline.tempPoint1 = new GPoint(0, 0);

function XPolyline(points, style) {
    this.points = null;
    this.nextPointIndexAtLevel = null;
    this.pointCount = 0;
    if (points) {
        this.points = [];
        for (var i = 0; i < points.length; i++) {
            this.points.push(points[i].y / 1.0e-5);
            this.points.push(points[i].x / 1.0e-5)
        }
        this.pointCount = points.length;
        this.nextPointIndexAtLevel = [[]];
        for (var i = 0; i < this.points.length / 2; i++) {
            this.nextPointIndexAtLevel[0][i] = i + 1
        }
    }

    style = style || {};
    this.style = this.createStyle(style, XPolyline.defaultStyle);

    this.polyline = this.createXPolyline(this.style);

    if (this.style.text) {
        style.textFgStyle = style.textFgStyle || {};
        this.style.textFgStyle = this.createStyle(style.textFgStyle, XPolyline.defaultStyle.textFgStyle, style);
        this.textFgPolyline = this.createXPolyline(this.style.textFgStyle, true);

        style.textBgStyle = style.textBgStyle || {};
        this.style.textBgStyle = this.createStyle(style.textBgStyle, XPolyline.defaultStyle.textBgStyle, style);
        this.textBgPolyline = this.createXPolyline(this.style.textBgStyle, true);
    }

    var from, to;

    if (this.style.beginArrow) {
        if (this.style.segmentCount) {
            var distanceAndAngle = XMaps.model.getDistanceAndAngle(this.getPoint(0), this.getPoint(1));
            distanceAndAngle.distance.value /= this.style.segmentCount;
            from = XMaps.model.getPointAtDistanceAndAngle(this.getPoint(0), distanceAndAngle.distance, distanceAndAngle.angle);
        } else {
            from = this.getPoint(1);
        }
        to = this.getPoint(0);
        this.beginArrow = new XArrow(from, to, to, this.style.color, this.style.opacity);
    }

    if (this.style.endArrow) {
        if (this.style.segmentCount) {
            var distanceAndAngle = XMaps.model.getDistanceAndAngle(this.getPoint(this.pointCount - 1), this.getPoint(this.pointCount - 2));
            distanceAndAngle.distance.value /= this.style.segmentCount;
            from = XMaps.model.getPointAtDistanceAndAngle(this.getPoint(this.pointCount - 1), distanceAndAngle.distance, distanceAndAngle.angle);
        } else {
            from = this.getPoint(this.pointCount - 2);
        }
        to = this.getPoint(this.pointCount - 1);
        this.endArrow = new XArrow(from, to, to, this.style.color, this.style.opacity);
    }
}

XPolyline.prototype.createXPolyline = function(style, textRendering) {
    var polyline = new GPolylineWrapper(this.points, this.nextPointIndexAtLevel, style);
    polyline.style.pattern = this.style.pattern;
    polyline.style.text = this.style.text;
    polyline.style.font = this.style.font;
    polyline.textRendering = textRendering;
    polyline.textEvery = this.style.textEvery;
    polyline.polylineWeight = this.style.weight;
    if (this.style.textFgStyle) {
        polyline.textFgWeight = this.style.textFgStyle.weight;
    }
    return polyline;
}

XPolyline.prototype.createStyle = function(sourceStyle, defaultStyle, baseStyle) {
    if (!sourceStyle) { return null }
    baseStyle = baseStyle || {};
    var destStyle = {};
    for (key in defaultStyle) {
        destStyle[key] = sourceStyle[key] || defaultStyle[key] || baseStyle[key];
    }
    return destStyle;
}

XPolyline.prototype.getPoint = GPolyline.prototype.getPoint;

XPolyline.prototype.initialize = function(map, basePane) {
    this.map = map;
    if (this.polyline) { this.polyline.initialize(map, basePane) }

    if (this.textFgPolyline) { this.textFgPolyline.initialize(map, this.map.polylineTextFgPane) }
    if (this.textBgPolyline) { this.textBgPolyline.initialize(map, this.map.polylineTextBgPane) }

    if (this.beginArrow) { this.beginArrow.initialize(map, basePane) }
    if (this.endArrow) { this.endArrow.initialize(map, basePane) }
}

XPolyline.prototype.remove = function() {
    if (this.polyline) { this.polyline.remove() }

    if (this.textFgPolyline) { this.textFgPolyline.remove() }
    if (this.textBgPolyline) { this.textBgPolyline.remove() }

    if (this.beginArrow) { this.beginArrow.remove() }
    if (this.endArrow) { this.endArrow.remove() }
}

XPolyline.prototype.copy = function() {
	return null
}

XPolyline.prototype.redraw = function(force) {
    if (this.polyline) { this.polyline.redraw(force) }
    if (this.textFgPolyline) { this.textFgPolyline.redraw(force) }
    if (this.textBgPolyline) { this.textBgPolyline.redraw(force) }

    if (this.beginArrow) { this.beginArrow.redraw(force) }
    if (this.endArrow) { this.endArrow.redraw(force) }
}

XPolyline.prototype.getLatitude = function() { return 0 }

XPolyline.prototype.setZIndex = function(zIndexe) {
    if (this.polyline) { this.polyline.setZIndex(zIndexe) }
    if (this.textBgPolyline) { this.textBgPolyline.setZIndex(zIndexe) }
    if (this.textFgPolyline) { this.textFgPolyline.setZIndex(zIndexe) }
    if (this.beginArrow) { this.beginArrow.setZIndex(zIndexe) }
    if (this.endArrow) { this.endArrow.setZIndex(zIndexe) }
}

XPolyline.prototype.display = function(visible) {
    if (this.polyline) { this.polyline.display(visible) }
    if (this.textFgPolyline) { this.textFgPolyline.display(visible) }
    if (this.textBgPolyline) { this.textBgPolyline.display(visible) }

    if (this.beginArrow) { this.beginArrow.display(visible) }
    if (this.endArrow) { this.endArrow.display(visible) }
}


// XPolygon allows you to create filled polygons for use on Google Maps.

 XPolygon.defaultOutlineStyle = {
    color: '#0000ff',
    weight: 5,
    opacity: 1,
    pattern: null,
    segmentsEvery: null,
    beginArrow: false,
    endArrow: false,
    arrowsEvery: null,
    zIndex: 9000,
    font: XMaps.defaultFont,
    text: null,
    textFgStyle: {
        color: '#000000',
        weight: 2,
        opacity: 1,
        pattern: null,
        segmentsEvery: null,
        arrowsEvery: null,
        zIndex: 9020
    },
    textBgStyle: {
        color: null,
        weight: 7,
        opacity: 1,
        pattern: null,
        segmentsEvery: null,
        arrowsEvery: null,
        zIndex: 9010
    }
};

XPolygon.defaultFillStyle = {
    color: '#ffff00',
    weight: null,
    opacity: 0.45,
    zIndex: 9000
};

XPolygon.defaultNumPoints = 6;
XPolygon.defaultStartAngle = 0; // radians


function XPolygon(points, outlineStyle, fillStyle) {
    this.points = null;
    if (points) {
        this.points = [];
        for (var i = 0; i < points.length; i++) {
            this.points.push(new GPoint(points[i].x, points[i].y));
        }
        this.points.push(new GPoint(points[0].x, points[0].y));
    }

    this.outlineStyle = this.createStyle(outlineStyle, XPolygon.defaultOutlineStyle);
    if (outlineStyle) {
        this.outlinePolyline = new XPolyline(this.points, this.outlineStyle);
    }

    this.fillStyle = this.createStyle(fillStyle, XPolygon.defaultFillStyle, this.outlineStyle);
    if (fillStyle) {
        this.fillPolyline = new XPolyline(this.points, this.fillStyle);
        if (XMaps.userAgent.type == XUserAgent.IE) {
            this.fillPolyline.polyline.getVectorsHelper = XPolygon.getIeFillVectorsHelper;
            this.fillPolyline.polyline.createVectorSegments = XPolygon.createIeFillVectorSegments;
        } else {
            this.fillPolyline.polyline.getVectorsHelper = XPolygon.getFillVectorsHelper;
            this.fillPolyline.polyline.getBitmapVectors = XPolygon.getFillBitmapVectors;
        }
    }
}

XPolygon.prototype.createStyle = function(sourceStyle, defaultStyle, baseStyle) {
    if (!sourceStyle) { return null }
    baseStyle = baseStyle || {};
    var destStyle = {};
    for (key in defaultStyle) {
        destStyle[key] = sourceStyle[key] || defaultStyle[key] || baseStyle[key];
    }
    return destStyle;
}

XPolygon.getFillVectorsHelper = function(a, b, c, d, e, f) {
    var h = 1 / XPolyline.vectorScalars[0];
    for(var g=d;g>0;--g) { h *= this.zoomFactor }
    var i = new GBounds();
    i.minX=-Number.MAX_VALUE;//Math.floor((a.minX-h)*100000);
    i.minY=Math.floor((a.minY-h)*100000);
    i.maxX=Number.MAX_VALUE;//Math.ceil((a.maxX+h)*100000);
    i.maxY=Math.ceil((a.maxY+h)*100000);
    var n=b;
    var m;
    var r=new GPoint();
    r.y=this.points[n<<1];
    r.x=this.points[(n<<1)+1];
    var t=new GPoint();
    while((m=this.nextPointIndexAtLevel[d][n])<=c){
        t.y=this.points[m<<1];
        t.x=this.points[(m<<1)+1];
        if(i.containsSegment(r,t)){
            if(d>e){
                this.getVectorsHelper(a,n,m,d-1,e,f)
            }else{
                f.push(r.y*1.0E-5);
                f.push(r.x*1.0E-5);
                f.push(t.y*1.0E-5);
                f.push(t.x*1.0E-5)
            }
        }
        var w=r;
        r=t;
        t=w;
        n=m
    }
}

XPolygon.getFillBitmapVectors = function(points, bitmapVectors, bitmapBounds,
        zoomLevel) {

    if (!bitmapVectors) { bitmapVectors = new Array() }
    if (!bitmapBounds) { bitmapBounds = new GBounds() }
    bitmapBounds.minX = Number.MAX_VALUE;
    bitmapBounds.minY = Number.MAX_VALUE;
    bitmapBounds.maxX = -Number.MAX_VALUE;
    bitmapBounds.maxY = -Number.MAX_VALUE;
    var bitmapZoomLevel = typeof zoomLevel != "undefined" ? zoomLevel : this.map.zoomLevel;

    var bitmapCoords = [];
    var coord = null;
    for (var i = 0; i < points.length; ) {
        var x = points[i++];
        var y = points[i++];
        coord = this.map.spec.getBitmapCoordinate(x, y, bitmapZoomLevel);
        coord.x = Math.round(coord.x);
        coord.y = Math.round(coord.y);
        bitmapCoords.push(coord);
        if (coord.x < bitmapBounds.minX) { bitmapBounds.minX = coord.x }
        if (coord.x > bitmapBounds.maxX) { bitmapBounds.maxX = coord.x }
        if (coord.y < bitmapBounds.minY) { bitmapBounds.minY = coord.y }
        if (coord.y > bitmapBounds.maxY) { bitmapBounds.maxY = coord.y }
    }

    var coordCount = bitmapCoords.length;

    var x1, y1;
    var x2, y2;
    for (var y = bitmapBounds.minY; y <= bitmapBounds.maxY; y += this.weight) {
        var xCoords = [];
        var intersectionCount = 0;
        var index1, index2;

        for (var i = 0; i < coordCount; i++) {
            if (i) {
                index1 = i - 1;
                index2 = i;
            } else {
                index1 = coordCount - 1;
                index2 = 0;
            }

            y1 = bitmapCoords[index1].y;
            y2 = bitmapCoords[index2].y;

            if (y1 < y2) {
                x1 = bitmapCoords[index1].x;
                x2 = bitmapCoords[index2].x;
            } else if (y1 > y2) {
                x2 = bitmapCoords[index1].x;
                x1 = bitmapCoords[index2].x;
                y2 = bitmapCoords[index1].y;
                y1 = bitmapCoords[index2].y;
            } else {
                continue;
            }

            if ((y >= y1) && (y < y2) || ((y == bitmapBounds.maxY) && (y > y1) && (y <= y2))) {
                xCoords[intersectionCount++] = Math.round((y - y1) * (x2 - x1) / (y2 - y1) + x1);
            }
        }

        xCoords.sort(XMaps.integerCompare);

        for (var i = 0; i < (intersectionCount >> 1) << 1; i++) {
            bitmapVectors.push(xCoords[i]);
            bitmapVectors.push(y);
        }
    }

    return bitmapVectors;
}

XPolygon.getIeFillVectorsHelper = function(a, b, c, d, e, f) {
    var h = 1 / XPolyline.vectorScalars[0];
    for(var g=d;g>0;--g) { h *= this.zoomFactor }
    var i = new GBounds();
    i.minX=-Number.MAX_VALUE;//Math.floor((a.minX-h)*100000);
    i.minY=Math.floor((a.minY-h)*100000);
    i.maxX=Number.MAX_VALUE;//Math.ceil((a.maxX+h)*100000);
    i.maxY=Math.ceil((a.maxY+h)*100000);
    var n=b;
    var m;
    var r=new GPoint();
    r.y=this.points[n<<1];
    r.x=this.points[(n<<1)+1];
    var t=new GPoint();
    while((m=this.nextPointIndexAtLevel[d][n])<=c){
        t.y=this.points[m<<1];
        t.x=this.points[(m<<1)+1];
        if(d>e){
            this.getVectorsHelper(a,n,m,d-1,e,f)
        }else{
            f.push(r.y*1.0E-5);
            f.push(r.x*1.0E-5);
            f.push(t.y*1.0E-5);
            f.push(t.x*1.0E-5)
        }
        var w=r;
        r=t;
        t=w;
        n=m
    }
}

XPolygon.createIeFillVectorSegments = function(a, b, c) {
//    P.start("Polyline","createVectorSegments");
    var d=this.getVectors(a,b);
    var e=[];
    var f=new GBounds();
    this.getBitmapVectors(d,e,f);
    if(!c){c=new GBounds()}
    var h=GBounds.intersection(c,f);
    var g;
    if (e.length > 0) {
        var i=this.map.centerBitmap;
        var l=this.map.getDivCoordinate(i.x,i.y,new GPoint(0, 0));
        g=document.createElement("v:shape");
        g.unselectable="on";
        g.fill=false;
        g.filled=false;
        var m=1;
        var s=1;
        g.style.position="absolute";
        g.style.width = XMaps.toPixelString(m);
        g.style.height = XMaps.toPixelString(s);
        g.style.left = XMaps.toPixelString(l.x);
        g.style.top = XMaps.toPixelString(l.y);
        var t=i.x+" "+i.y;
        g.coordorigin=t;
        g.coordsize=m+" "+s;
        g.path=this.getVectorPath(e);

        var stroke = document.createElement("v:stroke");
        stroke.on = false;
        g.appendChild(stroke);

        var fill = document.createElement("v:fill");
        fill.on = true;
        fill.opacity = this.opacity;
        fill.color = this.color;
        g.appendChild(fill)
    }else{
        g=document.createElement("div")
    }
//    P.end("Polyline","createVectorSegments");
    return g
}

XPolygon.deerParkSvgNamespace = "http://www.w3.org/2000/svg";

XPolygon.createDeerParkFillVectorSegments = function(a, b, c) {
//    P.start("Polyline","createVectorSegments");
    var d=this.getVectors(a,b);
    var e=[];
    var f=new GBounds();
    this.getBitmapVectors(d,e,f);
    if(!c){c=new GBounds()}
    var h=GBounds.intersection(c,f);
    var element;
    if (e.length > 0) {
        var i=this.map.centerBitmap;
        var l=this.map.getDivCoordinate(i.x,i.y,new GPoint(0, 0));

        element = document.createElementNS(XPolygon.deerParkSvgNamespace, 'svg');
        element.style.MozUserSelect = 'none';
        element.style.position = 'absolute';
        element.style.width = XMaps.toPixelString(1);
        element.style.height = XMaps.toPixelString(1);
        element.style.left = XMaps.toPixelString(l.x);
        element.style.top = XMaps.toPixelString(l.y);

        var path = document.createElementNS(XPolygon.deerParkSvgNamespace, 'path');
        path.stroke = 'none';
        path.fill = this.color;
        path.opacity = this.opacity;

        path.d = this.getVectorPath(e);
        element.appendChild(path)
    }else{
        element = document.createElement("div")
    }
//    P.end("Polyline","createVectorSegments");
    return element
}

XPolygon.getDeerParkFillVectorPath = function(a) {
    var b=new Array();
    var c;
    var d;
    for(var e=0;e<a.length;){
        var f=a[e++];
        var h=a[e++];
        var g=a[e++];
        var i=a[e++];
        if(h!=c||f!=d){
            b.push("M");b.push(f);b.push(h);b.push("L")
        }
        b.push(g);b.push(i);
        c=i;d=g
    }
    return b.join(" ")
}

XPolygon.prototype.initialize = function(map) {
    this.map = map;
    if (this.outlinePolyline) { this.outlinePolyline.initialize(map) }
    if (this.fillPolyline) { this.fillPolyline.initialize(map, map.mapFillPane) }
}
XPolygon.prototype.remove = function() {
    if (this.outlinePolyline) { this.outlinePolyline.remove() }
    if (this.fillPolyline) { this.fillPolyline.remove() }
}
XPolygon.prototype.copy = function() {
    var polygon = new XPolygon(null);
    polygon.points = this.points;
    polygon.outlineStyle = this.outlineStyle;
    if (this.outlinePolyline) {
        polygon.outlinePolyline = this.outlinePolyline.copy();
    }
    polygon.fillStyle = this.fillStyle;
    if (this.fillPolyline) {
        polygon.fillPolyline = this.fillPolyline.copy();
    }
	return polygon
}

XPolygon.prototype.redraw = function(force) {
    if (this.outlinePolyline) { this.outlinePolyline.redraw(force) }
    if (this.fillPolyline) { this.fillPolyline.redraw(force) }
}

XPolygon.prototype.getLatitude = function() { return 0 }

XPolygon.prototype.setZIndex = function(zIndex) {
    if (this.fillPolyline) { this.fillPolyline.setZIndex(zIndex) }
    if (this.outlinePolyline) { this.outlinePolyline.setZIndex(zIndex) }
}

XPolygon.prototype.display = function(visible) {
    if (this.outlinePolyline) { this.outlinePolyline.display(visible) }
    if (this.fillPolyline) { this.fillPolyline.display(visible) }
}

XPolygon.prototype.getPoint = function(index) { return new GPoint(this.points[index].x, this.points[index].y) }

XPolygon.createRegularPolygonFromRadius = function(center, radius, numPoints, startAngle) {
    numPoints = numPoints || XPolygon.defaultNumPoints;
    startAngle = startAngle || XPolygon.defaultStartAngle;

    var meters = XDistance.resolveToMeters(radius);
    var startRadians = XAngle.resolveToRadians(startAngle);
    startRadians = XAngle.normalizeRadians(startRadians);

    var points = [];
    var latScale = meters / XMaps.model.semimajorAxisInMeters * XAngle.radToDeg;
    var lngScale = latScale / Math.cos(center.y * XAngle.degToRad);

    for (var i = 0; i <= numPoints; i++) {
        var radians = startRadians + 2 * Math.PI * i / numPoints;
        points.push(new GPoint(
                center.x + lngScale * Math.cos(radians),
                center.y + latScale * Math.sin(radians)));
    }
    return points;
}
