/*
    http://www.JSON.org/json2.js
    2008-10-31

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
    charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
    getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
    parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    JSON = {};
}
(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                if (typeof c === 'string') {
                    return c;
                }
                return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// If the object has a dontEnum length property, we'll treat it as an array.

            if (typeof value.length === 'number' &&
                    !value.propertyIsEnumerable('length')) {

// The object is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
})();


/* 
 * MarkerManager, v1.0
 * Copyright (c) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 *
 *
 * Author: Doug Ricket, others
 * 
 * Marker manager is an interface between the map and the user, designed
 * to manage adding and removing many points when the viewport changes.
 *
 *
 * Algorithm: The MM places its markers onto a grid, similar to the map tiles.
 * When the user moves the viewport, the MM computes which grid cells have
 * entered or left the viewport, and shows or hides all the markers in those
 * cells.
 * (If the users scrolls the viewport beyond the markers that are loaded,
 * no markers will be visible until the EVENT_moveend triggers an update.)
 *
 * In practical consequences, this allows 10,000 markers to be distributed over
 * a large area, and as long as only 100-200 are visible in any given viewport,
 * the user will see good performance corresponding to the 100 visible markers,
 * rather than poor performance corresponding to the total 10,000 markers.
 *
 * Note that some code is optimized for speed over space,
 * with the goal of accommodating thousands of markers.
 *
 */



/**
 * Creates a new MarkerManager that will show/hide markers on a map.
 *
 * @constructor
 * @param {Map} map The map to manage.
 * @param {Object} opt_opts A container for optional arguments:
 *   {Number} maxZoom The maximum zoom level for which to create tiles.
 *   {Number} borderPadding The width in pixels beyond the map border,
 *                   where markers should be display.
 *   {Boolean} trackMarkers Whether or not this manager should track marker
 *                   movements.
 */
function MarkerManager(map, opt_opts) {
  var me = this;
  me.map_ = map;
  me.mapZoom_ = map.getZoom();
  me.projection_ = map.getCurrentMapType().getProjection();

  opt_opts = opt_opts || {};
  me.tileSize_ = MarkerManager.DEFAULT_TILE_SIZE_;
  
  var maxZoom = MarkerManager.DEFAULT_MAX_ZOOM_;
  if(opt_opts.maxZoom != undefined) {
    maxZoom = opt_opts.maxZoom;
  }
  me.maxZoom_ = maxZoom;

  me.trackMarkers_ = opt_opts.trackMarkers;

  var padding;
  if (typeof opt_opts.borderPadding == "number") {
    padding = opt_opts.borderPadding;
  } else {
    padding = MarkerManager.DEFAULT_BORDER_PADDING_;
  }
  // The padding in pixels beyond the viewport, where we will pre-load markers.
  me.swPadding_ = new GSize(-padding, padding);
  me.nePadding_ = new GSize(padding, -padding);
  me.borderPadding_ = padding;

  me.gridWidth_ = [];

  me.grid_ = [];
  me.grid_[maxZoom] = [];
  me.numMarkers_ = [];
  me.numMarkers_[maxZoom] = 0;

  GEvent.bind(map, "moveend", me, me.onMapMoveEnd_);

  // NOTE: These two closures provide easy access to the map.
  // They are used as callbacks, not as methods.
  me.removeOverlay_ = function(marker) {
    map.removeOverlay(marker);
    me.shownMarkers_--;
  };
  me.addOverlay_ = function(marker) {
    map.addOverlay(marker);
    me.shownMarkers_++;
  };

  me.resetManager_();
  me.shownMarkers_ = 0;

  me.shownBounds_ = me.getMapGridBounds_();
};

// Static constants:
MarkerManager.DEFAULT_TILE_SIZE_ = 1024;
MarkerManager.DEFAULT_MAX_ZOOM_ = 17;
MarkerManager.DEFAULT_BORDER_PADDING_ = 100;
MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE = 256;


/**
 * Initializes MarkerManager arrays for all zoom levels
 * Called by constructor and by clearAllMarkers
 */ 
MarkerManager.prototype.resetManager_ = function() {
  var me = this;
  var mapWidth = MarkerManager.MERCATOR_ZOOM_LEVEL_ZERO_RANGE;
  for (var zoom = 0; zoom <= me.maxZoom_; ++zoom) {
    me.grid_[zoom] = [];
    me.numMarkers_[zoom] = 0;
    me.gridWidth_[zoom] = Math.ceil(mapWidth/me.tileSize_);
    mapWidth <<= 1;
  }
};

/**
 * Removes all currently displayed markers
 * and calls resetManager to clear arrays
 */
MarkerManager.prototype.clearMarkers = function() {
  var me = this;
  me.processAll_(me.shownBounds_, me.removeOverlay_);
  me.resetManager_();
};


/**
 * Gets the tile coordinate for a given latlng point.
 *
 * @param {LatLng} latlng The geographical point.
 * @param {Number} zoom The zoom level.
 * @param {GSize} padding The padding used to shift the pixel coordinate.
 *               Used for expanding a bounds to include an extra padding
 *               of pixels surrounding the bounds.
 * @return {GPoint} The point in tile coordinates.
 *
 */
MarkerManager.prototype.getTilePoint_ = function(latlng, zoom, padding) {
  var pixelPoint = this.projection_.fromLatLngToPixel(latlng, zoom);
  return new GPoint(
      Math.floor((pixelPoint.x + padding.width) / this.tileSize_),
      Math.floor((pixelPoint.y + padding.height) / this.tileSize_));
};


/**
 * Finds the appropriate place to add the marker to the grid.
 * Optimized for speed; does not actually add the marker to the map.
 * Designed for batch-processing thousands of markers.
 *
 * @param {Marker} marker The marker to add.
 * @param {Number} minZoom The minimum zoom for displaying the marker.
 * @param {Number} maxZoom The maximum zoom for displaying the marker.
 */
MarkerManager.prototype.addMarkerBatch_ = function(marker, minZoom, maxZoom) {
  var mPoint = marker.getPoint();
  // Tracking markers is expensive, so we do this only if the
  // user explicitly requested it when creating marker manager.
  if (this.trackMarkers_) {
    GEvent.bind(marker, "changed", this, this.onMarkerMoved_);
  }

  var gridPoint = this.getTilePoint_(mPoint, maxZoom, GSize.ZERO);

  for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
    var cell = this.getGridCellCreate_(gridPoint.x, gridPoint.y, zoom);
    cell.push(marker);

    gridPoint.x = gridPoint.x >> 1;
    gridPoint.y = gridPoint.y >> 1;
  }
};


/**
 * Returns whether or not the given point is visible in the shown bounds. This
 * is a helper method that takes care of the corner case, when shownBounds have
 * negative minX value.
 *
 * @param {Point} point a point on a grid.
 * @return {Boolean} Whether or not the given point is visible in the currently
 * shown bounds.
 */
MarkerManager.prototype.isGridPointVisible_ = function(point) {
  var me = this;
  var vertical = me.shownBounds_.minY <= point.y &&
      point.y <= me.shownBounds_.maxY;
  var minX = me.shownBounds_.minX;
  var horizontal = minX <= point.x && point.x <= me.shownBounds_.maxX;
  if (!horizontal && minX < 0) {
    // Shifts the negative part of the rectangle. As point.x is always less
    // than grid width, only test shifted minX .. 0 part of the shown bounds.
    var width = me.gridWidth_[me.shownBounds_.z];
    horizontal = minX + width <= point.x && point.x <= width - 1;
  }
  return vertical && horizontal;
}


/**
 * Reacts to a notification from a marker that it has moved to a new location.
 * It scans the grid all all zoom levels and moves the marker from the old grid
 * location to a new grid location.
 *
 * @param {Marker} marker The marker that moved.
 * @param {LatLng} oldPoint The old position of the marker.
 * @param {LatLng} newPoint The new position of the marker.
 */
MarkerManager.prototype.onMarkerMoved_ = function(marker, oldPoint, newPoint) {
  // NOTE: We do not know the minimum or maximum zoom the marker was
  // added at, so we start at the absolute maximum. Whenever we successfully
  // remove a marker at a given zoom, we add it at the new grid coordinates.
  var me = this;
  var zoom = me.maxZoom_;
  var changed = false;
  var oldGrid = me.getTilePoint_(oldPoint, zoom, GSize.ZERO);
  var newGrid = me.getTilePoint_(newPoint, zoom, GSize.ZERO);
  while (zoom >= 0 && (oldGrid.x != newGrid.x || oldGrid.y != newGrid.y)) {
    var cell = me.getGridCellNoCreate_(oldGrid.x, oldGrid.y, zoom);
    if (cell) {
      if (me.removeFromArray(cell, marker)) {
        me.getGridCellCreate_(newGrid.x, newGrid.y, zoom).push(marker);
      }
    }
    // For the current zoom we also need to update the map. Markers that no
    // longer are visible are removed from the map. Markers that moved into
    // the shown bounds are added to the map. This also lets us keep the count
    // of visible markers up to date.
    if (zoom == me.mapZoom_) {
      if (me.isGridPointVisible_(oldGrid)) {
        if (!me.isGridPointVisible_(newGrid)) {
          me.removeOverlay_(marker);
          changed = true;
        }
      } else {
        if (me.isGridPointVisible_(newGrid)) {
          me.addOverlay_(marker);
          changed = true;
        }
      }
    }
    oldGrid.x = oldGrid.x >> 1;
    oldGrid.y = oldGrid.y >> 1;
    newGrid.x = newGrid.x >> 1;
    newGrid.y = newGrid.y >> 1;
    --zoom;
  }
  if (changed) {
    me.notifyListeners_();
  }
};


/**
 * Searches at every zoom level to find grid cell
 * that marker would be in, removes from that array if found.
 * Also removes marker with removeOverlay if visible.
 * @param {GMarker} marker The marker to delete.
 */
MarkerManager.prototype.removeMarker = function(marker) {
  var me = this;
  var zoom = me.maxZoom_;
  var changed = false;
  var point = marker.getPoint();
  var grid = me.getTilePoint_(point, zoom, GSize.ZERO);
  while (zoom >= 0) {
    var cell = me.getGridCellNoCreate_(grid.x, grid.y, zoom);

    if (cell) {
      me.removeFromArray(cell, marker);
    }
    // For the current zoom we also need to update the map. Markers that no
    // longer are visible are removed from the map. This also lets us keep the count
    // of visible markers up to date.
    if (zoom == me.mapZoom_) {
      if (me.isGridPointVisible_(grid)) {
          me.removeOverlay_(marker);
          changed = true;
      } 
    }
    grid.x = grid.x >> 1;
    grid.y = grid.y >> 1;
    --zoom;
  }
  if (changed) {
    me.notifyListeners_();
  }
};


/**
 * Add many markers at once.
 * Does not actually update the map, just the internal grid.
 *
 * @param {Array of Marker} markers The markers to add.
 * @param {Number} minZoom The minimum zoom level to display the markers.
 * @param {Number} opt_maxZoom The maximum zoom level to display the markers.
 */
MarkerManager.prototype.addMarkers = function(markers, minZoom, opt_maxZoom) {
  var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
  for (var i = markers.length - 1; i >= 0; i--) {
    this.addMarkerBatch_(markers[i], minZoom, maxZoom);
  }

  this.numMarkers_[minZoom] += markers.length;
};


/**
 * Returns the value of the optional maximum zoom. This method is defined so
 * that we have just one place where optional maximum zoom is calculated.
 *
 * @param {Number} opt_maxZoom The optinal maximum zoom.
 * @return The maximum zoom.
 */
MarkerManager.prototype.getOptMaxZoom_ = function(opt_maxZoom) {
  return opt_maxZoom != undefined ? opt_maxZoom : this.maxZoom_;
}


/**
 * Calculates the total number of markers potentially visible at a given
 * zoom level.
 *
 * @param {Number} zoom The zoom level to check.
 */
MarkerManager.prototype.getMarkerCount = function(zoom) {
  var total = 0;
  for (var z = 0; z <= zoom; z++) {
    total += this.numMarkers_[z];
  }
  return total;
};


/**
 * Add a single marker to the map.
 *
 * @param {Marker} marker The marker to add.
 * @param {Number} minZoom The minimum zoom level to display the marker.
 * @param {Number} opt_maxZoom The maximum zoom level to display the marker.
 */
MarkerManager.prototype.addMarker = function(marker, minZoom, opt_maxZoom) {
  var me = this;
  var maxZoom = this.getOptMaxZoom_(opt_maxZoom);
  me.addMarkerBatch_(marker, minZoom, maxZoom);
  var gridPoint = me.getTilePoint_(marker.getPoint(), me.mapZoom_, GSize.ZERO);
  if(me.isGridPointVisible_(gridPoint) && 
     minZoom <= me.shownBounds_.z &&
     me.shownBounds_.z <= maxZoom ) {
    me.addOverlay_(marker);
    me.notifyListeners_();
  }
  this.numMarkers_[minZoom]++;
};

/**
 * Returns true if this bounds (inclusively) contains the given point.
 * @param {Point} point  The point to test.
 * @return {Boolean} This Bounds contains the given Point.
 */
GBounds.prototype.containsPoint = function(point) {
  var outer = this;
  return (outer.minX <= point.x &&
          outer.maxX >= point.x &&
          outer.minY <= point.y &&
          outer.maxY >= point.y);
}

/**
 * Get a cell in the grid, creating it first if necessary.
 *
 * Optimization candidate
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @return {Array} The cell in the array.
 */
MarkerManager.prototype.getGridCellCreate_ = function(x, y, z) {
  var grid = this.grid_[z];
  if (x < 0) {
    x += this.gridWidth_[z];
  }
  var gridCol = grid[x];
  if (!gridCol) {
    gridCol = grid[x] = [];
    return gridCol[y] = [];
  }
  var gridCell = gridCol[y];
  if (!gridCell) {
    return gridCol[y] = [];
  }
  return gridCell;
};


/**
 * Get a cell in the grid, returning undefined if it does not exist.
 *
 * NOTE: Optimized for speed -- otherwise could combine with getGridCellCreate_.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @return {Array} The cell in the array.
 */
MarkerManager.prototype.getGridCellNoCreate_ = function(x, y, z) {
  var grid = this.grid_[z];
  if (x < 0) {
    x += this.gridWidth_[z];
  }
  var gridCol = grid[x];
  return gridCol ? gridCol[y] : undefined;
};


/**
 * Turns at geographical bounds into a grid-space bounds.
 *
 * @param {LatLngBounds} bounds The geographical bounds.
 * @param {Number} zoom The zoom level of the bounds.
 * @param {GSize} swPadding The padding in pixels to extend beyond the
 * given bounds.
 * @param {GSize} nePadding The padding in pixels to extend beyond the
 * given bounds.
 * @return {GBounds} The bounds in grid space.
 */
MarkerManager.prototype.getGridBounds_ = function(bounds, zoom, swPadding,
                                                  nePadding) {
  zoom = Math.min(zoom, this.maxZoom_);
  
  var bl = bounds.getSouthWest();
  var tr = bounds.getNorthEast();
  var sw = this.getTilePoint_(bl, zoom, swPadding);
  var ne = this.getTilePoint_(tr, zoom, nePadding);
  var gw = this.gridWidth_[zoom];
  
  // Crossing the prime meridian requires correction of bounds.
  if (tr.lng() < bl.lng() || ne.x < sw.x) {
    sw.x -= gw;
  }
  if (ne.x - sw.x  + 1 >= gw) {
    // Computed grid bounds are larger than the world; truncate.
    sw.x = 0;
    ne.x = gw - 1;
  }
  var gridBounds = new GBounds([sw, ne]);
  gridBounds.z = zoom;
  return gridBounds;
};


/**
 * Gets the grid-space bounds for the current map viewport.
 *
 * @return {Bounds} The bounds in grid space.
 */
MarkerManager.prototype.getMapGridBounds_ = function() {
  var me = this;
  return me.getGridBounds_(me.map_.getBounds(), me.mapZoom_,
                           me.swPadding_, me.nePadding_);
};


/**
 * Event listener for map:movend.
 * NOTE: Use a timeout so that the user is not blocked
 * from moving the map.
 *
 */
MarkerManager.prototype.onMapMoveEnd_ = function() {
  var me = this;
  me.objectSetTimeout_(this, this.updateMarkers_, 0);
};


/**
 * Call a function or evaluate an expression after a specified number of
 * milliseconds.
 *
 * Equivalent to the standard window.setTimeout function, but the given
 * function executes as a method of this instance. So the function passed to
 * objectSetTimeout can contain references to this.
 *    objectSetTimeout(this, function() { alert(this.x) }, 1000);
 *
 * @param {Object} object  The target object.
 * @param {Function} command  The command to run.
 * @param {Number} milliseconds  The delay.
 * @return {Boolean}  Success.
 */
MarkerManager.prototype.objectSetTimeout_ = function(object, command, milliseconds) {
  return window.setTimeout(function() {
    command.call(object);
  }, milliseconds);
};


/**
 * Refresh forces the marker-manager into a good state.
 * <ol>
 *   <li>If never before initialized, shows all the markers.</li>
 *   <li>If previously initialized, removes and re-adds all markers.</li>
 * </ol>
 */
MarkerManager.prototype.refresh = function() {
  var me = this;
  if (me.shownMarkers_ > 0) {
    me.processAll_(me.shownBounds_, me.removeOverlay_);
  }
  me.processAll_(me.shownBounds_, me.addOverlay_);
  me.notifyListeners_();
};


/**
 * After the viewport may have changed, add or remove markers as needed.
 */
MarkerManager.prototype.updateMarkers_ = function() {
  var me = this;
  me.mapZoom_ = this.map_.getZoom();
  var newBounds = me.getMapGridBounds_();
  
  // If the move does not include new grid sections,
  // we have no work to do:
  if (newBounds.equals(me.shownBounds_) && newBounds.z == me.shownBounds_.z) {
    return;
  }

  if (newBounds.z != me.shownBounds_.z) {
    me.processAll_(me.shownBounds_, me.removeOverlay_);
    me.processAll_(newBounds, me.addOverlay_);
  } else {
    // Remove markers:
    me.rectangleDiff_(me.shownBounds_, newBounds, me.removeCellMarkers_);

    // Add markers:
    me.rectangleDiff_(newBounds, me.shownBounds_, me.addCellMarkers_);
  }
  me.shownBounds_ = newBounds;

  me.notifyListeners_();
};


/**
 * Notify listeners when the state of what is displayed changes.
 */
MarkerManager.prototype.notifyListeners_ = function() {
  GEvent.trigger(this, "changed", this.shownBounds_, this.shownMarkers_);
};


/**
 * Process all markers in the bounds provided, using a callback.
 *
 * @param {Bounds} bounds The bounds in grid space.
 * @param {Function} callback The function to call for each marker.
 */
MarkerManager.prototype.processAll_ = function(bounds, callback) {
  for (var x = bounds.minX; x <= bounds.maxX; x++) {
    for (var y = bounds.minY; y <= bounds.maxY; y++) {
      this.processCellMarkers_(x, y,  bounds.z, callback);
    }
  }
};


/**
 * Process all markers in the grid cell, using a callback.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 * @param {Function} callback The function to call for each marker.
 */
MarkerManager.prototype.processCellMarkers_ = function(x, y, z, callback) {
  var cell = this.getGridCellNoCreate_(x, y, z);
  if (cell) {
    for (var i = cell.length - 1; i >= 0; i--) {
      callback(cell[i]);
    }
  }
};


/**
 * Remove all markers in a grid cell.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 */
MarkerManager.prototype.removeCellMarkers_ = function(x, y, z) {
  this.processCellMarkers_(x, y, z, this.removeOverlay_);
};


/**
 * Add all markers in a grid cell.
 *
 * @param {Number} x The x coordinate of the cell.
 * @param {Number} y The y coordinate of the cell.
 * @param {Number} z The z coordinate of the cell.
 */
MarkerManager.prototype.addCellMarkers_ = function(x, y, z) {
  this.processCellMarkers_(x, y, z, this.addOverlay_);
};


/**
 * Use the rectangleDiffCoords function to process all grid cells
 * that are in bounds1 but not bounds2, using a callback, and using
 * the current MarkerManager object as the instance.
 *
 * Pass the z parameter to the callback in addition to x and y.
 *
 * @param {Bounds} bounds1 The bounds of all points we may process.
 * @param {Bounds} bounds2 The bounds of points to exclude.
 * @param {Function} callback The callback function to call
 *                   for each grid coordinate (x, y, z).
 */
MarkerManager.prototype.rectangleDiff_ = function(bounds1, bounds2, callback) {
  var me = this;
  me.rectangleDiffCoords(bounds1, bounds2, function(x, y) {
    callback.apply(me, [x, y, bounds1.z]);
  });
};


/**
 * Calls the function for all points in bounds1, not in bounds2
 *
 * @param {Bounds} bounds1 The bounds of all points we may process.
 * @param {Bounds} bounds2 The bounds of points to exclude.
 * @param {Function} callback The callback function to call
 *                   for each grid coordinate.
 */
MarkerManager.prototype.rectangleDiffCoords = function(bounds1, bounds2, callback) {
  var minX1 = bounds1.minX;
  var minY1 = bounds1.minY;
  var maxX1 = bounds1.maxX;
  var maxY1 = bounds1.maxY;
  var minX2 = bounds2.minX;
  var minY2 = bounds2.minY;
  var maxX2 = bounds2.maxX;
  var maxY2 = bounds2.maxY;

  for (var x = minX1; x <= maxX1; x++) {  // All x in R1
    // All above:
    for (var y = minY1; y <= maxY1 && y < minY2; y++) {  // y in R1 above R2
      callback(x, y);
    }
    // All below:
    for (var y = Math.max(maxY2 + 1, minY1);  // y in R1 below R2
         y <= maxY1; y++) {
      callback(x, y);
    }
  }

  for (var y = Math.max(minY1, minY2);
       y <= Math.min(maxY1, maxY2); y++) {  // All y in R2 and in R1
    // Strictly left:
    for (var x = Math.min(maxX1 + 1, minX2) - 1;
         x >= minX1; x--) {  // x in R1 left of R2
      callback(x, y);
    }
    // Strictly right:
    for (var x = Math.max(minX1, maxX2 + 1);  // x in R1 right of R2
         x <= maxX1; x++) {
      callback(x, y);
    }
  }
};


/**
 * Removes value from array. O(N).
 *
 * @param {Array} array  The array to modify.
 * @param {any} value  The value to remove.
 * @param {Boolean} opt_notype  Flag to disable type checking in equality.
 * @return {Number}  The number of instances of value that were removed.
 */
MarkerManager.prototype.removeFromArray = function(array, value, opt_notype) {
  var shift = 0;
  for (var i = 0; i < array.length; ++i) {
    if (array[i] === value || (opt_notype && array[i] == value)) {
      array.splice(i--, 1);
      shift++;
    }
  }
  return shift;
};


// Text markers code taken from: http://uwmike.com/maps/manhattan2/labeled_marker.js.source
/* Constructor for an extended Marker class */
function LabeledMarker(latlng, options){
    this.latlng = latlng;
    this.labelText = options.labelText || "";
    this.labelClass = options.labelClass || "markerLabel";
    this.labelOffset = options.labelOffset || new GSize(0, 0);
    GMarker.apply(this, arguments);
}


/* It's a limitation of JavaScript inheritance that we can't conveniently
   extend GMarker without having to run its constructor. In order for the
   constructor to run, it requires some dummy GLatLng. */
LabeledMarker.prototype = new GMarker(new GLatLng(0, 0));


// Creates the text div that goes over the marker.
LabeledMarker.prototype.initialize = function(map) {
        // Do the GMarker constructor first.
        GMarker.prototype.initialize.call(this, map);

        var div = document.createElement("div");
        div.className = this.labelClass;
        div.innerHTML = this.labelText;
        div.style.position = "absolute";
        map.getPane(G_MAP_MARKER_PANE).appendChild(div);
        
        this.map = map;
        this.div = div;
}

// Redraw the rectangle based on the current projection and zoom level
LabeledMarker.prototype.redraw = function(force) {
        GMarker.prototype.redraw.call(this, map);
        
        // We only need to do anything if the coordinate system has changed
        if (!force) return;
        
        // Calculate the DIV coordinates of two opposite corners of our bounds to
        // get the size and position of our rectangle
        var p = this.map.fromLatLngToDivPixel(this.latlng);
        var z = GOverlay.getZIndex(this.latlng.lat());
        
        // Now position our DIV based on the DIV coordinates of our bounds
        this.div.style.left = (p.x + this.labelOffset.width) + "px";
        this.div.style.top = (p.y + this.labelOffset.height) + "px";
        this.div.style.zIndex = z + 1; // in front of the marker
}

// Remove the main DIV from the map pane
LabeledMarker.prototype.remove = function() {
  this.div.parentNode.removeChild(this.div);
  this.div = null;
  GMarker.prototype.remove.call(this);
}

//
// Create our custom control logo
//
function WyriMapsLogo(){}

WyriMapsLogo.prototype = new GControl();

WyriMapsLogo.prototype.initialize = function(a) {
	var b = document.createElement("div");

	b.id='GmapUploaderBug';
	b.style.zIndex=25500;
	b.innerHTML='+ <a href="http://wyrimaps.net"><img src="http://servcontent.wyrihaximus.net/wyrimaps.net/WyriMaps_small_map.png" style="border:0px;" /></a>';
	a.getContainer().appendChild(b);
	var c=1;
	var d=document.getElementById(b.id);
	if(typeof(d.style.filter)=='string'){d.style.filter='alpha(opacity:'+c*100+')';}
	if(typeof(d.style.KHTMLOpacity)=='string'){d.style.KHTMLOpacity=c;}
	if(typeof(d.style.MozOpacity)=='string'){d.style.MozOpacity=c;}
	if(typeof(d.style.opacity)=='string'){d.style.opacity=c;}

	a.getContainer().appendChild(b);
	return b;
};

WyriMapsLogo.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(65, 5));
};
//
// Create our custom link location control 
//
function WyriMapsLocationLink(){}

WyriMapsLocationLink.prototype = new GControl();

WyriMapsLocationLink.prototype.initialize = function(a) {
	var b = document.createElement("div");

	b.id='GmapUploaderBug';
	b.style.zIndex=25500;
	b.innerHTML='<span  style="display: inline; font: 12px arial; text-decoration: none; padding: 8px; border: 1px dotted black; color: black; background-color: #fff;"><span id="coord_x"></span> by <span id="coord_y"></span> Zoom: <span id="coord_zoom"></span> Share: <a href="http://wyrimaps.net/wow" id="linkto"><img src="http://tbn0.google.com/images?q=tbn:dNChSx4edNe-UM:http://www.mattwrightpr.com/wp-content/uploads/2008/06/symbolic_link_icon.png" style="border:0px;" /></a></span>';
	a.getContainer().appendChild(b);
	var c=1;
	var d=document.getElementById(b.id);
	if(typeof(d.style.filter)=='string'){d.style.filter='alpha(opacity:'+c*100+')';}
	if(typeof(d.style.KHTMLOpacity)=='string'){d.style.KHTMLOpacity=c;}
	if(typeof(d.style.MozOpacity)=='string'){d.style.MozOpacity=c;}
	if(typeof(d.style.opacity)=='string'){d.style.opacity=c;}

	a.getContainer().appendChild(b);
	return b;
};

WyriMapsLocationLink.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(65, 5));
};

// 
// The main map control
// 
function WyriMapsFrameWork() {}
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var maxOverlays = 5000;
var currentOverlays = 0;
var waitTime = 50;
var waitCount = 0;
var current_server = 0;
var current_server_small = 0;
// Zoom level 1 - 9 servers
var number_of_servers = 0;
var myservers=[];
myservers[number_of_servers++]="mt1.servcontent.wyrihaximus.net";
myservers[number_of_servers++]="mt1.wyrimaps.net";
myservers[number_of_servers++]="mt1.servcontent3.wyrihaximus.net";
myservers[number_of_servers++]="mt1.servcontent3.wyrihaximus.net";
myservers[number_of_servers++]="mt1.servcontent3.wyrihaximus.net";
myservers[number_of_servers++]="mt1.servcontent3.wyrihaximus.net";
myservers[number_of_servers++]="mt1.servcontent3.wyrihaximus.net";
myservers[number_of_servers++]="mt1.servcontent3.wyrihaximus.net";
myservers[number_of_servers++]="mt1.servcontent3.wyrihaximus.net";
// Zoom level 1 - 8 servers
var number_of_servers_small = number_of_servers;
myservers_small = myservers;
myservers_small[number_of_servers_small++]="mt1.servcontent.wyrihaximus.net";
/*myservers.sort(function(){
	return (Math.round(Math.random())*2.5);
});*/
//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/array/shuffle [v1.0]

shuffle = function(o){ //v1.0
   for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
   return o;
};

myservers_small = shuffle(myservers_small);
myservers = shuffle(myservers);

WyriMapsFrameWork.prototype.config = function(name,value) {
	this.settings[name] = value;
};
WyriMapsFrameWork.prototype.decode64 = function(input) {
   var output = "";
   var chr1, chr2, chr3;
   var enc1, enc2, enc3, enc4;
   var i = 0;
 
   // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
   input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
 
   do {
      enc1 = keyStr.indexOf(input.charAt(i++));
      enc2 = keyStr.indexOf(input.charAt(i++));
      enc3 = keyStr.indexOf(input.charAt(i++));
      enc4 = keyStr.indexOf(input.charAt(i++));
 
      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;
 
      output = output + String.fromCharCode(chr1);
 
      if (enc3 != 64) {
         output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
         output = output + String.fromCharCode(chr3);
      }
   } while (i < input.length);
 
   return output;
};
WyriMapsFrameWork.prototype.getURLParam = function(strParamName){
	var strReturn = "";
	var strHref = window.location.href;
	if ( strHref.indexOf("?") > -1 ){
		var strQueryString = strHref.substr(strHref.indexOf("?")).toLowerCase();
		var aQueryString = strQueryString.split("&");
		for ( var iParam = 0; iParam < aQueryString.length; iParam++ ){
			if ( aQueryString[iParam].indexOf(strParamName + "=") > -1 ){
				var aParam = aQueryString[iParam].split("=");
				strReturn = aParam[1];
				break;
			}
		}
	}
	return strReturn;
};
WyriMapsFrameWork.prototype.naptime = function(naptime){
	
};
WyriMapsFrameWork.prototype.setup = function(){
	// 
	// Init the differently used icons (for now just some round dot :P)
	//
	// Create our "tiny" marker icon
	this.tinyIcon = new GIcon();
	this.tinyIcon.image = "http://servcontent.wyrihaximus.net/wyrimaps.net/wow_icons/133.png";
	this.tinyIcon.shadow = "http://servcontent.wyrihaximus.net/wyrimaps.net/wow_icons/133.png";
	this.tinyIcon.iconSize = new GSize(11, 11);
	this.tinyIcon.shadowSize = new GSize(11, 11);
	this.tinyIcon.iconAnchor = new GPoint(5, 5);
	this.tinyIcon.iconWindowAnchor = new GPoint(5, 5);
	// Create our "hidden" marker icon
	this.hiddenIcon = new GIcon();
	this.hiddenIcon.image = "http://tbn0.google.com/images?q=tbn:eIZtiAVGFbUE_M:http://www.sonoma-county.org/board/images/empty.gif";
	this.hiddenIcon.shadow = "http://tbn0.google.com/images?q=tbn:eIZtiAVGFbUE_M:http://www.sonoma-county.org/board/images/empty.gif";
	this.hiddenIcon.iconSize = new GSize(1, 1);
	this.hiddenIcon.shadowSize = new GSize(1, 1);
	this.hiddenIcon.iconAnchor = new GPoint(1, 1);
	this.hiddenIcon.iconWindowAnchor = new GPoint(1, 1);
	
	this.settings = [];
	this.Overlays = [];
	this.tileOverlays = [];
	this.tileOverlaysCount = 0;
};
WyriMapsFrameWork.prototype.addWaitCycle = function(){
	waitCount = (waitCount + 1);
	return (waitCount * waitTime);
};
WyriMapsFrameWork.prototype.removeWaitCycle = function(){
	waitCount = (waitCount - 1);
};
WyriMapsFrameWork.prototype.getNextMarker = function(point,ops){
	currentOverlays = (currentOverlays + 1);
	if(currentOverlays>maxOverlays)
	{
		currentOverlays = 0;
	}
	try {
		this.map.removeOverlay(this.Overlays[currentOverlays]);
	}
	catch(e) {
		
	}
	this.Overlays[currentOverlays] = new GMarker(point,ops);
	return currentOverlays;
};
WyriMapsFrameWork.prototype.getMarker = function(index){
	return this.Overlays[index];
};
WyriMapsFrameWork.prototype.addMarker = function(x,y,opts,refresh){
	wait = this.addWaitCycle();
	setTimeout(function(){
		markerid = WyriMap.getNextMarker(new GLatLng(x, y),{icon:WyriMap.tinyIcon});
		marker = WyriMap.getMarker(markerid);
		GEvent.addListener(marker, "click", function() {
			win = new Ext.Window({
			    	layout      : 'fit',
                		width       : 200,
                		height      : 200,
                		closeAction :'hide',
                		plain       : true,
				items     : [{
					title    : 'Screenie',
                			html     : WyriMap.decode64(opts.popup)
				}]
            		});
	    		win.show();
		});
		WyriMap.mgr.addMarker(marker,opts.minZoom,opts.maxZoom);
		WyriMap.removeWaitCycle();
		if(refresh)
		{
			WyriMap.mgr.refresh();
		}
	},wait);
};
WyriMapsFrameWork.prototype.requestOverlays = function(z,x,y,type) {
	clearTimeout(this.timeoutId);
	this.tileOverlays[this.tileOverlaysCount++] = [z,x,y];
	this.timeoutId = setTimeout( function () {
		WyriMap.getOverlays(type,z);
	}, 1000);
};
WyriMapsFrameWork.prototype.getOverlays = function(type) {
	tmp = this.tileOverlays;
	tmpCount = this.tileOverlaysCount;
	WyriMap.tileOverlays = [];
	WyriMap.tileOverlaysCount = 0;
	// Create Google XmlHttpRequest object
	var req = GXmlHttp.create(); 
	req.open("POST", "/mapobject/json/" + this.settings['wyrimaps_net_ini_shortname'], true);
	req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 
	req.onreadystatechange = function() {
		if (req.readyState == 4) {
			if (req.status == 200) { 
				json = JSON.parse(req.responseText);
				if(json.status==1)
				{
	 				WyriMap.placeOverlays(json);
 				}
 				else
 				{
	 				//alert('failed');
 				}
			}
		}
	};
	//req.send("json=" + JSON.stringify(tmp) + "&type=" + type);
};
WyriMapsFrameWork.prototype.placeOverlays = function(json) {
		for(var i = 0; i < json.data_count; i++) {
			for( var j in json.data[i].screenies )
			{
				json.data[i].screenies[j].opts.minZoom = this.map.getZoom();
				json.data[i].screenies[j].opts.maxZoom = this.map.getZoom();
				this.addMarker(json.data[i].screenies[j].lat,json.data[i].screenies[j].lng,json.data[i].screenies[j].opts,false);
			} 
		}
		this.mgr.refresh();
};
WyriMapsFrameWork.prototype.init_map = function(){
	//
	// Check for Google Gears to use it for json/image/user defined Overlays cache
	// This is just for trying out, this could increase over all perforcemence or completely crash is but we'll see :)
	// 
	/*
	try {
		if(wyrimaps_net_ini_googlegears == "undefined") {}
	}
	catch(e) {
		wyrimaps_net_ini_googlegears = false;
	}
	wyrimaps_net_ini_googlegears = true;
	if(wyrimaps_net_ini_googlegears && (window.google || google.gears))
	{
		try {
			var db = google.gears.factory.create('beta.database');
			db.open('database-wyrimaps-' + wyrimaps_net_ini_shortname);
			var wyrimaps_net_ini_googlegears_use = true;
		} catch(e) {
			var wyrimaps_net_ini_googlegears_use = false;
		}
	}
	*/
	// 
	// 
	// Create map instance
	//
	var copyrightCollection = new GCopyrightCollection("<font color=\"red\">(C)</font>");
	copyrightCollection.addCopyright(new GCopyright(1, new GLatLngBounds(new GLatLng(-90,-180),new GLatLng(90,180)), 1, "<a href=\"http://wyrimaps.net/\">WyriMaps.net</a>"));
	var i = 2;
	for( var j in this.settings['wyrimaps_net_ini_copyright'] )
	{
		copyrightCollection.addCopyright(new GCopyright(i, new GLatLngBounds(new GLatLng(this.settings['wyrimaps_net_ini_copyright'][j][0],this.settings['wyrimaps_net_ini_copyright'][j][1]),new GLatLng(this.settings['wyrimaps_net_ini_copyright'][j][2],this.settings['wyrimaps_net_ini_copyright'][j][3])), 1, this.settings['wyrimaps_net_ini_copyright'][j][4]));
	}
	CustomGetTileUrl=function(a,b){
		// 
		// The following lines are going to be the object requesters
		// 
		WyriMap.requestOverlays(b,a.x,a.y,'markers');
		//WyriMap.requestOverlays(b,a.x,a.y,'naming');
		//WyriMap.requestOverlays(b,a.x,a.y,'travel');
		//WyriMap.requestOverlays(b,a.x,a.y,'instance');
		//WyriMap.requestOverlays(b,a.x,a.y,'raid');
		// 
		// Now return the image url for this tile
		//
		if(b==9)
		{
			current_server = (current_server+1) % number_of_servers;
			var f = "http://" + myservers[current_server] + '/' + WyriMap.settings['wyrimaps_net_ini_dir'] + '/' + b + '_' + a.x + '_' + a.y + '.png';
		}
		else
		{
			current_server_small = (current_server_small+1) % number_of_servers_small;
			var f = "http://" + myservers_small[current_server_small] + '/' + WyriMap.settings['wyrimaps_net_ini_dir'] + '/' + b + '_' + a.x + '_' + a.y + '.png';
		}
		return f;
	}
	var tilelayers = [new GTileLayer(copyrightCollection,2,9)];
	tilelayers[0].getTileUrl = CustomGetTileUrl;
	var custommap = new GMapType(tilelayers, new GMercatorProjection(10), "WyriHaximus.net");
	this.map = new GMap2(document.getElementById(this.settings['wyrimaps_net_ini_div'])/*,[custommap]*/);
	this.mgr = new GMarkerManager(this.map);
	this.map.setMapType(custommap);
	this.map.addControl(new GLargeMapControl());
	this.map.addControl(new WyriMapsLogo());
	this.map.addControl(new WyriMapsLocationLink());
	this.map.enableContinuousZoom();
	if (this.getURLParam("lat") != "") {
		this.map.setCenter(new GLatLng(parseFloat(this.getURLParam("lat")), parseFloat(this.getURLParam("lng")), true ),parseInt(this.getURLParam("zoom")));
		document.getElementById("coord_x").innerHTML = parseFloat(this.getURLParam("lng"));
		document.getElementById("coord_y").innerHTML = parseFloat(this.getURLParam("lat"));
		document.getElementById("coord_zoom").innerHTML = parseInt(this.getURLParam("zoom"));
	} else {
		this.map.setCenter(new GLatLng(this.settings['wyrimaps_net_ini_lat'],this.settings['wyrimaps_net_ini_lng'], true), this.settings['wyrimaps_net_ini_zoom']);
		document.getElementById("coord_x").innerHTML = this.settings['wyrimaps_net_ini_lng'];
		document.getElementById("coord_y").innerHTML = this.settings['wyrimaps_net_ini_lat'];
		document.getElementById("coord_zoom").innerHTML = this.settings['wyrimaps_net_ini_zoom'];
	}
	if(this.settings['wyrimaps_net_ini_autoupdate'])
	{
		function update_stuff(end)
		{
			var center = WyriMap.map.getCenter();
			var newLinkTo = "http://wyrimaps.net/" + WyriMap.settings['wyrimaps_net_ini_shortname'] + "?lng=" + center.x + "&lat=" + center.y + "&zoom=" + WyriMap.map.getZoom();
			document.getElementById('linkto').href = newLinkTo;
			document.getElementById('coord_x').innerHTML = center.x;
			document.getElementById('coord_x').innerHTML = center.y;
			document.getElementById('coord_zoom').innerHTML = WyriMap.map.getZoom();
		
		}
		function update_stuff_end()
		{
			update_stuff(true);
		}
		function update_stuff_norm()
		{
			update_stuff(false);
		}
		GEvent.addListener(this.map, "moveend", update_stuff_end);
		GEvent.addListener(this.map, "move", update_stuff_norm);
		update_stuff();
	}
	for( var j in this.settings['wyrimaps_net_ini_markers'] )
	{
		var x = this.settings['wyrimaps_net_ini_markers'][j];
		this.addMarker(x[0],x[1],{popup:x[3],minZoom:1,maxZoom:9},false);
	}
	this.mgr.refresh();
};

WyriMap = new WyriMapsFrameWork();