VMapObject.prototypes = [];

var USER_DATA = {

    Browser: {
        KHTML:   /Konqueror|KHTML/.test(navigator.userAgent) &&
                 !/Apple/.test(navigator.userAgent),
        Chrome:  /Chrome/.test(navigator.userAgent),
        Safari:  /KHTML/.test(navigator.userAgent) &&
                 /Apple/.test(navigator.userAgent),
        Opera:   !!window.opera,
        Mozilla: /Mozilla/.test(navigator.userAgent),
        MSIE:    !!(window.attachEvent && !window.opera),
        MSIE_6:  /msie 6/i.test(navigator.userAgent),
        Gecko:   /Gecko/.test(navigator.userAgent) &&
                 !/Konqueror|KHTML/.test(navigator.userAgent)
    },
    
    OS: {
        Windows: navigator.platform.indexOf("Win") > -1,
        Mac: navigator.platform.indexOf("Mac") > -1,
        Linux: navigator.platform.indexOf("Linux") > -1
    }
}

// Patch Array#push for IE5
if (!Array.prototype.push) {
    Array.prototype.push = function(item) {
        return this[this.length] = item;
    }
}
// Add 1.6 style Array#forEach for older browsers
if (!Array.prototype.forEach) {
    Array.prototype.forEach = function(func, scope) {
        scope = scope || this;
        for (var i = 0; i < this.length; i++) {
            func.call(scope, this[i], i, this);
        }
    }
}
// For IE
if(!Array.indexOf){
    Array.prototype.indexOf = function(obj) {
        for (var i = 0; i < this.length; i++) {
            if (this[i] == obj) {
                return i;
            }
        }
        return -1;
    }
}

var _VISICOM_MapGlobalContext;

function VisicomCommons() { }

VisicomCommons.API_URL = 'http://maps.visicom.ua/api/2.0.0//';
VisicomCommons.MAP_NAME = 'world';

VisicomCommons.MSG_SEARCH_ADDRESS = 'Поиск по адресной базе';
VisicomCommons.MSG_MORE_2 = 'Введите более 2-х символов...';

VisicomCommons.MSG_PLEASE_WAIT = [];
VisicomCommons.MSG_PLEASE_WAIT["ua"] = 'Зачекайте, іде завантаження...';
VisicomCommons.MSG_PLEASE_WAIT["ru"] = 'Подождите, идет загрузка...';
VisicomCommons.MSG_PLEASE_WAIT["en"] = 'Please wait...';

VisicomCommons.MSG_NOT_FOUND = [];
VisicomCommons.MSG_NOT_FOUND["ua"] = 'Нічого не знайдено.';
VisicomCommons.MSG_NOT_FOUND["ru"] = 'Нет результатов.';
VisicomCommons.MSG_NOT_FOUND["en"] = 'Not found.';

VisicomCommons.MSG_SETTLEMENT = [];
VisicomCommons.MSG_SETTLEMENT["ua"] = 'Нічого не знайдено.';
VisicomCommons.MSG_SETTLEMENT["ru"] = 'Нет результатов.';
VisicomCommons.MSG_SETTLEMENT["en"] = 'Not found.';

VisicomCommons.MSG_STREET = [];
VisicomCommons.MSG_STREET["ua"] = 'Нічого не знайдено.';
VisicomCommons.MSG_STREET["ru"] = 'Нет результатов.';
VisicomCommons.MSG_STREET["en"] = 'Not found.';

VisicomCommons.ZINDEX_SMOOTH_LAYER = '200';
VisicomCommons.ZINDEX_TILE = '210';
VisicomCommons.ZINDEX_MAP = '220';
VisicomCommons.ZINDEX_POLYLINE = '400';
VisicomCommons.ZINDEX_MARKERS = '500';
VisicomCommons.ZINDEX_MARKERS_HOVER = '520';
VisicomCommons.ZINDEX_INFOWINDOW_SHADOW = '580';
VisicomCommons.ZINDEX_LABEL = '585';
VisicomCommons.ZINDEX_INFOWINDOW = '590';
VisicomCommons.ZINDEX_INFOWINDOW_HOVER = '650';
VisicomCommons.ZINDEX_HINT = '670';
VisicomCommons.ZINDEX_LOGO = '700';
VisicomCommons.ZINDEX_CONTROLS = '700';

VisicomCommons.OPEN_HAND_CURSOR = 'url("' + VisicomCommons.API_URL + 'images/cursor/openhand.cur.ico"), default';
VisicomCommons.CLOSED_HAND_CURSOR = 'url("' + VisicomCommons.API_URL + 'images/cursor/closedhand.cur.ico"), default';


// ------------------------------------------
// Работа с проекцией
// ------------------------------------------

VisicomCommons.PreferableScale = 0.1;
VisicomCommons.m_EarthRadius = 6378137;
VisicomCommons.m_scaleFactor = VisicomCommons.m_EarthRadius / VisicomCommons.PreferableScale;

VisicomCommons.convertToGeoProjection = function(x, y) {
    var lng = VisicomCommons.radToDeg(x / VisicomCommons.m_scaleFactor);
    y = y / VisicomCommons.m_scaleFactor;
    var lat = VisicomCommons.radToDeg( Math.atan((Math.exp(y) - Math.exp(-y)) / 2 ));

    return {lng: lng, lat: lat};
}

VisicomCommons.convertFromGeoProjection = function(coords) {
    var x = VisicomCommons.degToRad(coords.lng) * VisicomCommons.m_scaleFactor;
    
    var a_latInRad = VisicomCommons.degToRad(coords.lat);
    var y = Math.log(Math.tan(a_latInRad) + 1 / Math.cos(a_latInRad)) * VisicomCommons.m_scaleFactor;
    
    return {x: x, y: y};
}

VisicomCommons.radToDeg = function(rad) {
    return (rad * 180) / Math.PI;
}

VisicomCommons.degToRad = function(deg) {
    return (Math.PI * deg) / 180;
}

VisicomCommons.a_BAxis = (1.0 - 1 / 298.257223563) * 6378137.0;

VisicomCommons.GeoCalcLen = function(points) {
    var a_Point1 = points[0];
    var dYrad1 = VisicomCommons.degToRad(a_Point1.lat);
    var dCosY1 = Math.cos(dYrad1);
    
    var dDlong;
    var dResLen = 0;

    for (var i = 1; i < points.length; i++) {
        a_Point1 = points[i - 1];
        var a_Point2 = points[i];
        if (a_Point2.lat == 90.0 || a_Point2.lat == -90.0) dDlong = 0;
        else {
            dDlong = a_Point2.lng - a_Point1.lng;
            if (dDlong > 180) dDlong = 360 - dDlong;
            else if (dDlong < -180) dDlong = 360 + dDlong;
                 dDlong = VisicomCommons.degToRad(dDlong);
        }
        
        var dYrad2 = VisicomCommons.degToRad(a_Point2.lat);
        var dDlat = dYrad2 - dYrad1;
        var dSin2lat = Math.sin(dDlat/2);
        var dSin2long = Math.sin(dDlong/2);
        var dCosY2 = Math.cos(dYrad2);
        dSin2lat = Math.sqrt(dSin2lat * dSin2lat + dSin2long * dSin2long * dCosY1 * dCosY2);
        if (dSin2lat > 1.0) dSin2lat = 1.0;
        dResLen += 2.0 *  Math.asin(dSin2lat);

        dYrad1 = dYrad2;
        dCosY1 = dCosY2;
    }

    var a_EarthRadius = Math.pow(VisicomCommons.m_EarthRadius, (1.0/3));
    a_EarthRadius *= a_EarthRadius * Math.pow(VisicomCommons.a_BAxis, (1.0/3));

    return a_EarthRadius * dResLen;
}

function _createVisicomMap() {
    try {
        createVisicomMap();
    } catch(err) { }
}

if (window.addEventListener)
    window.addEventListener('load', _createVisicomMap, false);
else if (window.attachEvent)
        window.attachEvent('onload', _createVisicomMap);

// ---------------------------------------------------------
// VCustomTile class
// ---------------------------------------------------------

VCustomTile.prototype._parent_map;
VCustomTile.prototype._tile;
VCustomTile.prototype._url;

VCustomTile.prototype._i;
VCustomTile.prototype._i;

VCustomTile.prototype._left;
VCustomTile.prototype._top;

VCustomTile.prototype._visible;

/**
 *  @constructor
 */
function VCustomTile(parent_map, i, j, url) {
    if (arguments.length == 0) return this;
    
    this._parent_map = parent_map;
    this._i = i;
    this._j = j;
    this._url = url;
    
    this._left = this._i * VMapTile.TILE_WIDTH;
    this._top = (this._parent_map._currentMap._height - VMapTile.TILE_HEIGHT) - this._j * VMapTile.TILE_HEIGHT;
    
    this._visible = false;

    return this;
}

VCustomTile.prototype.isVisible = function() { return this._visible; }
VCustomTile.prototype.getDOMElement = function() { return this._tile; }

VCustomTile.prototype.show = function() {
    if (this._tile == null) {
        // Создание image'a
        this._tile = document.createElement('img');
        
        this._tile.id = 'custom_tile_' + this._i + '_' + this._j;
        
        this._tile.style.left = '0px';
        this._tile.style.top = '0px';
        this._tile.style.width = VMapTile.TILE_WIDTH;
        this._tile.style.height = VMapTile.TILE_HEIGHT;
        this._tile.style.position = 'absolute';
        this._tile.style.background = 'none';
        this._tile.style.border = '0px';
        //this._tile.style.border = '1px dotted black';

        this._tile.style.display = 'none';

        this._tile.style.zIndex = VCustomTileLayer.ZINDEX;
        
        this._tile.src = this._url;

        this._tile.onload = function() {
            this.style.display = 'inline';
        }
    }
    
    this._parent_map.getDOMElement().appendChild(this._tile);
    
    this._visible = true;
}

/**
 *  Перерисовка тайла.
 */
VCustomTile.prototype.repaint = function() {
    if (this._tile == null) return;
    
    this._tile.style.left = this._parent_map._currentMap._left + this._left + VMapTile.TILE_WIDTH + 'px';
    this._tile.style.top = this._parent_map._currentMap._top + this._top + VMapTile.TILE_HEIGHT + 'px';
}

/**
 *  Скрытие тайла.
 */
VCustomTile.prototype.hide = function() {
    if (this._tile == null) return;
    
    try {
        this._parent_map.getTilesDOMElement().removeChild(this._tile);
    } catch(err) { }
    
    this._visible = false;
}

// ---------------------------------------------------------
// VCustomTileLayer class
// ---------------------------------------------------------

VCustomTileLayer.prototype._visible;
VCustomTileLayer.prototype._tiles;
VCustomTileLayer.prototype._parent_map;
VCustomTileLayer.ZINDEX = 221;


function VCustomTileLayer() {
    if (VCustomTileLayer.ZINDEX++ > VisicomCommons.ZINDEX_POLYLINE)
        VCustomTileLayer.ZINDEX = 221;
    
    this._visible = true;

    return this;
}

VCustomTileLayer.prototype._init = function() {
    delete(this._tiles);
    this._tiles = [];
}

VCustomTileLayer.prototype._remove = function() { }

VCustomTileLayer.prototype.visible = function(value) {
    if (arguments.length == 0) return this._visible;
    
    this._visible = value;
    
    return this;
}

/**
 *  @abstaract
 */
VCustomTileLayer.prototype.getTileURL = function(i, j) {
    return null;
}

VCustomTileLayer.prototype.getTile = function(i, j) {
    var url = this.getTileURL(i, j);
    if (url == null) return null;
    
    if (this._tiles[i] == null) this._tiles[i] = [];
    var tile = this._tiles[i][j];
    if (tile == null)
        tile = this._tiles[i][j] = new VCustomTile(this._parent_map, i, j, url);
    
    return tile;
}

// ---------------------------------------------------------
// VMapObject class
// Объект на карте.
// ---------------------------------------------------------

/** Тип элемента. */
//VMapObject.prototype.__UID__;

/** DOM элемент. */
VMapObject.prototype._div;
/** X координата на карте. */
VMapObject.prototype._left;
/** Y координата на карте. */
VMapObject.prototype._top;
/** Длинна. */
VMapObject.prototype._width;
/** Высота. */
VMapObject.prototype._height;

/** Высота. */
VMapObject.prototype._mouse_over = false;

/** Границы объекта на карте. */
VMapObject.prototype._bounds;

/** Флаг видимости. */
VMapObject.prototype._visible = true;
/** Флаг отображенности. */
VMapObject.prototype._displayed = false;

VMapObject.prototype._coords;

/** Родительский элемент. */
VMapObject.prototype._parent;
/** Дочерние элементы. */
VMapObject.prototype._childs;
/** Стиль элемента. */
VMapObject.prototype._style;
/** Стиль элемента. */
VMapObject.prototype._options;

/** Тип элемента. */
VMapObject.prototype.type;

/** Обработчики событий. */
VMapObject.prototype._listeners;

/**
 *  Возвращает флаг отображенности.
 */
VMapObject.prototype.displayed = function() { return this._displayed; }

//VMapObject.genUID = function() { return "" + Math.random(); }

/*
VMapObject.prototypes = [];
VMapObject.prototypes["mapobject"] = VMapObject.prototype;
VMapObject.prototypes["layer"] = VLayer.prototype;
VMapObject.prototypes["collection"] = VLayer.prototype;
//VMapObject.prototypes["point"] = VPoint.prototype;
VMapObject.prototypes["label"] = VLabel.prototype;
VMapObject.prototypes["marker"] = VMarker.prototype;
VMapObject.prototypes["info"] = VInfoWindow.prototype;
VMapObject.prototypes["line"] = VLine.prototype;
VMapObject.prototypes["area"] = VArea.prototype;
VMapObject.prototypes["route"] = VRoute.prototype;
VMapObject.prototypes["edge"] = VEdge.prototype;
*/

/**
 *  Warpping object
 */
VMapObject._wrap = function(parent, object) {
    if (object == null) return;

    //object.__UID__ = VMapObject.genUID();
    var type;
    if (VMapObject.prototypes[object.type] != null)
        type = VMapObject.prototypes[object.type];
    else {
        if (object.coords == null) return;
        var klop = object.coords[0];
        if (klop == null) return;

        if (klop.length != null)
            type = VMultiLine.prototype;
        else
            if (object.coords.length == 1)
                type = VMarker.prototype;
            else
                type = VLine.prototype;
    }

    if (type == null) return;
    // wrapping object data
    // properties
    for (var prop in type) {
        if (type[prop] instanceof Function) continue;
        
        object[prop] = type[prop];
    }
    // methods
    for (prop in type) {
        if (!(type[prop] instanceof Function)) continue;

        if (object[prop])
            object["_" + prop] = object[prop];
        
        object[prop] = type[prop];
    }
    
    object._listeners = []; // !!!

    object.constructor = type.constructor;

    if (parent)
        object._parent = parent;

    for (var i = 0; i < object._childs.length; i++) {
        VMapObject._wrap(object, object.childs(i));
    }
}

/**
 *  Объект на карте.
 *  @constructor
 */
function VMapObject(coords, options) {
    this._childs = [];
    this._listeners = [];
    this._style = {};
    this._options = (options == undefined) ? {} : options;
    
    if (coords != null)
        this.coords(coords);
    else
        this._coords = [];
    
    //this.__UID__ = VMapObject.genUID();

    return this;
}

/** Возвращает HTML элемент. */
VMapObject.prototype.getDOMElement = function() { return this._div; }

/**
 *  @abstract
 */
VMapObject.prototype._pre_init = function() { }

/**
 *  Возвращает стиль элемента.
 */
VMapObject.prototype.style = function(element) {
    var result = null;
    if (this._style != null) result = this._style[element];
    if (result == null) {
        if (this._parent == null) return null;
        return this._parent.style(element);
    } else
        return result;
}

/**
 *  @abstract
 *  Обработка установки опции.
 */
VMapObject.prototype._handleOptions = function(key) { }

/**
 *  Возвращает опцию элемента.
 */
VMapObject.prototype.options = function(key, value) {
    if (value == null) return this._options[key];
    
    this._options[key] = value;
    this._handleOptions(key);
    
    return this;
}

/**
 *  Инициализация объекта.
 */
VMapObject.prototype._init = function() {
    var map = this._getMap();
    if (map == null) return;

    this._pre_init();
    if (this._isGeo)
        map._addObject(this);

    if (this._childs.forEach == null) return;
    
    this._childs.forEach(
        function(child) {
            child._init();
        }
    );
}

/**
 *  Рекурсивно проходит по родительским элемента и возвращает объект VMap.
 *  @returns {VMap}
 */
VMapObject.prototype._getMap = function() {
    if (this._type == "map") return this;
    
    if (this._parent == null) return null;
    if (this._parent == this) return null;
    if (this._parent instanceof VMap) return this._parent;
    
    try {
        return this._parent._getMap();
    } catch (err) {
        return null;
    }
}

/**
 *  @abstract
 */
VMapObject.prototype._pre_add = function() { }

/**
 *  @abstract
 */
VMapObject.prototype._pre_remove = function() { }

/**
 *  @public
 *  Добавляет дочерний объект.
 *  @param {VMapObject} object объект.
 */
VMapObject.prototype.add = function(object) {
    if (object == null) return this;

    // если объект создавался не через new
    // var marker = {...}
    if (object.constructor == Object || object.constructor.toString().match(/function Object/) == "function Object") {
        VMapObject._wrap(null, object);
    }

    // Запрещаем добавление, если объект находится внутри другого
    if (object._parent != null) {
        var visible = object._visible;
        object._parent.remove(object);
        object._visible = visible;
    }
    
    this._childs.push(object);
    object._parent = this;
    this._pre_add(object);

    return this;
}

/**
 *  @public
 *  Удаляет объект.
 *  @param {VMapObject} object объект.
 */
VMapObject.prototype.remove = function(object) {
    if (object == null) return this;

    for (var i = 0; i < this._childs.length; i++) {
        if (this._childs[i] != object) continue;

        object._remove();
        this._childs.splice(i, 1);
        object._parent = null;
        this._pre_remove(object);
        break;
    }

    return this;
}

/**
 *  @public
 *  Работа с дочерними элементами.
 *  @returns {VMapObject[]} массив дочерних элементов.
 */
VMapObject.prototype.childs = function(childs) {
    if (arguments.length == 0) return this._childs;

    if (childs != null) {
        if (childs.length != null)
            // Устанавливаем массив дочерних элементов
            this._childs = childs;
        else if (typeof(childs) == "number")
                // Возвращаем элемент по его индексу
                return this._childs[childs];
    }

    return this;
}

/** @abstract */
VMapObject.prototype._pre_coords = function(coords) { }

/**
 *  @public
 *  Устанавливает/возвращает перечень точек объекта.
 *  @param {Object} coords перечень координат.
 */
VMapObject.prototype.coords = function(coords) {
    var result = this._pre_coords(coords);
    if (result != undefined) return result;
    
    // Без параметров -> возвращаем текущее значение
    if (arguments.length == 0) return this._coords;

    // Возвращаем элемент по его индексу
    if (typeof(coords) == "number") return this._coords[coords];

    var map = this._getMap();
    if (map != null)
        map._removeObject(this);

    // Устанавливаем новые координаты
    if (coords.length != null) {
        if (coords[0].type != null && coords[0].type == "point") {
            // Массив структур coords
            this._coords = [];
            for (var i = 0; i < coords.length; i++) {
                this._coords.push(coords[i].coords[0]);
            }
        } else
            // Массив структур {lng, lat}
            this._coords = coords;
    } else
    if (coords.type != null && coords.type == "point") {
        this._coords = coords.coords();
    } else
        this._coords = [coords];

    this._init();

    // "Перезагрузка"
    this._re_create();

    return this;
}

VMapObject.prototype._re_create = function() {
    if (this._displayed) {
        this._hide();
        this._show();
    }
}

VMapObject.prototype.move = function(x, y) {
    this._left += x;
    this._top += y;
}

/** @abstract */
VMapObject.prototype._pre_show = function() { }

/**
 *  Добавление объекта в DOM дерево.
 */
VMapObject.prototype._show = function() {
    if (this._displayed) return;

    this._pre_show();

    var map = this._getMap();
    if (map == null) return;

    if (this._div != null)
        map.getDOMElement().appendChild(this._div);

    this._event("show");

    this._displayed = true;
}

/** @abstract */
VMapObject.prototype._pre_hide = function() { }

/**
 *  Удаление объекта из DOM дерева.
 */
VMapObject.prototype._hide = function() {
    if (!this._displayed) return;

    this._pre_hide();

    this._event("hide");

    var map = this._getMap();
    try { map.getDOMElement().removeChild(this._div); } catch(err) { }
    this._div = null;
    
    this._displayed = false;
}

/** @abstract */
VMapObject.prototype._pre_visible = function(value) { }

/**
 *  @puublic
 *  Устанавливает видимость объекта.
 *  @param {Boolean} value значение флага.
 */
VMapObject.prototype.visible = function(value) {
    var result = this._pre_visible(value);
    if (result != undefined) return result;
    
    if (arguments.length == 0) return this._visible;
    
    this._visible = value;
    this._childs.forEach(
        function(child) {
            child.visible(value);
        }
    );

    return this;
}

/**
 *  Удаление элемента.
 */
VMapObject.prototype._remove = function() {
    this._hide();
    this.visible(false);

    
    var map = this._getMap();
    if (map && this._isGeo)
        map._removeObject(this);
    
    this._childs.forEach(
        function(child) {
            child._remove();
        }
    );
}

/** @abstract */
VMapObject.prototype._pre_repaint = function() { }

/**
 *  Перерисовка объекта.
 */
VMapObject.prototype._repaint = function() {
    if (this._div == null) return;

    this._pre_repaint();

    var map = this._getMap();
    if (map == null) return;

    try {
        this._div.style.left = Math.round(map._currentMap._left + VMapTile.TILE_WIDTH + this._left) + 'px';
        this._div.style.top = Math.round(map._currentMap._top + VMapTile.TILE_HEIGHT + this._top) + 'px';
    } catch(err) { }
}

/**
 *  @public
 *  Возвращает MBR объекта.
 *  @returns {VRect} MBR объекта.
 */
VMapObject.prototype.bounds = function() {
    if (this._bounds == null)
        this._bounds = new VRect(this._coords);
    return this._bounds;
}

/**
 *  Добавление/вызов обработчика события.
 *  @param {String} event_name название события.
 *  @param {function} handler обработчик события.
 */
VMapObject.prototype._event = function(event_name, handler) {
    var result = true;
    var args = arguments;
    
    if (handler != null)
        this._listeners.push(new VEvent(event_name, handler));
    else {
        this._listeners.forEach(
            function(event) {
                if (event._event_name != event_name) return;
                
                var klop = event._handler(args[2], args[3], args[4]);
                result = result && ((klop == undefined) ? true : klop);
            }
        );
    }

    return result;
}

/**
 *  Добавление/вызов обработчика события.
 *  @param {String} event_name название события.
 *  @param {function} handler обработчик события.
 *      function callback(coords, button) {
 *
 *      }
 *  @param {object} coords координаты.
 *  @param {string} button кнопка мыши.
 */
VMapObject.prototype._mouseEvent = function(event_name, handler, coords, button) {
    var result = true;
    
    if (handler != null)
        this._listeners.push(new VEvent(event_name, handler));
    else {
        this._listeners.forEach(
            function(event) {
                if (event._event_name != event_name) return;
                
                var klop = event._handler(coords, button);
                result = result && ((klop == undefined) ? true : klop);
            }
        );
    }
    
    return result;
}


VMapObject.prototype.mouseclick = function(handler, coords, button) { this._mouseEvent("mouseclick", handler, coords, button); }
VMapObject.prototype.mousedblclick = function(handler, coords, button) { this._mouseEvent("mousedblclick", handler, coords, button); }
VMapObject.prototype.mousedown = function(handler, coords, button) { this._mouseEvent("mousedown", handler, coords, button); }
VMapObject.prototype.mouseup = function(handler, coords, button) { this._mouseEvent("mouseup", handler, coords, button); }
VMapObject.prototype.mousemove = function(handler, coords, button) { this._mouseEvent("mousemove", handler, coords, button); }

VMapObject.prototype.mouseover = function(handler, coords) { this._mouseEvent("mouseover", handler, coords); }
VMapObject.prototype.mouseout = function(handler, coords) { this._mouseEvent("mouseout", handler, coords); }


VMapObject.prototype.startdrag = function(handler) { this._event("startdrag", handler); }
VMapObject.prototype.dragging = function(handler) { this._event("dragging", handler); }
VMapObject.prototype.enddrag = function(handler) { this._event("enddrag", handler, this); }

VMapObject.prototype.beforezoomchange = function(handler, index) { return this._event("beforezoomchange", handler, index); }
VMapObject.prototype.onzoomchange = function(handler) { return this._event("zoomchange", handler); }
VMapObject.prototype.onshow = function(handler) { this._event("show", handler); }
VMapObject.prototype.onhide = function(handler) { this._event("hide", handler); }

/**
 *  @constructor
 */
function VEvent(event_name, handler) {
    this._event_name = event_name;
    this._handler = handler;

    return this;
}

VEvent.prototype._event_name = null;
VEvent.prototype._handler = null;
// ---------------------------------------------------------
// VLayer class
// Слой для группировки объектов.
// ---------------------------------------------------------

/** Наследование. */
VLayer.prototype = new VMapObject();

/**
 *  @category = Layers
 *  Создание слоя.
 *  @class
 *  Слои применяются для группировки объектов на карте.
 *  @constructor
 */
function VLayer() {
    VMapObject.call(this);
    this.type = "layer";
}

VLayer.prototype._pre_add = function(object) {
    object._init();
}

VLayer.prototype.bounds = function() {
    var coords = [];
    for (var i = 0; i < this.childs().length; i++) {
        var child = this.childs(i);
        for (var j = 0; j < child.coords().length; j++) {
            coords.push(child.coords(j));
        }
    }
    this._bounds = new VRect(coords);
    
    return this._bounds;
}
// ---------------------------------------------------------
// VMap class
// Движок карты.
// ---------------------------------------------------------

/** Наследование. */
VMap.prototype = new VMapObject();

VMap.PROJECTION_FACTOR = 0.66648562929531985;
//VMap.PROJECTION_FACTOR = 1;
//VMap.PROJECTION_FACTOR = 0.47377422;


/**  */
VMap.prototype._viewport;
/**  */
VMap.prototype._viewport_inner;

VMap.prototype._logo;

/** Перечень слоев карт (zoom'ов). */
VMap.prototype._maps = Array();
/** Текущий видимый слой карты (zoom). */
VMap.prototype._currentMap = null;

VMap.prototype._draggable_marker = null;

VMap.prototype._mapName = null;
VMap.prototype._language = null;

VMap.prototype._mouse_down;
VMap.prototype._dragging;
VMap.prototype._dragged_object;
VMap.prototype._objects_index;
VMap.prototype._map_div;
VMap.prototype._tiles_div;
VMap.prototype._tiles;
VMap.prototype._center;
VMap.prototype._zoom_index;

VMap.prototype._zoomControl;

VMap.prototype._customTileLayers;

/**
 *  zoomControl             : object    - опции элемента управления масштабом
 *  {
 *      min                 : int       - минимальный индекс масштаба
 *      max                 : int       - максимальный индекс масштаба
 *      mouseWheel          : boolean   - включает/выключает управление колесиком мыши
 *      mouseDoubleClick    : boolean   - включает/выключает масштабирование двойным кликом мыши
 *      ruler               : boolean   - включает/выключает масштабную линейку
 *  }
 */
VMap.prototype._options = null;

VMap.prototype._mouse_point = null;
VMap.prototype._current_zoom_point = null;

VMap.prototype._zoom_helper;

VMap.AUTH_KEY = "";
VMap.prototype.initialized = false;

VMap.prototype._getContentSize = function(content, maxWidth, maxHeight) {
    maxWidth = maxWidth ? maxWidth : 350;
    maxHeight = maxHeight ? maxHeight : 350;

    var div = document.createElement('div');
    div.innerHTML = content;
    div.style.cssFloat = 'left';
    div.style.overflow = 'auto';
    div.style.border = '1px dotted gray';
    div.style.maxWidth = maxWidth + 'px';
    div.style.maxHeight = maxHeight + 'px';
    
    document.body.appendChild(div);
    var w = div.offsetWidth;
    var h = div.offsetHeight;
    document.body.removeChild(div);
    if (w > maxWidth) w = maxWidth;
    
    return {width: w, height: h};
}

/**
 *  @category = Map
 *  @class VMap является основным классом "движка" интернет-карты.
 *  @param {HTMLElement} viewport HTML элемент, в котором планируется отображать карту.
 *  @param {Object} options опции.
 *  @constructor
 */
function VMap(viewport, options) {
    VMapObject.call(this, null, options);

    this._type = "map";
    this._viewport = viewport;
    _VISICOM_MapGlobalContext = this;

    this._zoomControl = new VZoomControl(this, this.options("zoomControl"));

    this._tiles = [];
    this._customTileLayers = [];
    this._objects_index = [];

    if (this._zoomControl.options("smooth"))
        this._zoom_helper = new VZoomHelper(this);

    this.init();

    return this;
}

/**  */
VMap.prototype.getViewport = function() {return this._viewport_inner;}
VMap.prototype.getDOMElement = function() {return this._map_div;}
VMap.prototype.getTilesDOMElement = function() {return this._map_div;}

/**
 *  Инициализация.
 */
VMap.prototype._init_ = function() {
    if (USER_DATA['Browser'].MSIE) {
        document.namespaces.add('v', 'urn:schemas-microsoft-com:vml');

        // Блок стилей в теге HEAD
        var style = document.createElement("style");
        var head = document.getElementsByTagName("head")[0];
        if (head == null) {
            head = document.createElement("head");
            document.body.appendChild(head);
        }
        head.appendChild(style);

        document.styleSheets[0].addRule('v\\:polyline', 'behavior: url(#default#VML);');
        document.styleSheets[0].addRule('v\\:line', 'behavior: url(#default#VML);');
        document.styleSheets[0].addRule('v\\:stroke', 'behavior: url(#default#VML);');
        document.styleSheets[0].addRule('v\\:shape', 'behavior: url(#default#VML);');
        document.styleSheets[0].addRule('v\\:fill', 'behavior: url(#default#VML);');
        document.styleSheets[0].addRule('v\\:path', 'behavior: url(#default#VML);');
    }

    var left = (this._viewport.style.left != "") ? parseInt(this._viewport.style.left) : 0;
    var top = (this._viewport.style.top != "") ? parseInt(this._viewport.style.top) : 0;

    this._left = left;
    this._top = top;
    this._width = this._viewport.offsetWidth || parseInt(this._viewport.style.width);
    this._height = this._viewport.offsetHeight || parseInt(this._viewport.style.height);

    this._viewport.style.position = 'relative';

    this._viewport_relative = document.createElement("div");

    this._viewport_inner = document.createElement("div");
    this._viewport_inner.style.overflow = 'hidden';
    this._viewport_inner.style.position = 'absolute';
    this._viewport_inner.style.zIndex = '1';
    this._viewport_inner.style.left = '0px';
    this._viewport_inner.style.top = '0px';
    this._viewport_inner.style.width = this._width + 'px';
    this._viewport_inner.style.height = this._height + 'px';
    this._viewport.appendChild(this._viewport_inner);

    var _this = this;
    window.onmouseup = function(e) {
        _this._dragging = false;
    }

    this._map_div = document.createElement('div');
    this._map_div.id = 'visicom_map_div';
    this._map_div.style.position = 'absolute';
    this._map_div.style.left = '-' + VMapTile.TILE_WIDTH + 'px';
    this._map_div.style.top = '-' +  VMapTile.TILE_HEIGHT + 'px';
    this._map_div.style.width = this._width + VMapTile.TILE_WIDTH * 2 + 'px';
    this._map_div.style.height = this._height + VMapTile.TILE_HEIGHT * 2 + 'px';
    this._map_div.style.zIndex = VisicomCommons.ZINDEX_MAP;
    this._map_div.style.cursor = VisicomCommons.OPEN_HAND_CURSOR;

    VisicomUtils.clearStyles(this._map_div);
    this._viewport_inner.appendChild(this._map_div);
    
    var _shift = new Object();
    this._mouse_point = new Object();
    
    if (this.options("zoomControl") == null || this.options("zoomControl").mouseWheel != false) {
        // Обработка колеса мыши
        /* Opera */
        if (USER_DATA['Browser'].Opera)
            VisicomUtils.addHandler(window, 'mousewheel', function(e) {_this._onMouseWheel(e);});
        else /* IE */
        if (USER_DATA['Browser'].MSIE || USER_DATA['Browser'].Chrome)
            VisicomUtils.addHandler(document, 'mousewheel', function(e) {_this._onMouseWheel(e);});
        else /* Gecko */
            VisicomUtils.addHandler(window, 'DOMMouseScroll', function(e) {_this._onMouseWheel(e);});
    }
    
    this._map_div.onmouseover = function(e) {
        if(!e) e = window.event;
        _this._mouse_over = true;
        
        _this.mouseover();
    }
    this._map_div.onmouseout = function(e) {
        if(!e) e = window.event;
        
        if (!_this._isMouseOnViewport(e.clientX, e.clientY)) {
            _this._mouse_over = false;
            _this._mouse_down = false;
            if (_this._dragging) {
                _this.enddrag();
                _this._dragging = false;
            }
            return;
        }
        
        _this.mouseout();
    }

    VisicomUtils.addHandler(document, 'mousemove',
        function(e) {
            if (!_this._isMouseOnViewport(e.clientX, e.clientY)) {
                _this._mouse_over = false;
                _this._mouse_down = false;
                if (_this._dragging) {
                    _this.enddrag();
                    _this._dragging = false;
                }
                return;
            }
        }
    );
    
    this._map_div.onmousedown = function(e) {
        var btn = 0;
        if (e == undefined) {
            e = window.event;
            btn = window.event.button;
        } else
            btn = e.which;

        _this._mouseEvent("mousedown", null, _this._getCoords(e.clientX, e.clientY), (btn == 1) ? "left" : "right");
        if (btn != 1) return;

        var map = _this._currentMap;
        _shift.x = e.clientX - map._left;
        _shift.y = e.clientY - map._top;

        if (_this._zoom_helper != null) {
            _this._zoom_helper.mousedown(e);
        }
        
        if (!_this._mouse_over) return;
        
        VInfoWindow._changeWindow();

        _this._mouse_down = true;
        _this._map_div.style.cursor = VisicomCommons.CLOSED_HAND_CURSOR;

        _this.repaint();

        _this._clearSmoothTimer();

        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
    }
    
    VisicomUtils.addHandler(document, 'mousemove',
        function(e) {
            if(!e) e = window.event;

            _this._current_zoom_point = null;

            _this._mouse_point.x = e.clientX;
            _this._mouse_point.y = e.clientY;

            var map = _this._currentMap;

            if (_this._dragged_object != null) {
                var object = _this._dragged_object;

                object._moved = true;

                object.coords(_this._getCoords(e.clientX + object._move_delta.dx, e.clientY + object._move_delta.dy));
                object._pre_show();
                object._repaint();

                object.dragging();

                if (e.stopPropagation) e.stopPropagation();
                else e.cancelBubble = true;
                if (e.preventDefault) e.preventDefault();
                else e.returnValue = false;

                _this.repaint();

                return;
            }

            _this._current_zoom_point = _this._getCoords(e.clientX, e.clientY);

            if (!_this._mouse_down) {
                _this._mouseEvent("mousemove", null, _this._getCoords(e.clientX, e.clientY), "left");
                return;
            }

            if (!_this._dragging) _this.startdrag();

            map._left = e.clientX - _shift.x;
            map._top = e.clientY - _shift.y;

            if (_this._zoom_helper != null) {
                _this._zoom_helper.mousemove(e);
            }

            _this._map_div.style.cursor = VisicomCommons.CLOSED_HAND_CURSOR;
            _this.repaint();

            _this.dragging();
            _this._dragging = true;

            if (e.stopPropagation) e.stopPropagation();
            else e.cancelBubble = true;
            if (e.preventDefault) e.preventDefault();
            else e.returnValue = false;
        }
    );
    
    this._map_div.onmouseup = function(e) {
        var btn = 0;
        if (e == undefined) {
            e = window.event;
            btn = window.event.button;
        } else
            btn = e.which;
        if (btn != 1) return;

        _this._map_div.style.cursor = VisicomCommons.OPEN_HAND_CURSOR;
        _this._mouseEvent("mouseup", null, _this._getCoords(e.clientX, e.clientY), (btn == 1) ? "left" : "right");
        
        if (_this._dragged_object != null) {
            var object = _this._dragged_object;
            object._onMouseUp(e);
        } else
            if (!_this._dragging)
                _this._mouseEvent("mouseclick", null, _this._getCoords(e.clientX, e.clientY), (btn == 1) ? "left" : "right");
        
        _this._mouse_down = false;
        if (_this._dragging) {
            _this.enddrag();
        }
        _this._dragging = false;
    }

    this._map_div.ondblclick = function(e) {
        if(!e) e = window.event;
        var btn = 0;
        if (e == undefined) {
            e = window.event;
            btn = window.event.button;
        } else
            btn = e.which;

        var coords = _this._getCoords(e.clientX, e.clientY);
        _this._mouseEvent("mousedblclick", null, coords, (btn == 1) ? "left" : "right");

        _this._mouse_point.x = e.clientX;
        _this._mouse_point.y = e.clientY;
        
        if (_this.options("zoomControl") == null || _this.options("zoomControl").mouseDblClick != false) {
            var map = _this._currentMap;
            if (_this.zoom() == _this._maps.length - 1) {
                _this._clearSmoothTimer();
                _this._smoothMove(coords);
            } else {
                if (_this._zoomControl.options("smooth"))
                    _this._zoom_helper.run(map._zoom_index + 1, _this._mouse_point);
                _this._zoomByPoint(_this._mouse_point, map._zoom_index + 1);
            }
        }
    }
    
    this._repaintLogo();
}

// --------------------------------------------------------
// Функции для плавного перемещения.
// --------------------------------------------------------

/**
 *  Плавное перемещение к указанной точке.
 *  @param {Object} coords координата.
 */
VMap.prototype._smoothMove = function(coords, callback) {
    var id = Math.random();
    VMap.MAPS[id] = this;
    VMap.CALLBACKS[id] = callback;
    VMap.SMOOTH_MOVING_TIMER = setTimeout("VMap.SMOOTH_MOVING(" + id + ", { lng: " + coords.lng + ", lat: " + coords.lat + " })", 10);
}

VMap.MAPS = [];
VMap.CALLBACKS = [];
VMap.SMOOTH_MOVING_STEP = 17;
VMap.prototype.SMOOTH_MOVING_TIMER = null;
/**
 *  Плавное перемещение.
 *  @param {int} id идентификатор карты.
 *  @param {Object} coords координата.
 */
VMap.SMOOTH_MOVING = function(id, coords) {
    var map = VMap.MAPS[id];
    var callback = VMap.CALLBACKS[id];
    
    var center = map.center();
    
    var from = map._convertToScreenCoords(center);
    var to = map._convertToScreenCoords(coords);
    
    var dist = Math.sqrt(Math.pow(from.x - to.x, 2) + Math.pow(from.y - to.y, 2));
    
    var step = VMap.SMOOTH_MOVING_STEP;
    if (dist < step) {
        VMap.CLEAR_SMOOTH_TIMER(id);
        return;
    }

    var dx = (from.x - to.x);
    var dy = (from.y - to.y);
    var cosA = dx / dist;
    var sinA = dy / dist;
    
    map._currentMap._left += step * cosA;
    map._currentMap._top += step * sinA;
    map.repaint();

    if (callback != null) {
        var continue_ = callback();
        if (!continue_) {
            VMap.CLEAR_SMOOTH_TIMER(id);
            return;
        }
    }
    
    map.SMOOTH_MOVING_TIMER = setTimeout("VMap.SMOOTH_MOVING(" + id + ", { lng: " + coords.lng + ", lat: " + coords.lat + " })", 10);
}

VMap.prototype._clearSmoothTimer = function() {
    clearTimeout(this.SMOOTH_MOVING_TIMER);
}

VMap.CLEAR_SMOOTH_TIMER = function(id) {
    var map = VMap.MAPS[id];
    clearTimeout(map.SMOOTH_MOVING_TIMER);
    VMap.MAPS[id] = null;
}
// --------------------------------------------------------

VMap.prototype._isMouseOnViewport = function(clientX, clientY) {
    var bounds = VisicomUtils.getBounds(this._viewport);
    var x = clientX + VisicomUtils.getClientsLeft();
    var y = clientY + VisicomUtils.getClientsTop();
    return !(x - bounds.left >= this._width || y - bounds.top >= this._height ||
             x - bounds.left <= 0 || y - bounds.top <= 0);
}

/**
 *  Возвращает географические координаты по событию мыши.
 *  @returns {object} координаты.
 */
VMap.prototype._getCoords = function(clientX, clientY) {
    var map = this._currentMap;

    var bounds = VisicomUtils.getBounds(this._viewport);
    var coords = this._convertToGeoCoords((clientX + VisicomUtils.getClientsLeft()) - map._left - bounds.left,
                                          (clientY + VisicomUtils.getClientsTop()) - map._top - bounds.top,
                                          map._units_per_pixel);
    return coords;
}

/**
 *  Проверка выхода карты за границы окна видимости и корректировка ее позиции в этом окне.
 */
VMap.prototype._checkPosition = function() {
    var result = true;

    var TILE_WIDTH_2 = Math.round(VMapTile.TILE_WIDTH / 2);
    var TILE_HEIGHT_2 = Math.round(VMapTile.TILE_HEIGHT / 2);

    var map = this._currentMap;

    if (map._left > (this._width - TILE_WIDTH_2)) {
        map._left = this._width - TILE_WIDTH_2;
        result = false;
    }
    if (map._top > (this._height - TILE_HEIGHT_2)) {
        map._top = this._height - TILE_HEIGHT_2;
        result = false;
    }
    if (map._width + map._left < TILE_WIDTH_2) {
        map._left = TILE_WIDTH_2 - map._width;
        result = false;
    };
    if (map._height + map._top < TILE_HEIGHT_2) {
        map._top = TILE_HEIGHT_2 - map._height;
        result = false;
    };

    return result;
}

VMap.prototype._checkZoom = function(index) {
    var zoom = this.zoomControl();
    var max = (zoom.options("max") != null) ? zoom.options("max") : this._maps.length - 1;
    var min = (zoom.options("min") != null) ? zoom.options("min") : 0;

    return !(index < min || index > max);
}

/**
 *  @public
 *  Перерисовка карты.
 */
VMap.prototype.repaint = function() {
    if (this._currentMap == null) return this;

    if (this._viewport_inner == null) return this;

    this._checkResize();

    this._repaintMap();

    return this;
}

VMap.prototype.onpaint = function(handler) {
    this._event("onpaint", handler);
}

/** */
VMap.prototype.zoomControl = function() {return this._zoomControl;};

/**
 *  Отображение карты.
 */
VMap.prototype._showMap = function() {
    var map = this._currentMap;
    if (map == null) return;

    delete(this._tiles);
    this._tiles = [];

    // Определение координат центра экрана по локальным координатам
    if (this._center != null) {
        var dx = (this._center.lng - this.bounds().leftBottom().lng);
        var x_screen = dx / map._units_per_pixel;
        map._left = Math.round((this._width / 2) - x_screen);

        var dy = (this.bounds().rightTop().lat - this._center.lat);
        var y_screen = dy / map._units_per_pixel;
        map._top = Math.round((this._height / 2) - y_screen);
    } else {
        x_screen = (this._center.lng - this.bounds().leftBottom().lng) / map._units_per_pixel;
        map._left = (this._width / 2) - x_screen;

        y_screen = (this._center.lat - this.bounds().leftBottom().lat) / map.__units_per_pixel;
        map._top = (this._height / 2) - y_screen;
    }

    this._checkPosition();
    this._initLayers();
    this._initObjectsIndex();
    this._repaintMap();
}

/**
 *  Перерисовка карты.
 */
VMap.prototype._repaintMap = function() {
    var fromI, fromJ, toI, toJ;
    var min_i, min_j, max_i, max_j;

    var map = this._currentMap;

    this._checkPosition();

    // Определение области прорисовки
    if (map._width < this._width) {
        fromI = min_i = 0;
        toI = max_i = map._x_tiles_count - 1;
        fromJ = min_j = 0;
        toJ = max_j = map._y_tiles_count - 1;
    } else {
        if (map._left < 0) {
            min_i = Math.floor(Math.abs(map._left) / VMapTile.TILE_WIDTH);
            max_i = Math.floor((Math.abs(map._left) + this._width) / VMapTile.TILE_WIDTH);
        } else {
            min_i = 0;
            max_i = Math.floor((this._width - map._left) / VMapTile.TILE_WIDTH);
        }

        if (map._top < 0) {
            min_j = Math.floor((map._height - (Math.abs(map._top) + this._height)) / VMapTile.TILE_HEIGHT);
            max_j = Math.floor((map._height - Math.abs(map._top)) / VMapTile.TILE_HEIGHT);
        } else {
            min_j = Math.floor((map._height - (this._height - map._top)) / VMapTile.TILE_HEIGHT);
            max_j = map._y_tiles_count - 1;
        }

        fromI = ((map._current_min_i != null) && (map._current_min_i < min_i)) ? map._current_min_i : min_i - 1;
        toI = ((map._current_max_i != null) && (map._current_max_i > max_i)) ? map._current_max_i : max_i;
        fromJ = ((map._current_min_j != null) && (map._current_min_j < min_j)) ? map._current_min_j : min_j - 1;
        toJ = ((map._current_max_j != null) && (map._current_max_j > max_j)) ? map._current_max_j : max_j;

        fromI = (fromI < 0) ? fromI = 0 : fromI;
        fromJ = (fromJ < 0) ? fromJ = 0 : fromJ;
        toI = (toI >= map._x_tiles_count) ? toI = map._x_tiles_count - 1 : toI;
        toJ = (toJ >= map._y_tiles_count) ? toJ = map._y_tiles_count - 1: toJ;
    }

    // Текущие гранпицы прорисовки
    map._current_min_i = min_i;
    map._current_max_i = max_i;
    map._current_min_j = min_j;
    map._current_max_j = max_j;

    var objects_to_display = [];
    var objects_to_hide = [];

    // Перерисовка тайлов в шахматном порядке :)
    for (var i = fromI; i <= toI; i= i + 2) {
        for (var j = fromJ + 1; j <= toJ; j = j + 2) {
            this._repaintTile(i, j, objects_to_display, objects_to_hide);
        }
    }
    for (i = fromI + 1; i <= toI; i = i + 2) {
        for (j = fromJ; j <= toJ; j = j + 2) {
            this._repaintTile(i, j, objects_to_display, objects_to_hide);
        }
    }
    for (i = fromI; i <= toI; i= i + 2) {
        for (j = fromJ; j <= toJ; j = j + 2) {
            this._repaintTile(i, j, objects_to_display, objects_to_hide);
        }
    }
    for (i = fromI + 1; i <= toI; i = i + 2) {
        for (j = fromJ + 1; j <= toJ; j = j + 2) {
            this._repaintTile(i, j, objects_to_display, objects_to_hide);
        }
    }
    
    objects_to_display.forEach(
        function(object) {
            if (!object.displayed())
                object._show();
            object._repaint();
        }
    );
    objects_to_hide.forEach(
        function(object) {
            if (object.displayed())
                object._hide();
        }
    );

    this._center = this._convertToGeoCoords(Math.round(this._width / 2 - this._currentMap._left),
                                            Math.round(this._height / 2 - this._currentMap._top),
                                            this._currentMap._units_per_pixel);
}

/**
 *  Перерисовка участка с изображением карты.
 *  @param {int} i индекс участка по оси X.
 *  @param {int} j индекс участка по оси Y.
 *  @param {Array} objects_to_display перечень объектов для отображения.
 *  @param {Array} objects_to_hide перечень объектов для скрытия.
 */

VMap.prototype._repaintTile = function(i, j, objects_to_display, objects_to_hide) {
    var map = this._currentMap;

    if (this._tiles[i] == null)
        this._tiles[i] = [];
    if (this._tiles[i][j] == null)
        this._tiles[i][j] = new VMapTile(i, j, this);

    var tile = this._tiles[i][j];

    if ((i >= map._current_min_i) && (i <= map._current_max_i) && (j >= map._current_min_j) && (j <= map._current_max_j)) {
        // Участок видимый
        if (!tile.isVisible())
            tile.show();
        tile.repaint();

        var len = this._customTileLayers.length;
        for (var n = 0; n < len; n++) {
            var layer = this._customTileLayers[n];

            var custom_tile = layer.getTile(i, j);
            if (custom_tile == null) continue;

            if (!layer.visible()) {
                custom_tile.hide();
                continue;
            }

            if (!custom_tile.isVisible())
                custom_tile.show();
            custom_tile.repaint();
        }

        if ((this._objects_index[i] != null) && (this._objects_index[i][j] != null)) {
            var array = this._objects_index[i][j];
            if (array.length == 0) return;

            len = array.length;
            for (var z = 0; z < len; z++) {
                var object = array[z];
                if (object.visible()) {
                    // Помечаем объект на отображение
                    if (objects_to_display.indexOf(object) < 0)
                        objects_to_display.push(object);

                    var index = objects_to_hide.indexOf(object);
                    if (index >= 0)
                        // Удаляем объект из помеченных на скрытие
                        objects_to_hide.splice(index, 1);

                    object._repaint();
                } else {
                    if (objects_to_hide.indexOf(object) >= 0) continue;

                    objects_to_hide.push(object);
                }
            }
        }
    } else {
        // Участок невидимый
        if (tile.isVisible())
            tile.hide();

        len = this._customTileLayers.length;
        for (n = 0; n < len; n++) {
            layer = this._customTileLayers[n];
            custom_tile = layer.getTile(i, j);
            if (custom_tile == null) continue;

            if (custom_tile.isVisible())
                custom_tile.hide();
        }

        if ((this._objects_index[i] != null) && (this._objects_index[i][j] != null)) {
            array = this._objects_index[i][j];
            if (array.length == 0) return;

            len = array.length;
            for (z = 0; z < len; z++) {
                object = array[z];
                if (object.displayed()) {
                    if (objects_to_display.indexOf(object) >= 0) continue;
                    if (objects_to_hide.indexOf(object) >= 0) continue;

                    objects_to_hide.push(object);
                }
            }
        }
    }

}

/**
 *  Обработка нажатия клавиш.
 */
VMap.prototype._onKeyDown = function(e) {
    if (!this._mouse_over) return;

    var map = this._currentMap;

    if (!e) e = window.event;
    var keyCode = e.keyCode;
    var moved = false;

    var left = map._left;
    var top = map._top;

    switch (keyCode) {
        case 38:  // KEY_UP
            top += Math.round(VIEWPORT_WIDTH / 10);
            moved = true;
            break;

        case 40:  // KEY_DOWN
            top -= Math.round(VIEWPORT_WIDTH / 10);
            moved = true;
            break;

        case 37:  // KEY_LEFT
            left += Math.round(VIEWPORT_HEIGHT / 10);
            moved = true;
            break;

        case 39:  // KEY_RIGHT
            left -= Math.round(VIEWPORT_HEIGHT / 10);
            moved = true;
            break;

        case 33:  // PAGE_UP
            top += Math.round(VIEWPORT_WIDTH / 2);
            moved = true;
            break;

        case 34:  // PAGE_DOWN
            top -= Math.round(VIEWPORT_WIDTH / 2);
            moved = true;
            break;

        case 36:  // HOME
            left += Math.round(VIEWPORT_WIDTH / 2);
            moved = true;
            break;

        case 35:  // END
            left -= Math.round(VIEWPORT_WIDTH / 2);
            moved = true;
            break;
    }

    if (moved) {
        // Проверки выхода карты за границы окна
        if ( ((left <= 0) && (left + map._width > VIEWPORT_WIDTH)) ||
            ((left > 0) && (left + map._width <= VIEWPORT_WIDTH)) ) {
            map._left = Math.round(left);
        }

        if ( ((top <= 0) && (top + map._height > VIEWPORT_HEIGHT)) ||
            ((top > 0) && (top + map._height <= VIEWPORT_HEIGHT)) )   {
            map._top = Math.round(top);
        }

        map.repaintMap();
    }

}

/**
 *  Обработка колесика мыши.
 */
VMap.prototype._onMouseWheel = function(event) {
    if (!this._isMouseOnViewport(this._mouse_point.x, this._mouse_point.y)) return;
    
    var delta = 0;

    if (!event) event = window.event;
    
    if (!this._mouse_over) return;
    
    if (event.stopPropagation) event.stopPropagation();
    else event.cancelBubble = true;
    if (event.preventDefault) event.preventDefault();
    else event.returnValue = false;
    
    if (event.wheelDelta) {
        delta = event.wheelDelta / 120;
    } else if (event.detail) {
        delta = -event.detail / 3;
    }

    if (this.MOUSE_WHEEL_INDEX == null)
        this.MOUSE_WHEEL_INDEX = this.zoom();
    var index = this.MOUSE_WHEEL_INDEX + ((delta < 0) ? -1 : 1);
    if (!this._checkZoom(index)) return;
    if (Math.abs(index - this.zoom()) > 2) return;
    
    this.MOUSE_WHEEL_INDEX = index;
    
    if (this._zoomControl.options("smooth"))
        this._zoom_helper.run(index, {x: event.clientX, y: event.clientY});
    
    this._zoomControl.position(this.MOUSE_WHEEL_INDEX);
    this._startMouseWheelTimer(this._mouse_point);
}

// --------------------------------------------------------
// Функции для переключения колесиком по таймеру
// --------------------------------------------------------

VMap.prototype.MOUSE_WHEEL_INDEX;
VMap.prototype.MOUSE_WHEEL_TIMER = null;
VMap.MOUSE_WHEEL_TIMER_INTERVAL = 200;

VMap.prototype._startMouseWheelTimer = function(mouse_point) {
    this._clearMouseWheelTimer();
    
    var id = Math.random();
    VMap.MAPS[id] = this;
    this.MOUSE_WHEEL_TIMER = setTimeout("VMap.MOUSE_WHEEL_ZOOM(" + id + ", { x: " + mouse_point.x + ", y: " + mouse_point.y + " })", VMap.MOUSE_WHEEL_TIMER_INTERVAL);
}

VMap.prototype._clearMouseWheelTimer = function() {
    clearTimeout(this.MOUSE_WHEEL_TIMER);
}

VMap.MOUSE_WHEEL_ZOOM = function(id, mouse_point) {
    var map = VMap.MAPS[id];
    if (map == null) return;
    
    VInfoWindow._changeWindow();
    map._zoomByPoint(mouse_point, map.MOUSE_WHEEL_INDEX);
    map.MOUSE_WHEEL_INDEX = null;
    map.repaint();
    
    map._clearMouseWheelTimer();
}
// --------------------------------------------------------

/**
 *  Устанавливает индекс zoom'a.
 *  @param {int} index индекс zoom'a.
 */
VMap.prototype._setZoom = function(index) {
    this._clearSmoothTimer();

    VMarker._closeCurrentHint();

    this._zoom_index = index;
    this._zoomControl.position(index);
    
    if (this._maps[index] == null) return;
    
    if (this._currentMap != null)
        this._hide();

    this._currentMap = this._maps[index];
    this._currentMap._tiles_map = [];

    var map = this._currentMap;
    map._current_min_i = null;
    map._current_max_i = null;
    map._current_min_j = null;
    map._current_max_j = null;
}

/**d
 *  Zoom с сохранением точки в текущей области экрана.
 */
VMap.prototype._zoomByPoint = function(screen, index) {
    if (!this._checkZoom(index)) return;

    if (!this.beforezoomchange(null, index)) return this;

    var bounds = VisicomUtils.getBounds(this._viewport);
    var x_offset = screen.x - bounds.left + VisicomUtils.getClientsLeft();
    var y_offset = screen.y - bounds.top + VisicomUtils.getClientsTop();
    
    this._setZoom(index);
    
    var map = this._currentMap;
    if (map == null) return this;

    var geo = this._current_zoom_point;
    var _screen = this._convertToScreenCoords(geo);

    map._left = Math.round(x_offset - _screen.x);
    map._top = Math.round(y_offset - _screen.y);

    this._checkPosition();

    this._initLayers();

    this.repaint();

    this.onzoomchange();

    return this;
}

VMap.prototype._hideTiles = function() {
    var map = this._currentMap;

    for (var i = map._current_min_i; i <= map._current_max_i; i++) {
        if (this._tiles[i] == null) continue;

        for (var j = map._current_min_j; j <= map._current_max_j; j++) {
            var tile = this._tiles[i][j];
            if (tile != null)
                tile.hide();
        }
    }

    delete(this._tiles);
    this._tiles = [];
}

/**
 *  Скрытие карты.
 */
VMap.prototype._hide = function() {
    var map = this._currentMap;
    
    for (var i = map._current_min_i; i <= map._current_max_i; i++) {
        if (this._tiles[i] == null) continue;

        for (var j = map._current_min_j; j <= map._current_max_j; j++) {
            var tile = this._tiles[i][j];
            if (tile != null)
                tile.hide();
        }
    }

    delete(this._tiles);
    this._tiles = [];

    for (var n = 0; n < this._customTileLayers.length; n++) {
        var layer = this._customTileLayers[n];
        for (i = map._current_min_i; i <= map._current_max_i; i++) {
            for (j = map._current_min_j; j <= map._current_max_j; j++) {
                tile = layer.getTile(i, j);
                if (tile == null) continue;

                tile.hide();
            }
        }
    }

    this._hideLayers();

    // Обнуление границ прорисовки
    map._current_min_i = map._current_max_i = map._current_min_j = map._current_max_j = null;
}

/**
 *  @public
 *  Вовзращает/устанавливает текущий масштаб по его индексу.
 *  @params {int} index индекс масштаба.
 */
VMap.prototype.zoom = function(index) {
    if (arguments.length == 0) return this._zoom_index;
    index = parseInt(index);
    
    if (this._zoom_index == index) return this;
    if (this._maps[index] == null) return this;

    var bounds = VisicomUtils.getBounds(this._viewport);
    var coords = {x: this._width / 2 + bounds.left - VisicomUtils.getClientsLeft(),
                   y: this._height / 2 + bounds.top - VisicomUtils.getClientsTop()
                 };
    /*if (this._zoomControl.options("smooth"))
        this._zoom_helper.run(index, coords);*/

    this._zoom_index = index;

    VInfoWindow._changeWindow();

    this._current_zoom_point = this.center();

    this._zoomByPoint(coords, index);
    
    return this;
}

/**
 *  @public
 *  Увеличивает масштаб на единицу.
 */
VMap.prototype.zoomIn = function() {
    this._mouse_down = false;
    
    var index = this._currentMap._zoom_index + 1;
    if (this._zoom_index == index) return this;
    if (this._maps[index] == null) return this;
    
    var bounds = VisicomUtils.getBounds(this._viewport);
    var coords = {x: this._width / 2 + bounds.left - VisicomUtils.getClientsLeft(),
                   y: this._height / 2 + bounds.top - VisicomUtils.getClientsTop()
                 };
    if (this._zoomControl.options("smooth"))
        this._zoom_helper.run(index, coords);
    this.zoom(index);
    
    return this;
}

/**
 *  @public
 *  Уменьшает масштаб на единицу.
 */
VMap.prototype.zoomOut = function() {
    this._mouse_down = false;
    
    var index = this._currentMap._zoom_index - 1;
    if (this._zoom_index == index) return this;
    if (this._maps[index] == null) return this;
    
    var bounds = VisicomUtils.getBounds(this._viewport);
    var coords = {x: this._width / 2 + bounds.left - VisicomUtils.getClientsLeft(),
                   y: this._height / 2 + bounds.top - VisicomUtils.getClientsTop()
                 };
    if (this._zoomControl.options("smooth"))
        this._zoom_helper.run(index, coords);
    this.zoom(index);

    return this;
}

/**
 *  @public
 *  Установка центра карты и масштаба.
 *  @param {object} coords координаты центра.
 *  @param {int} [zoom_index] индекс масштаба.
 */
VMap.prototype.center = function(coords, zoom_index) {
    if (arguments.length == 0) return this._center;
    zoom_index = (zoom_index) ? parseInt(zoom_index) : zoom_index;
    
    var currentZoom = this.zoom();
    var currentCenter = this.center();
    if (!currentCenter) currentCenter = {lng: -1, lat: -1};

    if (coords instanceof VRect) {
        this._center = coords.center();
        zoom_index = this._getMinZoom(coords);
        if (!this.beforezoomchange(null, zoom_index)) return this;
        this._setZoom(zoom_index);
    } else {
        if (zoom_index != null) {
            if (!this.beforezoomchange(null, zoom_index)) return this;
            this._setZoom(zoom_index);
        }
        
        if (coords instanceof Array) this._center = coords[0];
        else this._center = coords;
    }

    var map = this._currentMap;
    if (map == null) return this;

    if (currentCenter.lng != this._center.lng || currentCenter.lat != this._center.lat) {
        this._hide();
        
        var screen = this._convertToScreenCoords(this._center);
        map._left = Math.round((this._width / 2) - screen.x);
        map._top = Math.round((this._height / 2) - screen.y);

        this._checkPosition();
    }
    
    if (currentZoom != zoom_index) {
        this._initLayers();
        this.onzoomchange();
    }
    
    this.repaint();

    return this;
}

/**
 *  Возвращает bounds видимой пользователем области.
 *  @returns {VRect} видимая область.
 */
VMap.prototype.clientRect = function() {
    var min = this._convertToGeoCoords(-this._currentMap._left,this._height - this._currentMap._top, this._currentMap._units_per_pixel);
    var max = this._convertToGeoCoords(this._width - this._currentMap._left, -this._currentMap._top, this._currentMap._units_per_pixel);

    var bounds = new VRect(min, max);

    return bounds;
}

/**
 *  @public
 *
 */
VMap.prototype._repaintLogo = function() {
    if (this._logo == null) {
        this._logo = document.getElementById('visicom_copyright_link');

        if (this._logo == null) {
            this._logo = document.createElement("a");
            this._logo.href = 'http://maps.visicom.ua';

            this._viewport.appendChild(this._logo);
        }

        this._logo.href = 'http://maps.visicom.ua/';
        this._logo.target = '_blank';
        this._logo.style.position = 'absolute';
        this._logo.style.right = '2px';
        this._logo.style.bottom = '2px';
        this._logo.style.zIndex = VisicomCommons.ZINDEX_LOGO;
        this._logo.style.marginLeft = '0px';
        this._logo.style.marginRight = '0px';
        this._logo.style.marginTop = '0px';
        this._logo.style.marginBottom = '0px';
        this._logo.style.maxWidth = 'none';
        this._logo.style.maxHeight = 'none';

        if (USER_DATA['Browser'].MSIE) {
            this._logo.innerHTML = '<img style="background: none;" border="0" width="132" height="21" src="' + VisicomCommons.API_URL + '/images/blank.gif" style="FILTER: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/logo-with-alpha.png\');" />';
        } else {
            this._logo.innerHTML = '<img style="background: none;" border="0" width="132" height="21"  src="' + VisicomCommons.API_URL + '/images/logo-with-alpha.png">';
        }
    }
}

/**
 *  Изменение размеров области видимости.
 */
VMap.prototype._checkResize = function() {
    this._width = this._viewport.offsetWidth || parseInt(this._viewport.style.width);
    this._height = this._viewport.offsetHeight || parseInt(this._viewport.style.height);
    
    this._viewport_inner.style.width = this._width + 'px';
    this._viewport_inner.style.height = this._height + 'px';

    this._map_div.style.width = this._width + VMapTile.TILE_WIDTH * 2 + 'px';
    this._map_div.style.height = this._height + VMapTile.TILE_HEIGHT * 2 + 'px';

    // Перерисовываем "копирайт"
    this._repaintLogo();
}

/**
 *  Инициализация индекса объектов.
 */
VMap.prototype._initObjectsIndex = function() {
    delete(this._objects_index);
    this._objects_index = [];
}

/**
 *  Добавление объекта в индекс.
 *  @param {VMapObject}
 */
VMap.prototype._addObject = function(object) {
    if (this._currentMap == null) return;
    var map = this._currentMap;

    var bounds = object.bounds();
    if (bounds == null) return;

    var min = bounds.leftBottom();
    var max = bounds.rightTop();

    // Экранные координаты
    var coords = this._convertToScreenCoords(min);

    // Определяем индексы tile'а
    var min_i = Math.floor((Math.abs(coords.x)) / VMapTile.TILE_WIDTH);
    var min_j = Math.floor((map._height - coords.y) / VMapTile.TILE_HEIGHT);

    coords = this._convertToScreenCoords(max);

    // Определяем индексы tile'а
    var max_i = Math.floor((Math.abs(coords.x)) / VMapTile.TILE_WIDTH);
    var max_j = Math.floor((map._height - coords.y) / VMapTile.TILE_HEIGHT);

    for (var i = min_i; i <= max_i; i++)
        for (var j = min_j; j <= max_j; j++)
            this._addObjectToIndex(i, j, object);
}

/**
 *  Удаление объекта из индекса.
 *  @param {VMapObject}
 */
VMap.prototype._removeObject = function(object) {
    var bounds = object.bounds();
    if (bounds == null) return;

    var min_point = bounds.leftBottom();
    var max_point = bounds.rightTop();

    // Экранные координаты
    var coords = this._convertToScreenCoords(min_point);

    // Определяем индексы tile'а
    var min_i = Math.floor((Math.abs(coords.x)) / VMapTile.TILE_WIDTH);
    var min_j = Math.floor((this._currentMap._height - coords.y) / VMapTile.TILE_HEIGHT);

    coords = this._convertToScreenCoords(max_point);

    // Определяем индексы tile'а
    var max_i = Math.floor((Math.abs(coords.x)) / VMapTile.TILE_WIDTH);
    var max_j = Math.floor((this._currentMap._height - coords.y) / VMapTile.TILE_HEIGHT);

    for (var i = min_i; i <= max_i; i++)
        for (var j = min_j; j <= max_j; j++)
            this._removeObjectFromIndex(i, j, object);
}

/**
 *  Добавляет объект в индекс.
 *  @param {int} i индекс по оси X.
 *  @param {int} j индекс по оси Y.
 *  @param {object} object объект.
 */
VMap.prototype._addObjectToIndex = function(i, j, object) {
    var array = this._objects_index[i];
    if (array == null)
        this._objects_index[i] = [];
    array = this._objects_index[i][j];
    if (array == null)
        array = this._objects_index[i][j] = [];

    // Добавляем элемент в конец массива
    this._objects_index[i][j].push(object);
}

/**
 *  Удаляет объект из индекса.
 *  @param {int} i индекс по оси X.
 *  @param {int} j индекс по оси Y.
 *  @param {object} object объект.
 */
VMap.prototype._removeObjectFromIndex = function(i, j, object) {
    var array = this._objects_index[i];
    if (array == null) return;

    array = array[j];
    if (array == null) return;

    for (var _i = 0; _i < array.length; _i++) {
        var _object = array[_i];
        if (object != _object) continue;

        // Удаляем элемент из массива
        array.splice(_i, 1);
        break;
    }
}

VMap.prototype.mapname = function() {
    return VisicomCommons.MAP_NAME;
}

/**
 *  @public
 *  Меняет язык отображения данных.
 *  @param {String} language селектор языка (ua, ru, en).
 *  @param {function} callback callback.
 */
VMap.prototype.language = function(language, callback) {
    if (arguments.length == 0) return this._language;
    
    var _this = this;

    VRemoteCall.request("language", this._mapName + "_" + language,
        function(data) {
            var min = VisicomCommons.convertToGeoProjection(data.bounds[0].x, data.bounds[0].y);
            var max = VisicomCommons.convertToGeoProjection(data.bounds[1].x, data.bounds[1].y);
            _this._bounds = new VRect(min, max);

            var zooms = data.zooms;
            for (var i = 0; i < zooms.length; i++) {
                var zoom = zooms[i];
                var mapZoom = _this._maps[i];

                mapZoom._base_url = zoom.base_url;
                mapZoom._units_per_pixel = zoom.units_per_pixel;
            }

            _this._language = language;

            _this._hideTiles();
            _this.repaint();
            
            if (callback != null) callback();
        }
    );
    
    return this;
}

/**
 *  Преобразование логических координат в координаты экрана.
 *  @param {object} coords координата.
 *  @param {object} units_per_pixel координата.
 *  @returns {object} точка на экране.
 */
VMap.prototype._convertToScreenCoords = function(coords, units_per_pixel) {
    var upp = (units_per_pixel != null) ? units_per_pixel : this._currentMap._units_per_pixel;
    
    var mercator = VisicomCommons.convertFromGeoProjection(coords);
    mercator.x += this.__maxLng;
    mercator.y = this.__maxLat - mercator.y;
    
    var x = mercator.x / upp;
    var y = mercator.y / upp;
    
    return {x: x, y: y};
}

/**
 *  Преобразование экранных координат в географические.
 *  @param {int} dx расстояние по X в координатах экрана.
 *  @param {int} dy расстояние по Y в координатах экрана.
 *  @param {float} units_per_pixel
 *  @returns {Object} точка в локальной системе координат.
 */
VMap.prototype._convertToGeoCoords = function(dx, dy, units_per_pixel) {
    var upp = (units_per_pixel != null) ? units_per_pixel : this._currentMap._units_per_pixel;
    
    var x = dx * upp - this.__maxLng;
    var y = this.__maxLat - dy * upp;
    
    var geo = VisicomCommons.convertToGeoProjection(x, y);
    
    return geo;
}

/**
 *  Вовзращает минимальный индекс масштаба для объекта VRect.
 *  @param {VRect} bounds объект VRect.
 */
VMap.prototype._getMinZoom = function(bounds) {
    var zoom_index = this._maps.length - 1;
    if (bounds.leftBottom().lng == bounds.rightTop().lng && bounds.leftBottom().lat == bounds.rightTop().lat) return zoom_index;

    for (var i = this._maps.length - 1; i >= 0; i--) {
        var map = this._maps[i];

        var screenMinCoords = this._convertToScreenCoords(bounds.leftBottom(), map._units_per_pixel);
        var screenMaxCoords = this._convertToScreenCoords(bounds.rightTop(), map._units_per_pixel);

        var screen_object_width = screenMaxCoords.x - screenMinCoords.x;
        var screen_object_height = screenMinCoords.y - screenMaxCoords.y;

        if ((this._width >= screen_object_width) && (this._height >= screen_object_height)) {
            zoom_index = map._zoom_index;
            break;
        }
    }

    return zoom_index;
}

/**
 *  Добавляет объект на карту.
 *  @param {VMapObject} object объект.
 */
VMap.prototype._pre_add = function(object) {
    if (object instanceof VCustomTileLayer) {
        this._customTileLayers.push(object);
        object._parent_map = this;
    }

    object._init();
}

VMap.prototype._pre_remove = function(object) {
    if (object instanceof VCustomTileLayer) {
        var index = this._customTileLayers.indexOf(object);
        this._customTileLayers.splice(index, 1);
    }
}
/**
 *  Инициализация слоев.
 */
VMap.prototype._initLayers = function() {
    this._childs.forEach(
        function(child) {
            child._init();
        }
    );
}

/**
 *  Скрытие слоев.
 */
VMap.prototype._hideLayers = function() {
    var map = this._currentMap;

    for (var i = map._current_min_i; i <= map._current_max_i; i++)
        for (var j = map._current_min_j; j <= map._current_max_j; j++) {
            var arr = this._objects_index[i];
            if (arr == null) continue;
            arr = arr[j];
            if (arr == null) continue;

            arr.forEach(
                function(object) {
                    if (object.displayed())
                        object._hide();
                }
            );
        }
}



VMap.string2lnglat = function(string) {
    var arr;
    
    // 48.6702 N, 33.1293 E
    if ((arr = /(\d+\.\d+)\s*N\s*,\s*(\d+\.\d+)\s*E/.exec(string)) != null) {
        return {lng: parseFloat(arr[2]), lat: parseFloat(arr[1])};
    }
    // 48.6702, 33.1293
    else if ((arr = /(\d+\.\d+)\s*,\s*(\d+\.\d+)/.exec(string)) != null) {
        return {lng: parseFloat(arr[2]), lat: parseFloat(arr[1])};
    }
    // 48.6702 33.1293
    else if ((arr = /(\d+\.\d+)\s*(\d+\.\d+)/.exec(string)) != null) {
        return {lng: parseFloat(arr[2]), lat: parseFloat(arr[1])};
    }
    
    return {lng: 0, lat: 0};
}// ---------------------------------------------------------
// VMapZoom class
// Слой карты заданного масштаба.
// ---------------------------------------------------------

VMapZoom.prototype._map;

// Границы карты
VMapZoom.prototype._left;
VMapZoom.prototype._top;
VMapZoom.prototype._width;
VMapZoom.prototype._height;
/** Базовый URL для рисунка. */
VMapZoom.prototype._base_url;
/** Индекс zoom'a. */
VMapZoom.prototype._zoom_index;
VMapZoom.prototype._units_per_pixel;
VMapZoom.prototype._screenOffset;

/** Количество участков по горизонтале. */
VMapZoom.prototype._x_tiles_count;
/** Количество участков по вертикале. */
VMapZoom.prototype._y_tiles_count;

VMapZoom.prototype._tiles_map = [];

// Граничные точки видимых элементов массива _tiles
VMapZoom.prototype._current_min_i = null;
VMapZoom.prototype._current_min_j = null;
VMapZoom.prototype._current_max_i = null;
VMapZoom.prototype._current_max_j = null;

/**
 *  Слой карты определенного zoom'a.
 *  @param {int} width длинна карты.
 *  @param {int} height высота карты.
 *  @param {int} zoom_index индекс масштаба.
 *  @param {VMap} map карта.
 *  @constructor
 */
function VMapZoom(width, height, zoom_index, map) {
    this._width = width;
    this._height = height;
    this._zoom_index = zoom_index;

    this._map = map;

    this._x_tiles_count = Math.round(this._width / VMapTile.TILE_WIDTH);
    this._y_tiles_count = Math.round(this._height / VMapTile.TILE_HEIGHT);

    this._left = Math.round((map._width - this._width) / 2);
    this._top = Math.round((map._height - this._height) / 2);

    this._tiles_map = [];

    return this;
}

VMapZoom.prototype._check_tiles = function() {
    for (var i = this._current_min_i; i <= this._current_max_i; i++) {
        for (var j = this._current_min_j; j <= this._current_max_j; j++) {
            if (this._tiles_map[i] == null) return;
            if (!this._tiles_map[i][j]) return;
        }
    }
    
    this._map.onpaint();
}

VMapZoom.prototype._tile_loaded = function(i, j) {
    if (this._tiles_map[i] == null) this._tiles_map[i] = [];
    this._tiles_map[i][j] = true;

    this._check_tiles();
}
// ---------------------------------------------------------
// VMapTile class
// Участок карты.
// ---------------------------------------------------------


VMapTile.TILE_WIDTH = 256;
VMapTile.TILE_HEIGHT = 256;

VMapTile.TMS_NUMBER = 0;
VMapTile.MAX_TMS_NUMBER = 2;

/**
 *  Tile.
 *  @param {int} x индекс участка по горизонтале.
 *  @param {int} y индекс участка по вертикале.
 *  @param {VMap} map карта к которой принадлежит участок.
 *  @constructor
 */
function VMapTile(x, y, map) {
    this._x = x;
    this._y = y;

    this._left = this._x * VMapTile.TILE_WIDTH;
    this._top = (map._currentMap._height - VMapTile.TILE_HEIGHT) - this._y * VMapTile.TILE_HEIGHT;
    this._parent_map = map;

    this._error_counter = 0;

    return this;
}

// Карта родитель
VMapTile.prototype._parent_map;
// Индексы ячейки
VMapTile.prototype._x;
VMapTile.prototype._y;
// Координаты на экране
VMapTile.prototype._left;
VMapTile.prototype._top;
VMapTile.prototype._width;
VMapTile.prototype._height;

/** HTML элемент &lt;div&gt;. */
VMapTile.prototype._tile = null;
/** Флаг видимости. */
VMapTile.prototype._visible = false;

VMapTile.prototype.getX = function() { return this._x; }
VMapTile.prototype.getY = function() { return this._y; }
VMapTile.prototype.getLeft = function() { return this._left; }
VMapTile.prototype.getTop = function() { return this._top; }
VMapTile.prototype.isVisible = function() { return this._visible; }

VMapTile.prototype._error_counter;

/**
 *  Возвращает элемент &lt;div&gt;.
 *  @returns {HTMLElement} HTML элемент.
 */
VMapTile.prototype.getDOMElement = function() { return this._tile; }

VMapTile.getTmsNumber = function(x, y) {
    return ((x % 2 > 0) ? 0 : 1) + ((y % 2 > 0) ? 0 : 1);
}
/**
 *  Отображение участка.
 */
VMapTile.prototype.show = function() {
    if (this._tile == null) {
        // Создание image'a
        this._tile = document.createElement('img');

        this._tile.id = 'tile_' + this._parent_map.zoom() + '_' + this._x + '_' + this._y;

        this._tile.style.left = '0px';
        this._tile.style.top = '0px';
        this._tile.style.width = VMapTile.TILE_WIDTH;
        this._tile.style.height = VMapTile.TILE_HEIGHT;
        this._tile.style.position = 'absolute';
        this._tile.style.background = 'none';
        this._tile.style.border = '0px';
        //this._tile.style.border = '1px dotted black';

        this._tile.style.display = 'none';

        var src = this._parent_map._currentMap._base_url;
        src = src.replace(/tms\./, "tms" + VMapTile.getTmsNumber(this._x, this._y) + ".");
        this._tile.src = src + '/' + this._x + '/' + this._y + '.png';

        if (this._tile.complete) {
            this._tile.style.display = 'block';
            //this._parent_map._currentMap._tile_loaded(this._x, this._y);
        }
        var _this = this;
        this._tile.onload = function() {
            _this._tile.style.display = 'block';
            _this._parent_map._currentMap._tile_loaded(_this._x, _this._y);
        }

        this._tile.style.zIndex = '101';
        
        this._tile.onerror = function(e) {
            _this._error_counter++;
            if (_this._error_counter > VMapTile.MAX_TMS_NUMBER) {
                _this._tile.onerror = null;
                return;
            }

            VMapTile.TMS_NUMBER++;
            if (VMapTile.TMS_NUMBER > VMapTile.MAX_TMS_NUMBER) VMapTile.TMS_NUMBER = 0;

            src = _this._parent_map._currentMap._base_url;
            src = src.replace(/tms\./, "tms" + VMapTile.TMS_NUMBER + ".");
            _this._tile.src = src + '/' + _this._x + '/' + _this._y + '.png';
        }
    }
    
    this._parent_map.getTilesDOMElement().appendChild(this._tile);

    this._visible = true;
}

/**
 *  Перерисовка учатска.
 */
VMapTile.prototype.repaint = function() {
    if (this._tile == null) return;

    var left = 0;
    var top = 0;
    /*if (this._parent_map._zoom_helper != null) {
        left = this._parent_map._zoom_helper.offset.left;
        top = this._parent_map._zoom_helper.offset.top;
    }*/
    
    this._tile.style.left = left + this._parent_map._currentMap._left + this._left + VMapTile.TILE_WIDTH + 'px';
    this._tile.style.top = top + this._parent_map._currentMap._top + this._top + VMapTile.TILE_HEIGHT + 'px';
}

/**
 *  Скрытие участка.
 */
VMapTile.prototype.hide = function() {
    try { this._parent_map.getTilesDOMElement().removeChild(this._tile); } catch(err) { }
    
    this._visible = false;
}

// ---------------------------------------------------------
// VMapHelper class
// 
// ---------------------------------------------------------

VZoomHelper.prototype.map;
VZoomHelper.prototype.tiles;

VZoomHelper.prototype.base_width;
VZoomHelper.prototype.base_height;

VZoomHelper.prototype.current_width;
VZoomHelper.prototype.current_height;

VZoomHelper.prototype.next_width;
VZoomHelper.prototype.next_height;

VZoomHelper.prototype.current_zoom;
VZoomHelper.prototype.dest_zoom;

VZoomHelper.prototype.div;

VZoomHelper.prototype.is_run;
VZoomHelper.prototype.onpaint;
VZoomHelper.prototype.center;

VZoomHelper.prototype._isMousedown;
VZoomHelper.prototype._mouseCoords;
//VZoomHelper.prototype.offset;

VZoomHelper.prototype.coords;
VZoomHelper.prototype.counter;
//VZoomHelper.prototype.atempts;
VZoomHelper.prototype.clear_timer;
VZoomHelper.prototype.steps;
VZoomHelper.steps = 8;
VZoomHelper.delay = 40;
//VZoomHelper.step_delay = 10;
//VZoomHelper.zoom_in_koef = 1 / VZoomHelper.steps;
//VZoomHelper.zoom_out_koef = 1 / 2 / VZoomHelper.steps;

/**
 *  
 *  @constructor
 */
function VZoomHelper(map) {
    this.map = map;

    this._isMousedown = false;
    
    var _this = this;
    this.map.onpaint(
        function() {
            _this.onpaint = true;
            if (_this.div == null) return;
            if (_this.is_run) return;

            var id = Math.random();
            VZoomHelper.INSTANCES[id] = _this;
            _this.clear_timer = setTimeout("VZoomHelper.clear(" + id + ");", 10);
        }
    );
}

VZoomHelper.prototype.create = function() {
    this.tiles = [];
    this.center = this.map.center();
    
    var map = this.map._currentMap;
    
    if (this.div != null)
        this.map.getViewport().removeChild(this.div);

    this.div = document.createElement('div');
    this.div.style.position = 'absolute';
    this.div.style.left = '0px';
    this.div.style.top = '0px';
    this.div.style.width = this.map._width + 'px';
    this.div.style.height = this.map._height + 'px';
    this.div.style.zIndex = VisicomCommons.ZINDEX_SMOOTH_LAYER;
    //this.div.style.border = '1px dotted black';
    this.map.getViewport().appendChild(this.div);

    var _this = this;
    this._mouseCoords = {};
    VisicomUtils.addHandler(document, 'mousedown',
        function(e) {
            if(!e) e = window.event;
            _this.mousedown(e);
        }
    );
    VisicomUtils.addHandler(document, 'mousemove',
        function(e) {
            if (!_this._isMousedown) return;
            _this.mousemove(e);
        }
    );
    VisicomUtils.addHandler(document, 'mouseup',
        function(e) {
            if(!e) e = window.event;

            _this._isMousedown = false;

            if (e.stopPropagation) e.stopPropagation();
            else e.cancelBubble = true;
            if (e.preventDefault) e.preventDefault();
            else e.returnValue = false;
        }
    );
    
    for (var i = map._current_min_i; i <= map._current_max_i; i++) {
        if (this.map._tiles[i] == null) continue;

        for (var j = map._current_min_j; j <= map._current_max_j; j++) {
            var tile = this.map._tiles[i][j];

            var element = tile.getDOMElement();
            tile.left = element.offsetLeft - VMapTile.TILE_WIDTH;
            tile.top = element.offsetTop - VMapTile.TILE_HEIGHT;
            tile.width = element.offsetWidth;
            tile.height = element.offsetHeight;
            this.setData(tile);
            
            this.div.appendChild(element);

            this.tiles.push(tile);
        }
    }
}

VZoomHelper.prototype.mousemove = function(e) {
    if(!e) e = window.event;
    if (this.div == null) return;
    
    this.div.style.left = this.div.offsetLeft + (e.clientX - this._mouseCoords.x) + 'px';
    this.div.style.top = this.div.offsetTop + (e.clientY - this._mouseCoords.y) + 'px';

    this._mouseCoords.x = e.clientX;
    this._mouseCoords.y = e.clientY;

    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;
}

VZoomHelper.prototype.mousedown = function(e) {
    if (this.div == null) return;
    
    this._isMousedown = true;
    
    this._mouseCoords.x = e.clientX;
    this._mouseCoords.y = e.clientY;

    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;   
}

VZoomHelper.prototype.setNext = function() {
    var znak = (this.dest_zoom > this.current_zoom) ? 1 : -1;
    //var koef = (this.dest_zoom > this.current_zoom) ? VZoomHelper.zoom_in_koef : VZoomHelper.zoom_out_koef;
    
    var razn = Math.abs(this.dest_zoom - this.current_zoom);
    this.steps = VZoomHelper.steps;
    var mul = Math.pow(Math.abs(this.dest_zoom - this.current_zoom), 2) / this.steps;
    if (znak < 0) mul = mul / 2;
    
    this.next_width = this.current_width + znak * mul * this.current_width;
    this.next_height = this.current_height + znak * mul * this.current_height;
}

VZoomHelper.prototype.setData = function(tile) {
    var element = tile.getDOMElement();
    
    element.style.left = tile.left + 'px';
    element.style.top = tile.top + 'px';
    element.style.width = tile.width + 'px';
    element.style.height = tile.height + 'px';
}

VZoomHelper.prototype.run = function(zoom, coords) {
    this.dest_zoom = zoom;
    this.coords = coords;
    if (this.is_run) return;

    if (this.clear_timer != null) {
        clearTimeout(this.clear_timer);
        this.clear_timer = null;
    }
    this.create();

    this.current_zoom = this.map.zoom();
    this.current_width = VMapTile.TILE_WIDTH;
    this.current_height = VMapTile.TILE_HEIGHT;
    this.base_width = VMapTile.TILE_WIDTH;
    this.base_height = VMapTile.TILE_WIDTH;

    this.next_width = this.current_width;
    this.next_height = this.current_height;
    this.counter = 0;
    //this.atempts = 0;

    this.map.getDOMElement().style.display = 'none';
    this.map.getTilesDOMElement().style.display = 'none';
    
    var id = Math.random();
    VZoomHelper.INSTANCES[id] = this;

    setTimeout("VZoomHelper.onTimer(" + id + ");", 20);
    this.is_run = true;
    this.onpaint = false;
}

VZoomHelper.INSTANCES = [];
VZoomHelper.onTimer = function(id) {
    var helper = VZoomHelper.INSTANCES[id];

    helper.counter++;
    if (helper.counter >= helper.steps - 1) {
        VZoomHelper.INSTANCES[id] = null;
        helper.stop();
        return;
        
        /*var znak = (helper.dest_zoom > helper.current_zoom) ? 1 : -1;
        helper.current_zoom += znak;
        
        if (helper.current_zoom == helper.dest_zoom) {
            VZoomHelper.INSTANCES[id] = null;
            helper.stop();
            return;
        }
        
        helper.base_width = helper.current_width;
        helper.base_height = helper.current_height;

        helper.counter = 0;
        //helper.atempts++;

        /*if (helper.atempts >= 2) {
            VZoomHelper.INSTANCES[id] = null;
            helper.stop();
            return;
        }*/
    }

    helper.setNext();
    var bounds = VisicomUtils.getBounds(helper.map._viewport);

    for (var i in helper.tiles) {
        if (i == "forEach" || i == "indexOf" || i == "") continue;
        
        var tile = helper.tiles[i];
        
        tile.left += ((helper.coords.x - tile.left - bounds.left + VisicomUtils.getClientsLeft()) / helper.current_width) * (helper.current_width - helper.next_width);
        tile.top += ((helper.coords.y - tile.top - bounds.top + VisicomUtils.getClientsTop()) / helper.current_height) * (helper.current_height - helper.next_height);
        
        tile.width = helper.next_width + 1;
        tile.height = helper.next_height + 1;

        helper.setData(tile);
    }

    helper.current_width = helper.next_width;
    helper.current_height = helper.next_height;
    
    setTimeout("VZoomHelper.onTimer(" + id + ");", VZoomHelper.delay);
}

VZoomHelper.prototype.stop = function() {
    this.is_run = false;

    this.map.getDOMElement().style.display = 'block';
    this.map.getTilesDOMElement().style.display = 'block';

    if (!this.onpaint) return;
    
    var id = Math.random();
    VZoomHelper.INSTANCES[id] = this;
    this.clear_timer = setTimeout("VZoomHelper.clear(" + id + ");", 20);
}

VZoomHelper.clear = function(id) {
    var helper = VZoomHelper.INSTANCES[id];

    try {
        helper.map.getViewport().removeChild(helper.div);
    } catch (err) { }
    
    helper.div = null;
    VZoomHelper.INSTANCES[id] = null;

    helper.is_run = false;
}
// ---------------------------------------------------------
// VisicomUtils class
// Участок маршрута.
// ---------------------------------------------------------

var VisicomUtils = (function() {

    function getBounds(element) {
        if (element == null) return(null);

        var left = element.offsetLeft;
        var top = element.offsetTop;
        for (var parent = element.offsetParent; parent; parent = parent.offsetParent) {
            left += parent.offsetLeft/* - parent.scrollLeft*/;
            top += parent.offsetTop/* - parent.scrollTop*/
        }

        return { left: left, top: top, width: element.offsetWidth, height: element.offsetHeight };
    }

    function getClientsTop() {
        var result = self.pageYOffset || (document.documentElement && document.documentElement.scrollTop) || (document.body && document.body.scrollTop);
        return result;
    }

    function getClientsLeft() {
        var result = self.pageXOffset || (document.documentElement && document.documentElement.scrollLeft) || (document.body && document.body.scrollLeft);
        return result;
    }
    
    function clearStyles(element) {
        if (element.style.align)
            element.style.align = 'none';
        if (element.style.backgroundColor)
            element.style.backgroundColor = 'none';
        element.style.backgroundImage = 'none';
        if (element.style.margin)
            element.style.margin = 'none';
        element.style.marginBottom = '0px';
        element.style.marginLeft = '0px';
        element.style.marginRight = '0px';
        element.style.marginLeft = '0px';
        if (element.style.padding)
            element.style.padding = 'none';
        if (element.style.border)
            element.style.border = 'none';
        element.style.fontSize = '0px';
        element.style.outline = 'none';
        element.style.verticalAlign = 'baseline';
    }
    
    function addHandler(object, event, handler, useCapture) {
        if (object.addEventListener) {
            object.addEventListener(event, handler, useCapture ? useCapture : false);
        } else if (object.attachEvent) {
            object.attachEvent('on' + event, handler);
        }
    }

    // Взято отсюда http://codesnippets.joyent.com/posts/show/536
    function toHex(dec) {
        // create list of hex characters
        var hexCharacters = "0123456789ABCDEF"
        // if number is out of range return limit
        if (dec < 0)
                return "00"
        if (dec > 255)
                return "FF"
        // decimal equivalent of first hex character in converted number
        var i = Math.floor(dec / 16)
        // decimal equivalent of second hex character in converted number
        var j = dec % 16
        // return hexadecimal equivalent
        return hexCharacters.charAt(i) + hexCharacters.charAt(j)
    }
    
    function copy(source, dest) {
        for (var prop in source) {
            dest[prop] = source[prop];
        }
    }

    return { getBounds          : getBounds,
             getClientsTop      : getClientsTop,
             getClientsLeft     : getClientsLeft,
             addHandler         : addHandler,
             toHex              : toHex,
             clearStyles        : clearStyles,
             copy               : copy
           };
})();// ---------------------------------------------------------
// VRect class
// Область на карте.
// Задается двумя локальными точками {lng, lat}.
// ---------------------------------------------------------

/** extends VMapObject */
VRect.prototype = new VMapObject();

VRect.prototype.leftBottom = function() { return this._coords[0]; }
VRect.prototype.rightTop = function() { return this._coords[1]; }

/**
 *  @category = Map
 *  Область на карте.
 *  @class Прямоугольная область на карте.
 *  Задается 2-мя точками в локальных координатах системы.
 *  @constructor
 */
function VRect() {
    VMapObject.call(this); // extends

    if (arguments.length == 1)
        this._coords = (arguments[0] instanceof Array) ? this.create(arguments[0]) : this.create([arguments[0]]);
    else
    if (arguments.length == 2) {
        this._coords[0] = arguments[0];
        this._coords[1] = arguments[1];
    }
    
    return this;
}

/**
 *  @public
 *  Проверяет входит ли точка в заданную область.
 *  @params {lng, lat} point точка.
 */
VRect.prototype.contains = function(coords) {
    return (coords.lng >= this._coords[0].lng) && (coords.lng <= this._coords[1].lng) &&
           (coords.lat >= this._coords[0].lat) && (coords.lat <= this._coords[1].lat);
}

/**
 *  Создает объект Bounds.
 *  @params {lng, lat} point перечень точек.
 */
VRect.prototype.create = function(coords) {
    var min_lng = null;
    var min_lat = null;
    var max_lng = null;
    var max_lat = null;

    for (var i = 0; i < coords.length; i++) {
        var point = coords[i];
        
        var lng = point.lng;
        var lat = point.lat;

        if ((lng < min_lng) || (min_lng == null))
            min_lng = lng;
        if ((lat < min_lat) || (min_lat == null))
            min_lat = lat;
        if ((lng > max_lng) || (max_lng == null))
            max_lng = lng;
        if ((lat > max_lat) || (max_lat == null))
            max_lat = lat;
    }

    return [{lng: min_lng, lat: min_lat}, {lng: max_lng, lat: max_lat}];
}

/**
 *  
 *  @return {lng, lat}
 */
VRect.prototype.center = function() {
    var lng = this.leftBottom().lng + (this.rightTop().lng - this.leftBottom().lng) / 2;
    var lat = this.leftBottom().lat + (this.rightTop().lat - this.leftBottom().lat) / 2;

    return { lng: lng, lat: lat };
}

// ---------------------------------------------------------
// VZoomControl class
// Элемент управения масштабом.
// ---------------------------------------------------------

/** extends VMapObject */
VZoomControl.prototype = new VMapObject();


VZoomControl.prototype._zoom_in;
VZoomControl.prototype._ruler;
VZoomControl.prototype._runner;
VZoomControl.prototype._zoom_out;

VZoomControl.ZOOM_IN_WIDTH = 17;
VZoomControl.ZOOM_OUT_WIDTH = 17;
VZoomControl.ZOOM_IN_HEIGHT = 17;
VZoomControl.ZOOM_OUT_HEIGHT = 17;

VZoomControl.RULER_ITEM_WIDTH = 17;
VZoomControl.RULER_ITEM_TOP_HEIGHT = 16;
VZoomControl.RULER_ITEM_MIDDLE_HEIGHT = 14;
VZoomControl.RULER_ITEM_BOTTOM_HEIGHT = 16;

/**
 *  Элемент управения масштабом.
 *  @param {VMap} map карта.
 *  @param {object} options object.
 *  @constructor
 */
function VZoomControl(map, options) {
    VMapObject.call(this, null, options);
    
    this._parent = map;

    return this;
}

VZoomControl.prototype.getDOMElement = function() { return this._div; }

VZoomControl.prototype._createRuler = function() {
    this._ruler = document.createElement('div');
    this._ruler.style.position = 'relative';
    this._ruler.style.background = 'none';
    this._ruler.style.zIndex = VisicomCommons.ZINDEX_CONTROLS;
    this._ruler.style.padding = '0px';
    this._ruler.style.border = '0px';
    this._ruler.style.width = VZoomControl.RULER_ITEM_WIDTH + 'px';
    //this._ruler.style.border = '1px dotted black';
    this._ruler.style.cursor = 'pointer';

    var max = (this.options("max") != null) ? this.options("max") : this._parent._maps.length - 1;
    var min = (this.options("min") != null) ? this.options("min") : 0;
    var zooms = max - min;
    this._ruler.style.height = ((zooms + 1) * VZoomControl.RULER_ITEM_MIDDLE_HEIGHT) + 'px';

    for (var i = zooms; i >= 0; i--) {
        var item = document.createElement('img');
        this._ruler.appendChild(item);
        
        item.style.position = 'absolute';
        item.style.border = '0px';
        //item.style.border = '1px dotted black';
        item.style.cursor = 'pointer';
        item.style.margin = '0px';
        
        item.style.left = '0px';
        var klop = zooms - i;

        if (i == zooms) {
            item.src = VisicomCommons.API_URL + '/images/zoom/ruler-top.gif';
            item.style.width = VZoomControl.RULER_ITEM_WIDTH + 'px';
            item.style.height = VZoomControl.RULER_ITEM_TOP_HEIGHT + 'px';

            item.style.top = (klop * VZoomControl.RULER_ITEM_MIDDLE_HEIGHT) + 'px';
        } else
        if (i == 0) {
            item.src = VisicomCommons.API_URL + '/images/zoom/ruler-bottom.gif';
            item.style.width = VZoomControl.RULER_ITEM_WIDTH + 'px';
            item.style.height = VZoomControl.RULER_ITEM_BOTTOM_HEIGHT + 'px';

            item.style.top = (klop * VZoomControl.RULER_ITEM_MIDDLE_HEIGHT) + 'px';
        } else {
            item.src = VisicomCommons.API_URL + '/images/zoom/ruler-middle.gif';
            item.style.width = VZoomControl.RULER_ITEM_WIDTH + 'px';
            item.style.height = VZoomControl.RULER_ITEM_MIDDLE_HEIGHT + 'px';
            
            item.style.top = (klop * VZoomControl.RULER_ITEM_MIDDLE_HEIGHT) + 'px';
        }
        
    }
    
    
    this._runner = document.createElement('div');
    var img = document.createElement('img');
    img.src = VisicomCommons.API_URL + '/images/zoom/runner.gif';
    img.style.border = '0px';
    this._runner.appendChild(img);
    this._runner.style.position = 'absolute';
    this._runner.style.width = VZoomControl.RULER_ITEM_WIDTH + 'px';
    this._runner.style.height = VZoomControl.RULER_ITEM_TOP_HEIGHT + 'px';
    this._runner.style.border = '0px';
    this._runner.style.cursor = 'pointer';
    this._ruler.appendChild(this._runner);

    var _this = this;
    var isMousedown = false;

    function getZoomIndex(event) {
        var bounds = VisicomUtils.getBounds(_this._ruler);
        var index = Math.floor((event.clientY - bounds.top + VisicomUtils.getClientsTop()) / VZoomControl.RULER_ITEM_MIDDLE_HEIGHT);
        index = max - index;
        return (index > max ? max : (index < min ? min : index));
    }
    
    this._ruler.onmousedown = function(e) {
        if(!e) e = window.event;
        
        var index = getZoomIndex(e);
        var map = _this._parent;
        if (index < min || index > max) {
            map.zoom((index < min) ? 0 : max);
            return;
        }
        
        if (map.zoom() != index)
            map.zoom(index);
        isMousedown = true;

        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
    }
    VisicomUtils.addHandler(document, 'mouseup',
        function(e) {
            if (!isMousedown) return;

            if(!e) e = window.event;

            var index = getZoomIndex(e);
            var map = _this._parent;
            if (index < min || index > max) {
                map.zoom((index < min) ? 0 : max);
                return;
            }

            if (map.zoom() != index)
                map.zoom(index);

            isMousedown = false;
        }
    );
    this._ruler.onmousemove = function(e) {
        if (!isMousedown) return;
        
        if(!e) e = window.event;

        var index = getZoomIndex(e);
        var map = _this._parent;
        if (index < min || index > max) {
            map.zoom((index < min) ? 0 : max);
            return;
        }
        
        _this.position(index);
        
        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
    }

    this._div.appendChild(this._ruler);
}

VZoomControl.prototype._createDOMElement = function() {
    this._div = document.createElement('div');
    
    this._div.style.position = 'absolute';
    this._div.style.background = 'none';
    this._div.style.zIndex = VisicomCommons.ZINDEX_CONTROLS;
    this._div.style.padding = '0px';
    this._div.style.margin = '0px';
    this._div.style.border = '0px';
    //this._div.style.border = '1px dotted black';
    
    this._div.style.left = (this.options("left") ? this.options("left") : 10) + 'px';
    this._div.style.top = (this.options("top") ? this.options("top") : 10) + 'px';

    var _this = this;

    this._zoom_in = document.createElement('div');
    var img = document.createElement('img');
    img.src = VisicomCommons.API_URL + '/images/zoom/zoom-in.gif';
    img.style.border = '0px';
    this._zoom_in.appendChild(img);
    this._zoom_in.style.position = 'relative';
    this._zoom_in.style.width = VZoomControl.ZOOM_IN_WIDTH + 'px';
    this._zoom_in.style.height = VZoomControl.ZOOM_IN_HEIGHT + 'px';
    this._zoom_in.src = VisicomCommons.API_URL + '/images/zoom/zoom-in.gif';
    this._zoom_in.style.border = '0px';
    //this._zoom_in.style.border = '1px dotted black';
    this._zoom_in.style.cursor = 'pointer';

    var max = (this.options("max") != null) ? this.options("max") : this._parent._maps.length - 1;
    var min = (this.options("min") != null) ? this.options("min") : 0;

    this._zoom_in.onclick = function(e) {
        var map = _this._parent;
        var index = map.zoom() + 1;
        if (index < min || index > max) return;
        
        _this._parent.zoomIn();
    }
    this._div.appendChild(this._zoom_in);


    this._createRuler();
    if (this.options("ruler") == false) {
        this._ruler.style.display = 'none';
        this._zoom_in.style.marginBottom = '3px';
    }

    this._zoom_out = document.createElement('div');
    img = document.createElement('img');
    img.src = VisicomCommons.API_URL + '/images/zoom/zoom-out.gif';
    img.style.border = '0px';
    this._zoom_out.appendChild(img);
    this._zoom_out.style.position = 'relative';
    this._zoom_out.style.width = VZoomControl.ZOOM_OUT_WIDTH + 'px';
    this._zoom_out.style.height = VZoomControl.ZOOM_OUT_HEIGHT + 'px';
    this._zoom_out.style.border = '0px';
    //this._zoom_out.style.border = '1px dotted black';
    this._zoom_out.style.cursor = 'pointer';
    
    this._zoom_out.onclick = function(e) {
        var map = _this._parent;
        var index = map.zoom() - 1;
        if (index < min || index > max) return;

        _this._parent.zoomOut();
    }
    this._div.appendChild(this._zoom_out);
    
    if (this.options("visible") != false)
        this._parent._viewport.appendChild(this._div);
}

VZoomControl.prototype.position = function(index) {
    if (index == null) return this;
    if (this._runner == null) return this;
    
    var max = (this.options("max") != null) ? this.options("max") : this._parent._maps.length - 1;
    var klop = max - index;
    this._runner.style.top = (klop * VZoomControl.RULER_ITEM_MIDDLE_HEIGHT) + 'px';

    return this;
}

VZoomControl.prototype.visible = function(value) {
    if (value == null) return this._visible;

    try {
        if (value)
            this._parent._viewport.appendChild(this._div);
        else
            this._parent._viewport.removeChild(this._div);
    } catch (err) { }

    this._visible = value;

    return this;
}

VZoomControl.prototype.options = function(key, value) {
    if (arguments.length == 0) return this._options;

    if (value == null) return this._options[key];

    this._options[key] = value;

    switch (key) {
        case "ruler":
            if (value)
                this._ruler.style.display = 'none';
            else
                this._ruler.style.display = 'block';
            
            break;
    }

    return this;
}
// ---------------------------------------------------------
// VMarkerIcon class
// Тип маркера.
// ---------------------------------------------------------

VMarkerIcon.prototype._width = null;
VMarkerIcon.prototype._height = null;
VMarkerIcon.prototype._image_src = null;
VMarkerIcon.prototype._alpha_enable = false;

/**
 *  @public
 *  Возвращает длинну.
 *  @returns {int} длинна.
 */
VMarkerIcon.prototype.width = function() { return this._width; }
/** @public Возвращает ширину. */
VMarkerIcon.prototype.height = function() { return this._height; }
/** @public Возвращает путь к изображению. */
VMarkerIcon.prototype.source = function() { return this._image_src; }

/**
 *  @category = Markers
 *  Изображение маркера.
 *  @class
 *  Класс VMarkerIcon позволяет задать для маркера определенное изображение.
 *  @param {int} width длинна.
 *  @param {int} height высота.
 *  @param {String} image_src путь к изображению.
 *  @param {Boolean} [alpha_enable] флаг альфа-прозрачности.
 *  @constructor
 */
function VMarkerIcon(width, height, image_src, alpha_enable) {
    this._width = width;
    this._height = height;
    this._image_src = image_src;
    if (alpha_enable != null)
        this._alpha_enable = alpha_enable;

    return this;
}

/**
 *  @public
 *  Устанавливает флаг альфа прозрачности для PNG изображений.
 *  @param {Boolean} enable true/false.
 */
VMarkerIcon.prototype.alphaEnable = function(enable) {
    if (enable == null) return this._alpha_enable;
    this._alpha_enable = enable;

    return this;
}

/** Размеры маркера по умолчанию. */
VMarkerIcon.MARKER_WIDTH = 30;
VMarkerIcon.MARKER_HEIGHT = 28;
/** Иконка маркера по умолчанию. */
var DEFAULT_MARKER_ICON = new VMarkerIcon(VMarkerIcon.MARKER_WIDTH, VMarkerIcon.MARKER_HEIGHT, VisicomCommons.API_URL + 'images/markers/marker-red-ws.png');
DEFAULT_MARKER_ICON.alphaEnable(true);
// ---------------------------------------------------------
// VMarker class
// Указатель объекта на карте.
// ---------------------------------------------------------

/** extends VMapObject */
VMarker.prototype = new VMapObject();

VMarker.prototype.type = "marker";
VMarker.prototype._isGeo = true;

/** Информационное окно. */
VMarker.prototype._info = null;
/** Всплывающая подсказка. */
VMarker.prototype._hint = null;
/** Тип маркера {@link VMarkerIcon} */
VMarker.prototype._icon = null;

/** Флаг нажатия клавиши мыши. */
VMarker.prototype._mousedown = false;
/** Текущие коориднаты мыши. */
VMarker.prototype._mouse_coords = null;
VMarker.prototype._move_delta = null;
/** Флаг движения курсора мыши. */
VMarker.prototype._moved = false;

/**
 *  @public
 *  Возвращает значение флага, указывающего на возможность перетаскивания пользователем.
 *  @param {Boolean} draggable Значение флага.
 *  @returns {Boolean} значение флага видимости маркера
 */
VMarker.prototype.draggable = function(draggable) {
    if (arguments.length == 0) return this.options("draggable");
    this._options.draggable = draggable;

    return this;
}

/** Указатель на географическую точку. */
VMarker.prototype._pointer;

/**
 *  @public
 *  Устанавливает указатель маркера.
 *  @param {} point экранная точка {x, y}.
 */
VMarker.prototype.pointer = function(point) {
    if (arguments.length == 0) return (this._pointer == undefined) ? { x : (this.icon().width() / 2), y : this.icon().height() } : this._pointer;
    
    this._pointer = point;

    return this;
}


/**
 *  @category = Markers
 *  Маркер.
 *  @class
 *  Маркер применяется для отметки определенной точки на карте.<br/>
 *  Для маркера можно задавать собственное изображение и информационное окно, описывающее данную точку.
 *  Инициализировать можно как географическими координатами так и локальными.
 *  @param {VGeoPoint} point точка на карте в географических координатах.
 *  @param {VMarkerIcon} [icon] (не обязательный параметр) изображение маркера.
 *  @constructor
 */
function VMarker(point, icon) {
    VMapObject.call(this, point);

    if( typeof(icon) == "string" )
    {
        this.info(icon);
        this._icon = DEFAULT_MARKER_ICON;
    }
    else
        this._icon = (icon != null) ? icon : DEFAULT_MARKER_ICON;
    this._mouse_coords = {};
    this._bounds = new VRect( this._coords );
    
    return this;
}

/**
 *  @public
 *  Устанавливает информационное окно.
 *  @param {VInfoWindow} info информационное окно либо его текст.
 *  @param {VInfoWindow} options опции.
 */
VMarker.prototype.info = function() {
    if (arguments.length == 0) {
        if (typeof(this._info) == "string") {
            this.info(this._info);
        }

        return this._info;
    }
    
    if (this._info != null && typeof(this._info) != "string") {
        this._info._hide();
        this.remove(this._info);
    }

    var options = {};
    
    if (arguments[0].type == "info") {
        this._info = arguments[0];
        options = this._info._options;
    } else {
        if (arguments.length == 3) {
            if (typeof(arguments[2]) != "string") VisicomUtils.copy(arguments[2], options);
            this._info = new VInfoWindow(this._coords[0], arguments[0], arguments[1], arguments[2]);
        } else
        if (arguments.length == 2) {
            if (typeof(arguments[1]) != "string") VisicomUtils.copy(arguments[1], options);
            this._info = new VInfoWindow(this._coords[0], arguments[0], arguments[1]);
        } else
            this._info = new VInfoWindow(this._coords[0], arguments[0]);
    }
    
    this.add(this._info);
    this._info.visible(false);
    
    if (options.alwaysOpen == null)
        this._info.options("alwaysOpen", false);
    if (options.canClose == null)
        this._info.options("canClose", true);
    
    this._info.__marker = this;

    return this;
}

VMarker.prototype._pre_coords = function(coords) {
    if (coords == null) return;

    // Установка координат для информационного окна
    if (this._info != null && typeof(this._info) != "string")
        this._info.coords(coords);
}

/**
 *  Создает DOM элемент &lt;img&gt;.
 */
VMarker.prototype._createDOMElement = function() {
    // Создание image'a
    this._div = document.createElement('img');

    if (this._icon == null) {
        var source = this.style("icon");
        if (source == null)
            this._icon = DEFAULT_MARKER_ICON;
        else {
            this._icon = new VMarkerIcon(24, 24, source);
        }
    }
    
    this._div.id = 'marker_' + this.coords(0).lng + this.coords(0).lat;
    this._div.style.position = 'absolute';
    this._div.width = this._icon.width();
    this._div.height = this._icon.height();
    this._div.style.zIndex = VisicomCommons.ZINDEX_MARKERS;
    this._div.style.border = '0px';
    //this._div.style.border = '1px dotted black';
    
    if (USER_DATA['Browser'].MSIE) {
        // Включение альфа-прозрачности для Internet explorer'а
        this._div.src = VisicomCommons.API_URL + '/images/blank.gif';
        this._div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this._icon.source() + "')";
    } else
        this._div.src = this._icon.source();

    this._div.style.cursor = 'pointer';
    
    var _this = this;
    this._div.onmouseout = function(e) {
        _this._onMouseOut(e);
    }
    this._div.onclick = function(e) {
        _this._onMouseClick(e);
    }
    this._div.onmousedown = function(e) {
        _this._onMouseDown(e);
    }
    this._div.onmouseup = function(e) {
        _this._onMouseUp(e);
    }
    this._div.ondblclick = function(e) {
        var btn = 0;
        if (e == undefined) {
            e = window.event;
            btn = window.event.button;
        } else
            btn = e.which;
        
        var map = _this._getMap();
        if (map == null) return;
        _this._mouseEvent("mousedblclick", null, map._getCoords(e.clientX, e.clientY), (btn == 1) ? "left" : "right");
    }
    this._div.onmouseover = function(e) {
        if(!e) e = window.event;
        
        _this.mouseover();
        
        _this._div.style.zIndex = VisicomCommons.ZINDEX_MARKERS_HOVER;

        if (_this._mousedown) return;

        var map = _this._getMap();
        if (map == null) return;
        if (map._dragged_object == _this) {
            _this._hideHint();
            return;
        }
        
        _this._showHint();
    }
}

// Обработчики DOM событий
VMarker.prototype._onMouseDown = function(e) {
    var btn = 0;
    if (e == undefined) {
        e = window.event;
        btn = window.event.button;
    } else
        btn = e.which;

    var map = this._getMap();
    if (map == null) return;

    this._moved = false;

    this._mouseEvent("mousedown", null, map._getCoords(e.clientX, e.clientY), (btn == 1) ? "left" : "right");
    
    //VInfoWindow._changeWindow();
    if (this.options("draggable")) {
        this._mouse_coords = { x: e.clientX, y: e.clientY };
        
        var geo = map._getCoords(e.clientX, e.clientY);
        var mouse = map._convertToScreenCoords(geo);
        var pointer = map._convertToScreenCoords(this.coords(0));
        this._move_delta = {dx: pointer.x - mouse.x, dy: pointer.y - mouse.y};
        
        map._dragged_object = this;
        
        if (this.info() != null) {
            this.info()._hide();
            this.info().visible(false);
        }

        this._hideHint();
        
        // Ставим маркер поверх остальных
        this._div.style.zIndex = VisicomCommons.ZINDEX_MARKERS_HOVER;

        this.startdrag();
    }

    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;
}

VMarker.prototype._onMouseUp = function(e) {
    if(!e) e = window.event;

    if (!this._moved)
        this.mouseup();
    
    var map = this._getMap();
    if (map == null) return;

    map._mouse_over = true;
    map._dragged_object = null;

    if (!this.options("draggable")) {
        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
        
        return;
    }
    
    if (this._moved) {
        this._repaint();
        
        this.mouseup();
        this.enddrag();
    }
    
    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;
}

VMarker.prototype._onMouseClick = function(e) {
    var btn = 0;
    if (e == undefined) {
        e = window.event;
        btn = window.event.button;
    } else
        btn = e.which;

    var map = this._getMap();
    if (map == null) return;

    if (!this._moved)
        this._mouseEvent("mouseclick", null, map._getCoords(e.clientX, e.clientY), (btn == 1) ? "left" : "right");

    if (this.info() == null) return;
    
    VInfoWindow._changeWindow();

    if (!this._moved) {
        this.info().coords(this.coords());
        this.info().visible(true);
        if (this.info().options("scrollOnOpen") != false)
            this.info()._smoothOpen();
        this._hideHint();
    }

    map.repaint();

    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;
}

VMarker.prototype._onMouseOut = function(e) {
    if(!e) e = window.event;

    this._div.style.zIndex = VisicomCommons.ZINDEX_MARKERS;
    this._hideHint();

    this.mouseout();
    
    var map = this._getMap();
    if (!map._isMouseOnViewport(e.clientX, e.clientY) && map._dragged_object == this) {
        this._onMouseUp(e);
    }
    
    if (e.stopPropagation) e.stopPropagation();
    else e.cancelBubble = true;
    if (e.preventDefault) e.preventDefault();
    else e.returnValue = false;
}

// ----------------------

/**
 *  Инициализация маркера на текущей карте.
 */
VMarker.prototype._pre_init = function() {
    this._bounds = new VRect( this._coords );
}

/**
 *  Отображение.
 */
VMarker.prototype._pre_show = function() {
    if (this._div == null)
        this._createDOMElement();

    var map = this._getMap();
    if (map == null) return;

    // Экранные координаты
    var screen = map._convertToScreenCoords(this.coords(0));
    
    this._left = Math.round(screen.x - this.pointer().x);
    this._top = Math.round(screen.y - this.pointer().y);
}

/**
 *  Скрытие маркера.
 */
VMarker.prototype._pre_hide = function() {
    // Скрываем информационное окно, если оно открыто
    if (this.info() != null && this.info().visible())
        this.info()._hide();
}

VMarker.prototype._showHint = function() {
    if (this.info() != null && this.info().visible() && this.info().displayed()) return;
    
    if (this.hint() == null) return;
    if (this.hint().visible()) return;

    VMarker._closeCurrentHint();

    this.hint().coords(this.coords());
    this.hint().visible(true);
    this.hint()._show();
    this.hint()._repaint();
    VMarker.CURRENT_HINT = this.hint();
}

VMarker.prototype._hideHint = function() {
    if (this.hint() == null) return;
    if (!this.hint().visible()) return;

    this.hint().visible(false);
    this.hint()._hide();
}

/**
 *  @override
 *  
 */
VMarker.prototype.visible = function(value) {
    if (arguments.length == 0) return this._visible;
    if (this._visible == value) return this;

    this._visible = value;
    
    if (this.info() != null) this.info().visible(false);
    if (this.hint() != null) this.hint().visible(false);
    
    return null;
}

/**
 *  @public
 *  Устанавливает всплывающую подсказку.
 *  @param {String} html текст всплывающей подсказки.
 */
VMarker.prototype.hint = function(html) {
    if (arguments.length == 0) {
        if (typeof(this._hint) == "string") {
            this.hint(this._hint);
        }
        
        return this._hint;
    }
    
    if (this._hint != null) this.remove(this._hint);

    this._hint = new VLabel(this.coords(), html);
    this._hint.visible(false);
    
    this._hint.move(0, - this.pointer().y);
    this.add(this._hint);
    
    return this;
}

/**
 *  @public
 *  Устанавливает изображение для маркера.
 *  @param {VMarkerIcon} icon изображение для маркера.
 */
VMarker.prototype.icon = function(icon) {
    if (this._icon == null) this._icon = (icon != null) ? icon : DEFAULT_MARKER_ICON;
    if (arguments.length == 0) return this._icon;
    
    this._icon = icon;
    if (this._div == null) this._createDOMElement();

    this._div.width = this._icon.width();
    this._div.height = this._icon.height();

    if (this.hint() != null) this.hint().move(0, - this.pointer().y);

    if (USER_DATA['Browser'].MSIE) {
        // Включение альфа-прозрачности для Internet explorer'а
        this._div.src = VisicomCommons.API_URL + '/images/blank.gif';
        this._div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this._icon.source() + "')";
    } else
        this._div.src = this._icon.source();

    this._hide();
    this._show();

    return this;
}

VMarker._closeCurrentHint = function() {
    if (VMarker.CURRENT_HINT == null) return;

    VMarker.CURRENT_HINT.visible(false);
}
// ---------------------------------------------------------
// VInfoWindow class
// Информационное окно маркера.
// ---------------------------------------------------------

/** exnends VMapObject */
VInfoWindow.prototype = new VMapObject();

VInfoWindow.prototype.type = "info";
VInfoWindow.prototype._isGeo = true;

VInfoWindow.CURRENT_WINDOW;

VInfoWindow.FORM_OLDSTYLE =
'<div>' +
'    <!-- top left -->' +
'    <div style="position: absolute; left: 13px; top: 0px; width: 15px; height: 15px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/top_left.png" width="15" height="15" /></div>' +

'    <!-- arrow -->' +
'    <div style="position: absolute; left: 1px; top: 15px; width: 13px; height: 61px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/arrow.png" width="13" height="61" /></div>' +

'    <!-- left border -->' +
'    <div style="position: absolute; left: 13px; top: 76px; width: 15px; height: #height#px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/left.png" width="15" height="#height#" /></div>' +

'    <!-- bottom left -->' +
'    <div style="position: absolute; left: 13px; top: #height+76#px; width: 15px; height: 30px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/bottom_left.png" width="15" height="30" /></div>' +

'    <div style="position: absolute; left: 14px; top: 15px; width: 15px; height: 61px; background-color: white;"></div>' +

'    <!-- background -->' +
'    <div style="position: absolute; left: 28px; top: 15px; width: #width#px; height: #height+61#px; background-color: white;"><div style="padding: 3px;">#html#</div></div>' +

'    <!-- top border -->' +
'    <div style="position: absolute; left: 28px; top: 0px; width: #width#px; height: 15px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/top.png" width="#width#" height="15" /></div>' +

'    <!-- bottom border -->' +
'    <div style="position: absolute; left: 28px; top: #height+76#px; width: #width#px; height: 30px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/bottom.png" width="#width#" height="30" /></div>' +

'    <!-- top right -->' +
'    <div style="position: absolute; left: #width+28#px; top: 0px; width: 30px; height: 15px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/top_right.png" width="30" height="15" /></div>' +

'    <!-- right border -->' +
'    <div style="position: absolute; left: #width+28#px; top: 15px; width: 30px; height: #height+61#px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/right.png" width="30" height="#height+61#" /></div>' +

'    <!-- bottom right -->' +
'    <div style="position: absolute; left: #width+28#px; top: #height+76#px; width: 30px; height: 30px;"><img src="' + VisicomCommons.API_URL + '/images/info_old/bottom_right.png" width="30" height="30" /></div>' +
'</div>';

VInfoWindow.FORM_OLDSTYLE_IE =
'<div>' +
'    <!-- top left -->' +
'    <div style="position: absolute; left: 13px; top: 0px; width: 15px; height: 15px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/top_left.png\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="15" height="15" /></div>' +

'    <!-- arrow -->' +
'    <div style="position: absolute; left: 1px; top: 15px; width: 13px; height: 61px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/arrow.png\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="13" height="61" /></div>' +

'    <!-- left border -->' +
'    <div style="position: absolute; left: 13px; top: 76px; width: 15px; height: #height-76#px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/left.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="15" height="#height-76#" /></div>' +

'    <!-- bottom left -->' +
'    <div style="position: absolute; left: 13px; top: #height+76#px; width: 15px; height: 30px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/bottom_left.png\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="15" height="30" /></div>' +

'    <div style="position: absolute; left: 14px; top: 15px; width: 15px; height: 61px; background-color: white;"></div>' +

'    <!-- background -->' +
'    <div style="position: absolute; left: 28px; top: 15px; width: #width#px; height: #height+62#px; background-color: white;"><div style="padding: 3px;">#html#</div></div>' +

'    <!-- top border -->' +
'    <div style="position: absolute; left: 28px; top: 0px; width: #width#px; height: 15px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/top.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="#width#" height="15" /></div>' +

'    <!-- bottom border -->' +
'    <div style="position: absolute; left: 28px; top: #height+76#px; width: 300px; height: 30px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/bottom.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="#width#" height="30" /></div>' +

'    <!-- top right -->' +
'    <div style="position: absolute; left: #width+28#px; top: 0px; width: 30px; height: 15px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/top_right.png\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="30" height="15" /></div>' +

'    <!-- right border -->' +
'    <div style="position: absolute; left: #width+28#px; top: 15px; width: 30px; height: #height-61#px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/right.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="30" height="#height+61#" /></div>' +


'    <!-- bottom right -->' +
'    <div style="position: absolute; left: #width+28#px; top: #height+76#px; width: 30px; height: 30px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info_old/bottom_right.png\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="30" height="30" /></div>' +
'</div>';

VInfoWindow.FORM_IE =
'<div>' +
'    <div style="position: absolute; left: 0px; top: 0px; width: 9px; height: 9px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/corner-left-top.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="9" height="9" /></div>' +
'    <div style="position: absolute; left: 9px; top: 0px; width: #width#px; height: 9px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/border-top.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="#width#" height="9" /></div>' +
'    <div style="position: absolute; left: #width+9#px; top: 0px; width: 9px; height: 9px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/corner-right-top.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="9" height="9" /></div>' +

'    <div style="position: absolute; left: 0px; top: 9px; width: 9px; height: #height#px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/border-left.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="9" height="#height#" /></div>' +

'    <div style="position: absolute; left: 9px; top: 9px; width: #width#px; height: #height#px; background-color: white; font-size: 1px;"><div style="padding: 3px;">#html#</div></div>' +

'    <div style="position: absolute; left: #width+9#px; top: 9px; width: 9px; height: 120px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/border-right.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="9" height="#height#" /></div>' +

'    <div style="position: absolute; left: 0px; top: #height+9#px; width: 9px; height: 15px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/corner-left-bottom.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="9" height="15" /></div>' +
'    <div style="position: absolute; left: 9px; top: #height+9#px; width: 33px; height: 15px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/pointer.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="33" height="15" /></div>' +

'    <div style="position: absolute; left: 42px; top: #height+9#px; width: #width-33#px; height: 15px;"><img src="' + VisicomCommons.API_URL + '/images/blank.gif" style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/border-bottom.png\', sizingMethod=\'scale\');" width="#width-33#" height="15" /></div>' +

'    <div style="position: absolute; left: #width+9#px; top: #height+9#px; width: 9px; height: 15px;"><img style="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' + VisicomCommons.API_URL + '/images/info/corner-right-bottom.png\', sizingMethod=\'scale\');" src="' + VisicomCommons.API_URL + '/images/blank.gif" width="9" height="15" /></div>' +
'</div>';

VInfoWindow.FORM =
'<div>' +
'    <div style="position: absolute; left: 0px; top: 0px; width: 9px; height: 9px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/corner-left-top.png" width="9" height="9" />' +
'    </div>' +

'    <div style="position: absolute; left: 9px; top: 0px; width: #width#px; height: 9px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/border-top.png" width="#width#" height="9" />' +
'    </div>' +

'    <div style="position: absolute; left: #width+9#px; top: 0px; width: 9px; height: 9px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/corner-right-top.png" width="9" height="9" />' +
'    </div>' +

'    <div style="position: absolute; left: 0px; top: 9px; width: 9px; height: #height#px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/border-left.png" width="9" height="#height#" />' +
'    </div>' +

'    <div style="position: absolute; left: 9px; top: 9px; width: #width#px; height: #height#px; background-color: white;"><div style="padding: 3px;">#html#</div></div>' +

'    <div style="position: absolute; left: #width+9#px; top: 9px; width: 9px; height: #height#px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/border-right.png" width="9" height="#height#" />' +
'    </div>' +

'    <div style="position: absolute; left: 0px; top: #height+9#px; width: 9px; height: 15px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/corner-left-bottom.png" width="9" height="15" />' +
'    </div>' +

'    <div style="position: absolute; left: 9px; top: #height+9#px; width: 33px; height: 15px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/pointer.png" width="33" height="15" />' +
'    </div>' +

'    <div style="position: absolute; left: 42px; top: #height+9#px; width: #width-33#px; height: 15px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/border-bottom.png" width="#width-33#" height="15" />' +
'    </div>' +

'    <div style="position: absolute; left: #width+9#px; top: #height+9#px; width: 9px; height: 15px;">' +
'        <img src="' + VisicomCommons.API_URL + '/images/info/corner-right-bottom.png" width="9" height="15" />' +
'    </div>' +
'</div>';

/** Заголовок информационного окна. */
VInfoWindow.prototype._header = "";
/** HTML текст информационного окна. */
VInfoWindow.prototype._text = "";

/**
 *  @public
 *  По-умолчанию информационное окно закрывается при клике на области карты либо вызове каких-либо других действий. Установка флага always_open в true закрепит данное окно на экране.
 *  @param {Boolean} alwaysOpen значение флага.
 */
VInfoWindow.prototype._alwaysOpen = function(alwaysOpen) {
    if (arguments.length == 0) return this.options("alwaysOpen");
    this._options.alwaysOpen = alwaysOpen;
    
    var map = this._getMap();
    if (map == null) return(this);

    if (this._options.alwaysOpen && VInfoWindow.CURRENT_WINDOW == this) {
        VInfoWindow._changeWindow();
        map.repaint();
    }

    return this;
}

/**
 *  @public
 *  Устанавливает фалг прокрутки карты при открытии информационного окна.
 *  @param {Boolean} scroolOnOpen значение флага.
 */
VInfoWindow.prototype.scrollOnOpen = function(scroolOnOpen) {
    if (arguments.length == 0) return this.options("scrollOnOpen");
    this._options.scrollOnOpen = scroolOnOpen;

    return this;
}

VInfoWindow.prototype.canClose = function(canClose) {
    if (arguments.length == 0) return this.options("canClose");
    this._options.canClose = canClose;

    return this;
}

/**
 *  @category = Markers
 *  Информационное окно маркера.
 *  @class Информационное окно маркера используется для создания комментария, описания к данной точке.<br/>
 *  Позволяет задать HTML текст для заголовка и описания.
 *  По умолчанию информационное окно открывается при клике на маркере.
 *  @constructor
 */
function VInfoWindow() {
    if (arguments.length == 0) return this;

    if (typeof(arguments[0]) != "string") {
        // Заданы координаты
        if (typeof(arguments[2]) == "string") {
            // Задан заголовок и текст
            this._header = arguments[1];
            this._text = arguments[2];

            VMapObject.call(this, arguments[0], arguments[3]);
        } else {
            this._html = arguments[1];
            this._header = arguments[1];
            this._text = arguments[2];

            VMapObject.call(this, arguments[0], arguments[2]);
        }
    } else {
        if (typeof(arguments[1]) == "string") {
            // Задан заголовок и текст
            this._header = arguments[0];
            this._text = arguments[1];

            VMapObject.call(this, null, arguments[2]);
        } else {
            this._html = arguments[0];
            this._header = arguments[0];
            this._text = arguments[0];

            VMapObject.call(this, null, arguments[1]);
        }
    }
    
    if (this.options("alwaysOpen") == null) this.options("alwaysOpen", true);
    if (this.options("canClose") == null) this.options("canClose", true);
    
    return this;
}

/**
 *  Создает HTML элемент.
 */
VInfoWindow.prototype._createDOMElement = function() {
    if (this._div == null) {
        // Создание div'a
        this._div = document.createElement('div');

        this._div.id = 'info_window_';
        this._div.style.position = 'absolute';
        this._div.style.zIndex = VisicomCommons.ZINDEX_INFOWINDOW;
        this._div.style.cursor = 'default';
        this._div.style.padding = '0px';
        //this._div.style.border = '1px dashed black';
        this._div.style.border = '0px';

        var content = "";
        var html = "";
        var map = this._getMap();
        if (map == null) return;
        
        if (this._html == null) {
            var header = "<div style='color: #303030; font: bold 12px Tahoma; padding: 2px; height: 14px; background-color: #E9EEF7;'>" + this._header + "</div>";
            var text = "<div style='color: #303030; font: normal 11px Tahoma; margin-top: 3px; padding: 3px; overflow: auto;'>" + this._text + "</div>";
            html = header + text;
        } else
            html = "<div style='color: #303030; font: normal 11px Arial, Helvetica, sans-serif; max-height: " + (this._options.height - 6) + "px; height: " + (this._options.height - 6) + "px; overflow: auto;'>" + this._html + "</div>";
        
        if (!this._options.width) {
            var size = map._getContentSize(html);
            this._options.width = size.width + 7;
            this._options.height = size.height + 7;
        }
        
        if (this._options.style == "1.0.0") {
            if (USER_DATA['Browser'].MSIE)
                content = VInfoWindow.FORM_OLDSTYLE_IE;
            else
                content = VInfoWindow.FORM_OLDSTYLE;
            
            content = content.replace(/\#header\#/, this._header);
            content = content.replace(/\#html\#/, html);
            content = content.replace(/\#width\#/g, this._options.width);
            content = content.replace(/\#width\+28\#/g, this._options.width + 28);
            content = content.replace(/\#height\#/g, this._options.height);
            content = content.replace(/\#height\+76\#/g, this._options.height + 76);
            content = content.replace(/\#height\-76\#/g, this._options.height - 76);
            content = content.replace(/\#height\+61\#/g, this._options.height + 61);
            content = content.replace(/\#height\-61\#/g, this._options.height - 61);
            content = content.replace(/\#height\+62\#/g, this._options.height + 62);

            this._div.innerHTML = content;

            if (this.options("canClose")) {
                var button_close = document.createElement("img");
                button_close.style.height = "12px";
                button_close.style.width = "12px";

                if (USER_DATA['Browser'].MSIE) {
                    button_close.style.height = "1px";
                    button_close.style.width = "1px";
                    button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info_old/button_close.png')";
                } else {
                    button_close.style.height = "12px";
                    button_close.style.width = "12px";
                    button_close.src = VisicomCommons.API_URL + "images/info_old/button_close.png";
                }
                
                button_close.style.position = "absolute";
                button_close.style.left = (this._options.width + 24) + "px";
                button_close.style.top = "8px";
                button_close.style.zIndex = VisicomCommons.ZINDEX_INFOWINDOW;
                button_close.style.cursor = "pointer";
                this._div.appendChild(button_close);

                var _this = this;
                button_close.onmouseover = function() {
                    if (USER_DATA['Browser'].MSIE) button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info_old/button_close_hover.png')";
                    else button_close.src = VisicomCommons.API_URL + "images/info_old/button_close_hover.png";
                }
                button_close.onmouseout = function() {
                    if (USER_DATA['Browser'].MSIE) button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info_old/button_close.png')";
                    else button_close.src = VisicomCommons.API_URL + "images/info_old/button_close.png";
                }
                button_close.onclick = function() {
                    _this.visible(false);
                    _this._hide();
                    if (USER_DATA['Browser'].MSIE) button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info_old/button_close.png')";
                    else button_close.src = VisicomCommons.API_URL + "images/info_old/button_close.png";
                }
            }
        } else {
            if (USER_DATA['Browser'].MSIE)
                content = VInfoWindow.FORM_IE;
            else
                content = VInfoWindow.FORM;

            //this._options.width += 4;
            //this._options.height += 4;

            content = content.replace(/\#header\#/, this._header);
            content = content.replace(/\#html\#/, html);
            content = content.replace(/\#width\#/g, this._options.width);
            content = content.replace(/\#width\+9\#/g, this._options.width + 9);
            content = content.replace(/\#width\-33\#/g, this._options.width - 33);
            content = content.replace(/\#height\#/g, this._options.height);
            content = content.replace(/\#height\+9\#/g, this._options.height + 9);

            this._div.innerHTML = content;
            
            if (this.options("canClose")) {
                button_close = document.createElement("img");
                button_close.style.height = "15px";
                button_close.style.width = "15px";
                
                if (USER_DATA['Browser'].MSIE) {
                    button_close.style.height = "1px";
                    button_close.style.width = "1px";
                    button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info/close.png')";
                } else {
                    button_close.style.height = "15px";
                    button_close.style.width = "15px";
                    button_close.src = VisicomCommons.API_URL + "images/info/close.png";
                }

                button_close.style.position = "absolute";
                button_close.style.left = (this._options.width - 11) + "px";
                button_close.style.top = "13px";
                button_close.style.zIndex = VisicomCommons.ZINDEX_INFOWINDOW;
                button_close.style.cursor = "pointer";
                this._div.appendChild(button_close);

                var _this = this;
                button_close.onmouseover = function() {
                    if (USER_DATA['Browser'].MSIE) button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info/close_hover.png')";
                    else button_close.src = VisicomCommons.API_URL + "images/info/close_hover.png";
                }
                button_close.onmouseout = function() {
                    if (USER_DATA['Browser'].MSIE) button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info/close.png')";
                    else button_close.src = VisicomCommons.API_URL + "images/info/close.png";
                }
                button_close.onclick = function() {
                    _this.visible(false);
                    _this._hide();
                    if (USER_DATA['Browser'].MSIE) button_close.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + VisicomCommons.API_URL + "images/info/close.png')";
                    else button_close.src = VisicomCommons.API_URL + "images/info/close.png";
                }
            }
        }
        
        var _this = this;
        this._div.onmouseover = function(e) {
            if(!e) e = window.event;

            _this._div.style.zIndex = VisicomCommons.ZINDEX_INFOWINDOW_HOVER;
            var map = _this._getMap();
            if (map != null)
                map._mouse_over = false;

            if (e.stopPropagation) e.stopPropagation();
            else e.cancelBubble = true;
            if (e.preventDefault) e.preventDefault();
            else e.returnValue = false;
        }
        this._div.onmouseout = function(e) {
            if(!e) e = window.event;

            _this._div.style.zIndex = VisicomCommons.ZINDEX_INFOWINDOW;

            if (e.stopPropagation) e.stopPropagation();
            else e.cancelBubble = true;
            if (e.preventDefault) e.preventDefault();
            else e.returnValue = false;
        }
        this._div.onmouseup = function(e) {
            if(!e) e = window.event;

            if (e.stopPropagation) e.stopPropagation();
            else e.cancelBubble = true;
            if (e.preventDefault) e.preventDefault();
            else e.returnValue = false;
        }
    }
}

VInfoWindow.prototype._pre_init = function() {
    this._bounds = new VRect(this._coords);
}

/**
 *  Отображение.
 *  @override
 */
VInfoWindow.prototype._pre_show = function() {
    if (this._div == null) this._createDOMElement();
    
    var map = this._getMap();
    if (map == null) return;
    
    var coords = map._convertToScreenCoords(this.coords(0));

    if (this._options.style == "1.0.0") {
        this._left = coords.x;
        this._top = coords.y - 48;
    } else {
        this._left = coords.x - 32;
        this._top = coords.y - this._options.height - 23;
    }
    
    if (!this.options("alwaysOpen"))
        VInfoWindow._changeWindow(this);
    
    if (this.__marker == null) return;
    this.__marker._hideHint();
    var element = this.__marker.getDOMElement();
    if (element == null) return;
    element.style.display = 'none';
}

VInfoWindow.prototype._pre_hide = function() {
    var map = this._getMap();
    if (map == null) return;

    this._event("close");

    if (this.__marker == null) return;
    var element = this.__marker.getDOMElement();
    if (element == null) return;
    if (!this.__marker.visible()) return;
    element.style.display = 'block';
}

/**
 *  @public
 *  Устанавливает размеры информационного окна в пикселях.
 *  Максимальные размеры окна 800x600 пикселей.
 *  @params {int} width длинна.
 *  @params {int} height высота.
 */
VInfoWindow.prototype.size = function(width, height) {
    if (height == null) {
        switch (width) {
            case "small":
                this._options.width = 190;
                this._options.height = 55;
                break;

            case "medium":
                this._options.width = 290;
                this._options.height = 141;
                break;

            case "large":
                this._options.width = 490;
                this._options.height = 302;
                break;
        }
        return;
    }

    this._options.width = width;
    this._options.height = height;
    
    if (this._options.width > 800) this._options.width = 800;
    if (this._options.height > 600) this._options.height = 600;
}

/**
 *  Меняет текущие информационное окно.
 */
VInfoWindow._changeWindow = function() {
    if (VInfoWindow.CURRENT_WINDOW != null) {
        if (arguments.length > 0) {
            if (VInfoWindow.CURRENT_WINDOW != arguments[0]) {
                VInfoWindow.CURRENT_WINDOW._hide();
                VInfoWindow.CURRENT_WINDOW.visible(false);
            }
        } else {
            VInfoWindow.CURRENT_WINDOW._hide();
            VInfoWindow.CURRENT_WINDOW.visible(false);
        }
    }

    if (arguments.length > 0)
        VInfoWindow.CURRENT_WINDOW = arguments[0];
    else
        VInfoWindow.CURRENT_WINDOW = null;
}

VInfoWindow.prototype._smoothOpen = function() {
    var map = this._getMap();
    if (map == null) return;

    if (this._div == null) this._createDOMElement();

    var coords = map._convertToScreenCoords(this.coords(0));
    var _left;
    var _top;

    if (this._options.style == "1.0.0") {
        _left = coords.x;
        _top = coords.y - 48;
    } else {
        _left = coords.x - 32;
        _top = coords.y - this.options("height") - 23;
    }

    var offset = 10;
    var offset_2 = offset * 2;
    _left -= offset;
    _top -= offset;

    var left_top = map._convertToGeoCoords( _left - offset, _top - offset  );
    var right_top = map._convertToGeoCoords( _left + this.options("width") + offset_2, _top );
    var right_bottom = map._convertToGeoCoords( _left + this.options("width") + offset_2, _top + this.options("height") + offset_2 );
    var left_bottom = map._convertToGeoCoords( _left, _top + this.options("height") + offset_2 );

    var clientBounds = map.clientRect();
    if (clientBounds.contains(left_top) && clientBounds.contains(right_top) &&
        clientBounds.contains(right_bottom) && clientBounds.contains(left_bottom)) return;
    
    map._clearSmoothTimer();
    var move_point = {
                        lng: left_bottom.lng + ((right_top.lng - left_bottom.lng) / 2),
                        lat: left_bottom.lat + ((right_top.lat - left_bottom.lat) / 2)
                     };
    map._smoothMove(move_point,
        function() {
            var clientBounds = map.clientRect();
            
            return !((clientBounds.contains(left_top) && clientBounds.contains(right_top) &&
                      clientBounds.contains(right_bottom) && clientBounds.contains(left_bottom)));
        }
    );
}
// ---------------------------------------------------------
// VLabel class
// Текстовая подпись на карте.
// ---------------------------------------------------------

/** Наследование. */
VLabel.prototype = new VMapObject();

VLabel.prototype._isGeo = true;

VLabel.prototype._html;
/** Смещение. */
VLabel.prototype._offset;

/**
 *  @constructor
 */
function VLabel(coords, html) {
    VMapObject.call(this, coords); // extends

    this._html = (html != null) ? html : "";
    this.type = "label";

    return this;
}

VLabel.prototype.html = function(html) {
    if (arguments.length == 0) return this._html;

    this._html = html;
    if (this._div != null) {
        this._div.innerHTML = "<nobr>" + this._html + "</nobr>";
        this._div.style.display = (this._html != "") ? 'block' : 'none';
    }

    return this;
}

VLabel.prototype._createDOMElement = function() {
    this._div = document.createElement('div');

    this._div.style.position = 'absolute';
    this._div.style.zIndex = VisicomCommons.ZINDEX_LABEL;
    this._div.style.cursor = 'default';
    this._div.style.font = 'normal 11px Arial';
    this._div.style.color = '#000000';
    this._div.style.padding = "2px";

    this._div.style.border = '1px solid gray';
    this._div.style.backgroundColor = '#FFFFE1';

    // Текст подсказки
    this._div.innerHTML = "<nobr>" + this._html + "</nobr>";

    var _this = this;
    this._div.onmousedown = function(e) {
        if(!e) e = window.event;

        _this.mousedown();

        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
    }
    this._div.onmouseup = function(e) {
        if(!e) e = window.event;

        _this.mouseup();

        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
    }
    this._div.onmousemove = function(e) {
        if(!e) e = window.event;

        _this.mousemove();
    }
    this._div.onclick = function(e) {
        if(!e) e = window.event;

        _this.mouseclick();

        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
    }
    this._div.onmouseover = function(e) {
        if(!e) e = window.event;

        if (e.stopPropagation) e.stopPropagation();
        else e.cancelBubble = true;
        if (e.preventDefault) e.preventDefault();
        else e.returnValue = false;
    }
}

VLabel.prototype._pre_init = function() {
    this._bounds = new VRect( this.coords() );
}

VLabel.prototype._pre_show = function() {
    if (this._div == null)
        this._createDOMElement();

    var map = this._getMap();
    if (map == null) return;

    this._div.style.display = (this._html != "") ? 'block' : 'none';

    // Экранные координаты
    var coords = map._convertToScreenCoords(this.coords(0));

    this._left = Math.round(coords.x);
    this._top = Math.round(coords.y);
}

VLabel.prototype._pre_repaint = function() {
    var map = this._getMap();
    if (map == null) return;

    // Экранные координаты
    var coords = map._convertToScreenCoords(this.coords(0));
    this._left = Math.round(coords.x);
    this._top = Math.round(coords.y);

    this._top -= VisicomUtils.getBounds(this._div).height;

    if (this._offset == null) return;
    this._left += this._offset.x;
    this._top += this._offset.y;
}

VLabel.prototype.move = function(x, y) {
    this._offset = {x : x, y : y};

    return this;
}// ---------------------------------------------------------
// VLine class
// Ломаная линия.
// ---------------------------------------------------------

/** Наследование. */
VLine.prototype = new VMapObject();

VLine.prototype.type = "line";
VLine.prototype._isGeo = true;

/**
 *  @public
 *  Устанавливает цвет линии.
 *  @param {int} color цвет.
 */
VLine.prototype.color = function(color) {
    if (arguments.length == 0) return this.style("color");
    this._style.color = color;

    var _this = this;
    this._childs.forEach(
        function(child) {
            child.color(_this._style.color);
            child._re_create();
        }
    );

    this._re_create();

    return this;
}

/**
 *  @public
 *  Устанавливает цвет линии.
 *  @param {int} lineWidth цвет.
 */
VLine.prototype.lineWidth = function(lineWidth) {
    if (arguments.length == 0) return this.style("lineWidth");
    this._style.lineWidth = lineWidth;

    var _this = this;
    this._childs.forEach(
        function(child) {
            child.lineWidth(_this._style.lineWidth);
            child._re_create();
        }
    );

    this._re_create();

    return this;
}

/**
 *  @public
 *  Устанавливает прозрачность линии.
 *  @param {int} opacity прозрачность. Число в диапазоне от 0 до 1.
 */
VLine.prototype.opacity = function(opacity) {
    if (arguments.length == 0) return this.style("opacity");
    this._style.opacity = opacity;

    var _this = this;
    this._childs.forEach(
        function(child) {
            child.opacity(_this._style.opacity);
            child._re_create();
        }
    );

    this._re_create();

    return this;
}

/** Точки в экранных координатах на текущей карте. */
VLine.prototype._screen_coords = null;

/**
 *  @category = Lines
 *  @class
 *  Ломаная линия. Применяется для отрисовки маршрутов, треков и т.п.
 *  @param {lng, lat} coords массив точек в локальных (либо географических) координатах.
 *  @constructor
 */
function VLine(coords) {
    VMapObject.call(this, coords); // extends
    
    return this;
}

/**
 *  Инициализация линии на текущем масштабе.
 */
VLine.prototype._pre_init = function() {
    var map = this._getMap();
    if (map == null) return;
    
    if (this._div != null)
        this._hide();
    
    var _this = this;
    this._childs.forEach(
        function(polyline) {
            polyline._remove();
        }
    );

    this._childs = [];
    this._displayed = false;
    
    if (this._coords == null) return;
    
    var MAX_CANVAS_WIDTH = 3500;
    var MAX_CANVAS_HEIGHT = 3500;
    var OFFSET = 500;

    // Создание экранных точек
    var createScreenCoords = function(coords) {
        var screen_coords = [];

        coords.forEach(
            function(point) {
                var screenPoint = map._convertToScreenCoords(point);
                                                    // Координаты относительно начала tile'a
                screen_coords.push( { x: screenPoint.x - screenPointMin.x + _this.lineWidth(),
                                      y: _this._height - (screenPointMin.y - screenPoint.y) + _this.lineWidth() }
                                  );
            }
        );

        return screen_coords;
    }

    // Проверка вхождения canvas'a в допустимую область
    var checkOverflow = function(coords) {
        var bounds = new VRect(coords);

        // Определение границ прорисовки в экранных координатах на текущей карте
        var _min = map._convertToScreenCoords(bounds.leftBottom());
        var _max = map._convertToScreenCoords(bounds.rightTop());
        var width = (_max.x - _min.x);
        var height = (_min.y - _max.y);

        return (!(width > MAX_CANVAS_WIDTH || height > MAX_CANVAS_HEIGHT));
    }

    // Разделение отрезка на несколько в случае, если он не попадает в допустимую область
    var dividePolyline = function(points) {
        var point1 = points[0];
        var point2 = points[1];

        var dx = (point2.lng - point1.lng);
        var dy = (point2.lat - point1.lat);
        var sqrt = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));

        var cosA = dx / sqrt;
        var sinA = dy / sqrt;

        var _coords = [];
        var unitPerPixel = map._currentMap._units_per_pixel;
        // Средняя точка
        var x = ((MAX_CANVAS_WIDTH - OFFSET) * unitPerPixel) * cosA;
        var y = ((MAX_CANVAS_HEIGHT - OFFSET) * unitPerPixel) * sinA;
        var geo = VisicomCommons.convertToGeoProjection(x, y);
        var point = { lng: point1.lng + geo.lng / 2,
                      lat: point1.lat + geo.lat / 2
                    };
        
        _coords.push(point1);
        _coords.push(point);
        _coords.push(point2);
        return _coords;
    }

    var _coords = [];
    var inc = 0;
    check = checkOverflow(this._coords);
    if (!check) {
        for (var i = 0; i < this._coords.length; i++) {
            inc++;

            var point = this._coords[i];

            _coords.push(point);
            
            var check = false;
            
            while (!check) {
                check = checkOverflow(_coords);
                if (check) break;
                
                if (_coords.length == 2) {
                    _coords = dividePolyline(_coords);
                    var last_point = _coords[1];
                    _coords.splice(2, 1);
                    var childPolyline = new VLine(_coords);
                    childPolyline.visible(this._visible);
                    this.add(childPolyline);
                    childPolyline._init();
                } else {
                    // Удаляем последнюю точку, которая не помещается на canvas
                    _coords.splice(inc - 1, 1);
                    last_point = _coords[inc - 2];
                    childPolyline = new VLine(_coords);
                    childPolyline.visible(this._visible);
                    this.add(childPolyline);
                    childPolyline._init();
                }

                _coords = [];
                _coords.push(last_point);
                _coords.push(point);
                inc = 2;
            }
        }
    } else
        _coords = this._coords;
    
    this._bounds = new VRect(_coords);
    // Определение границ прорисовки в экранных координатах на текущей карте
    var screenPointMin = map._convertToScreenCoords(this._bounds.leftBottom());
    var screenPointMax = map._convertToScreenCoords(this._bounds.rightTop());
    this._left = screenPointMin.x - this.lineWidth();
    this._top = screenPointMin.y - this.lineWidth();
    this._width = Math.abs(screenPointMax.x - screenPointMin.x);
    this._height = screenPointMin.y - screenPointMax.y;

    // Оставшееся точки оставляем в данной линии
    this._screen_coords = createScreenCoords(_coords);

    this._top -= this._height;
    this._width += this.lineWidth() * 2;
    this._height += this.lineWidth() * 2;
}

/**
 *  Добавление линии на слой.
 */
VLine.prototype._pre_show = function() {
    if (this._div != null) return;
    
    if (this._screen_coords == null) return;
    if (this._screen_coords.length == 0) return;

    var lineWidth = this.lineWidth();
    if (lineWidth == null) lineWidth = 2;

    var opacity = this.opacity();
    if (opacity == null) opacity = 1.0;

    var color = this.color();
    if (color == null) color = "#00ffff";

    if (USER_DATA['Browser'].MSIE) {
        // VML для IE

        this._div = document.createElement('div');
        
        this._div.id = 'polyline_' + this._left + '_' + this._top;
        this._div.style.position = 'absolute';
        this._div.style.width = Math.round(this._width) + 'px';
        this._div.style.height = Math.round(this._height) + 'px';

        this._div.style.zIndex = VisicomCommons.ZINDEX_POLYLINE;
        this._div.style.border = 'none';
        //this._div.style.border = '1px dotted black';

        // v:polyline
        var line = document.createElement("v:polyline");
        line.style.position = "absolute";
        line.style.antialias = "true";

        var stroke = document.createElement("v:stroke");
        if (opacity != null)
            stroke.setAttribute("opacity", opacity);
        stroke.setAttribute("joinstyle", "round");
        stroke.setAttribute("filltype", "solid");
        line.appendChild(stroke);

        line.setAttribute("strokecolor", ((color == null) ? "#0000C8" : color));
        line.setAttribute("strokeweight", lineWidth + "px");
        line.setAttribute("filled", "false");

        var points = "";
        for (var i = 0; i < this._screen_coords.length; i++) {
            var screen_point = this._screen_coords[i];
            points += screen_point.x + "," + screen_point.y + " ";
        }
        line.setAttribute("points", points);
        line.setAttribute("opacity", opacity);

        this._div.appendChild(line);
    } else {
        // Canvas для Gecko браузеров

        // Создание canvas'a
        this._div = document.createElement('canvas');

        this._div.id = 'polyline_' + this._left + '_' + this._top;
        this._div.style.position = 'absolute';
        this._div.width = Math.round(this._width);
        this._div.height = Math.round(this._height);
        this._div.style.zIndex = VisicomCommons.ZINDEX_POLYLINE;
        this._div.style.border = 'none';
        //this._div.style.border = '1px dashed black';

        var canvas;
        try { canvas = this._div.getContext('2d'); }
        catch (err) { return; }

        canvas.lineWidth = lineWidth;
        canvas.lineCap = 'round';
        canvas.lineJoin = 'round';
        canvas.strokeStyle = (color == null) ? 'rgb(0, 0, 200)' : color;
        if (opacity != null)
            canvas.globalAlpha = opacity;

        // Начинаем отрисовку
        canvas.beginPath();
        // Отрисовка линии по заданным точкам
        for (i = 0; i < this._screen_coords.length; i++) {
            screen_point = this._screen_coords[i];

            // Ставим точку на исходную позицию
            if (i == 0) canvas.moveTo(screen_point.x, screen_point.y);
            // Рисуем линии от точки к точке
            else canvas.lineTo(screen_point.x, screen_point.y);
        }
        canvas.stroke();
    }
}

/**
 *  Расчет дистанции линии.
 *  @returns {float} дистанция в метрах.
 */
VLine.prototype.distance = function() {
    return VisicomCommons.GeoCalcLen(this.coords());
}// ---------------------------------------------------------
// VMultiLine class
// Связка линий.
// ---------------------------------------------------------

/** Наследование. */
VMultiLine.prototype = new VLine();

VMultiLine.prototype.type = "multiline";


/**
 *  
 *  @constructor
 */
function VMultiLine(coords) {
    VLine.call(this, coords); // extends
    
    return this;
}

VMultiLine.prototype.bounds = function() {
    if (this._bounds == null)
        this._createLines();
    return this._bounds;
}

VMultiLine.prototype._createLines = function() {
    if (this._childs.length != 0) return;
    this._childs = [];
    
    var arr = [];
    for (var i = 0; i < this._coords.length; i++) {
        var klop = this._coords[i];
        if (klop.length == null) {
            this.add(new VLine(this._coords));
            arr = this._coords;
            break;
        }

        this.add(new VLine(klop));

        var rect = new VRect(klop);
        arr.push(rect.leftBottom());
        arr.push(rect.rightTop());
    }

    this._bounds = new VRect(arr);
}

VMultiLine.prototype._pre_init = function() {
    this._createLines();
}

/**
 *  Расчет дистанции линии.
 *  @returns {float} дистанция в метрах.
 */
VMultiLine.prototype.distance = function() {
    if (this._childs.length == 0) this._createLines();

    var dist = 0;
    for (var i in this._childs) {
        var line = this._childs[i];
        dist += line.distance();
    }

    return dist;
}
// ---------------------------------------------------------
// VArea class
// Полигон.
// ---------------------------------------------------------

/** extends VLine */
VArea.prototype = new VLine();

/**
 *  @class
 *  Полигон.
 *  @constructor
 *  @params {VPoints} array_of_coords массив точек.
 */
function VArea(coords) {
    VLine.call(this, coords); // extends

    this.type = "area";

    return this;
}

/**
 *  Отображение полигона.
 */
VArea.prototype._pre_show = function() {
    if (this._screen_coords == null) return;
    if (this._screen_coords.length == 0) return;

    var lineWidth = this.lineWidth();
    if (lineWidth == null) lineWidth = 3;

    var opacity = this.opacity();
    if (opacity == null) opacity = 0.35;

    var color = this.color();
    if (color == null) color = "#0000ff";

    if (USER_DATA['Browser'].MSIE) {
        // VML для IE

        this._div = document.createElement('div');

        this._div.id = 'polygone_' + this._left + '_' + this._top;
        this._div.style.position = 'absolute';
        this._div.style.width = Math.round(this._width);
        this._div.style.height = Math.round(this._height);

        this._div.style.zIndex = '102';
        this._div.style.border = '0px';
        //this._div.style.border = '1px dashed black';
        
        var shape = document.createElement("v:shape");
        shape.setAttribute("fillcolor", color);
        shape.setAttribute("coordorigin", "0 0");
        shape.setAttribute("coordsize", Math.round(this._width) + " " + Math.round(this._height));
        shape.style.left = '0px';
        shape.style.top = '0px';
        shape.style.width = Math.round(this._width) + 'px';
        shape.style.height = Math.round(this._height) + 'px';
        shape.style.position = "absolute";

        var fill = document.createElement("v:fill");
        fill.opacity = opacity;
        shape.appendChild(fill);

        var stroke = document.createElement("v:stroke");
        stroke.fillstyle = color;
        stroke.opacity = opacity;
        stroke.joinstyle = 'miter';
        shape.appendChild(stroke);

        var path = document.createElement("v:path");
        shape.appendChild(path);

        var first_point = this._screen_coords[0];
        var vPath = "m " + Math.round(first_point.x) + "," + Math.round(first_point.y) + " l ";

        for (var i = 1; i < this._screen_coords.length; i++) {
            if (i != 1)
                vPath += ", ";

            var screen_point = this._screen_coords[i];
            vPath += Math.round(screen_point.x) + "," + Math.round(screen_point.y);
        }

        vPath += " x e";

        path.setAttribute("v", vPath);

        this._div.appendChild(shape);
    } else {
        // Canvas для Gecko браузеров

        // Создание canvas'a
        this._div = document.createElement('canvas');

        this._div.id = 'polygone_' + this._left + '_' + this._top;
        this._div.style.position = 'absolute';
        this._div.width = Math.round(this._width);
        this._div.height = Math.round(this._height);
        this._div.style.zIndex = '102';
        this._div.style.border = '0px';
        //this._div.style.border = '1px dashed black';
        
        var canvas;
        try { canvas = this._div.getContext('2d'); }
        catch (err) { return; }

        canvas.lineWidth = lineWidth;
        canvas.lineCap = 'round';
        canvas.lineJoin = 'round';
        canvas.fillStyle = (color == null) ? 'rgb(0, 0, 200)' : color;
        canvas.globalAlpha = opacity;

        // Начинаем отрисовку
        canvas.beginPath();
        // Отрисовка линии по заданным точкам
        for (i = 0; i < this._screen_coords.length; i++) {
            var screen_point = this._screen_coords[i];

            // Ставим точку на исходную позицию
            if (i == 0) canvas.moveTo(screen_point.x, screen_point.y);
            // Рисуем линии от точки к точке
            else canvas.lineTo(screen_point.x, screen_point.y);
        }
        canvas.fill();
    }
}// ---------------------------------------------------------
// VRemoteRequest class
// Движок обмена данными с сервером.
// ---------------------------------------------------------

VRemoteRequest.prototype._script;
VRemoteRequest.prototype._callback;

/**
 *  Сервис удаленной работы с ГЕО-сервером VISICOM.
 *  @constructor
 */
function VRemoteRequest() { return this; }

/**
 *  Таблица запросов.
 */
var _VISICOM_remoteRequestMap = new Object();
/**
 *  Запрос.
 *  @param {String} url URL запрос.
 *  @param {function} callback callback функция.
 */
VRemoteRequest.prototype.request = function(url, callback) {
    this._callback = callback;

    var id = "remote_" + Math.random();
    _VISICOM_remoteRequestMap[id] = this;

    // Создание DOM-элемента <script>
    this._script = document.createElement("script");
    this._script.id = id;
    this._script.type = "text/javascript";
    // Добавляем ID запроса
    this._script.src = url + '&id=' + id + '&mapname=' + _VISICOM_MapGlobalContext.mapname() + "_" + _VISICOM_MapGlobalContext.language();

    document.body.appendChild(this._script);
}

/**
 *  Callback функция для вызова из скрипта, загруженного с удаленного сервера.
 *  @param {int} id идентификатор запроса.
 *  @param {object} data данные.
 */
function _VISICOM_remoteRequestCallback(id, data) {
    //  -> url?a=0&b=0&...&id=23ded423f
    //  <- Скрипт, срабатывающий в загруженном удаленном скрипте
    //  <script>
    //  ...
    //  _VISICOM_remoteRequestCallback(id, <данные>);
    //  </script>

    var remoteRequest = _VISICOM_remoteRequestMap[id];
    if (remoteRequest == null) return;

    // Вызываем callback
    if (remoteRequest._callback != null)
        remoteRequest._callback(data);

    // Завершение
    // Удаляем скрипт из DOM дерева
    if (remoteRequest._script != null) {
        document.body.removeChild(remoteRequest._script);
        delete(remoteRequest._script);
    }

    // Обнуляем запись в таблице
    _VISICOM_remoteRequestMap[id] = null;
}// ---------------------------------------------------------
// VRemoteCall class
// Сервис для вызова удаленных методов.
// ---------------------------------------------------------

var VRemoteCall = (function() {

    function request() {
        if (arguments.length < 2) return;

        var method = arguments[0];
        var callback = arguments[arguments.length - 1];

        var req = new VRemoteRequest();

        var url = VisicomCommons.API_URL + method + "?";
        for (var i = 1; i < arguments.length - 1; i++) {
            var param = arguments[i];

            if (i >= 2) url += "&";

            if ((param.lng != null) && (param.lat != null)) {
                var value = "";
                value += param.lng.toPrecision(6) + "," + param.lat.toPrecision(6) + ";";
                url += "p" + i + "=" + value;
            } else
            if (param instanceof Array) {
                value = "";
                for (var j = 0; j < param.length; j++) {
                    value += param[j].lng.toPrecision(6) + "," + param[j].lat.toPrecision(6) + ";";
                }
                
                url += "p" + i + "=" + value;
            } else
            if (param instanceof VMapObject) {
                value = "";
                for (var j = 0; j < param.coords().length; j++) {
                    value += param.coords(j).lng.toPrecision(6) + "," + param.coords(j).lat.toPrecision(6) + ";";
                }
                
                url += "p" + i + "=" + value;
            } else
            if (typeof(param) == "number") {
                url += "p" + i + "=" + param;
            } else
            if (typeof(param) == "string") {
                url += "p" + i + "=" + encodeURI(param);
            }
        }

        if (arguments.length >= 2) url += "&";
        else url += "?";
        url += ((VMap.AUTH_KEY != "") ? "key=" + VMap.AUTH_KEY : "");
        
        req.request(url,
            function(data) {
                if (data.error != null) throw new Error(data.error);
                
                VMapObject._wrap(null, data);
                callback(data);
            }
        );
    }

    function address() { request("address", arguments); }

    function route() { request("route", arguments); }

    function language() { request("language", arguments); }

    return { request : request,
             address : address,
             route   : route
           };
})();

/* 
 *  Временная поддержка классов и методов которые являются устаревшими.
 */

VBounds.prototype = new VRect();

VBounds.prototype.min = function() { return this._coords[0]; }
VBounds.prototype.max = function() { return this._coords[1]; }


function VBounds() {
    VRect.call(this, arguments); // extends
    return this;
}


VMap.prototype.clientBounds = function() {
    return this.clientRect();
}


// ---------------------------------------------------------
// VPoint class
// Точка на карте.
// ---------------------------------------------------------

/** extends VMapObject */
VPoint.prototype = new VMapObject();

/**
 *  @category = Map
 *  @class
 *  Точка на карте представленная внутренними, "локальными" координатами.
 *  Данные координаты работают только внутри картографического сервера компании Визиком.
 *  @param {float} lng долгота.
 *  @param {float} lat широта.
 *  @constructor
 */
function VPoint(lng, lat) {
    VMapObject.call(this, {lng: lng, lat: lat});

    return this;
}

/**
 *  @public
 *  Определение расстояния между точками.
 *  @params {VPoint} point;
 */
VPoint.prototype.distanceTo = function(point) {
    var len = VisicomCommons.GeoCalcLen([this.coords(0), point]);

    return len;
}

// ---------------------------------------------------------
// VRoute class
// Маршрут.
// ---------------------------------------------------------

/** extends VMapObject */
VRoute.prototype = new VMapObject();

/** Дистанция. */
VRoute.prototype._distance = null;

/**
 *  @public
 *  Устанавливает дистанцию по маршруту.
 *  @param {float} distance расстояние.
 */
VRoute.prototype.distance = function(distance) {
    if (arguments.length == 0) return this._distance;
    this._distance = distance;

    return this;
}

/**
 *  @category = Routes
 *  @class
 *  Маршрут.
 *  @param {VEdge[]} edges массив участков.
 *  @constructor
 */
function VRoute(edges) {
    VMapObject.call(this); // extends

    if (edges != null) this.edges(edges);
    this._type = "route";

    return this;
}

/**
 *  @public
 *  Возвращает массив участков.
 *  @param {VEdge[]} edges массив участков.
 *  @returns {VEdge[]} массив учатсков.
 */
VRoute.prototype.edges = function(edges) {
    if (arguments.length == 0) return this._childs;
    this._childs = edges;

    return this;
};


// ---------------------------------------------------------
// VEdge class
// Участок маршрута.
// ---------------------------------------------------------

/** extends VLine */
VEdge.prototype = new VLine();

/** Наименование участка. */
VEdge.prototype._name = null;

/** Дистанция. */
VEdge.prototype._distance = null;

/**
 *  @category = Routes
 *  @class
 *  Участок маршрута.
 *  @param {Object} [coords] массив координат.
 *  @constructor
 */
function VEdge(coords) {
    VLine.call(this, coords); // extends

    return this;
}

/**
 *  @public
 *  Устанавливает название участка.
 *  @param {String} name расстояние.
 */
VEdge.prototype.name = function(name) {
    if (arguments.length == 0) return this._name;
    this._name = name;

    return this;
}

/**
 *  @public
 *  Устанавливает дистанцию по маршруту.
 *  @param {float} distance расстояние.
 */
VEdge.prototype.distance = function(distance) {
    if (arguments.length == 0) return this._distance;
    this._distance = distance;

    return this;
}

VMapObject.prototypes["route"] = VRoute.prototype;
VMapObject.prototypes["edge"] = VEdge.prototype;

VMap.prototype.init = function() {
  if (this.initialized) return;
  
    this._screenOffsets = [];
    
    this._init_();

    this._mapName = 'world_ru';
    this._mapName = this._mapName.split("_")[0];
    
    this._language = 'ru';

    var min_lng = parseFloat(-200375082);
    var max_lng = parseFloat(200375082);
    var min_lat = parseFloat(-200375083);
    var max_lat = parseFloat(200375083);

    this.__minLng = min_lng;
    this.__maxLng = max_lng;
    this.__minLat = min_lat;
    this.__maxLat = max_lat;
    
    var min = VisicomCommons.convertToGeoProjection(min_lng, min_lat);
    var max = VisicomCommons.convertToGeoProjection(max_lng, max_lat);

    this._bounds = new VRect(min, max);

    var i = 0;

    var href = 'http://tms.visicom.ua/1.0.3/world_ru/0';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 1565430.332031;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/1';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 782715.166016;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/2';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 391357.583008;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/3';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 195678.791504;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/4';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 97839.395752;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/5';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 48919.697876;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/6';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 24459.848938;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/7';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 12229.924469;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/8';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 6114.962234;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/9';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 3057.481117;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/10';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 1528.740559;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/11';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 764.370279;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/12';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 382.185140;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/13';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 191.092570;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/14';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 95.546285;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/15';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 47.773142;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/16';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 23.886571;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/17';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 11.943286;

    this._maps[i] = map;
    i++;
    var href = 'http://tms.visicom.ua/1.0.3/world_ru/18';
    var arr = href.split("/");
    var index = arr[arr.length - 1];

    var map_width = Math.pow(2, index) * VMapTile.TILE_WIDTH;
    var map_height = Math.pow(2, index) * VMapTile.TILE_HEIGHT;
    
    var map = new VMapZoom(map_width, map_height, i, this);
    map._base_url = href;
    map._units_per_pixel = 5.971643;

    this._maps[i] = map;
    i++;
    
    this._currentMap = this._maps[this._zoom_index];
    this._showMap();

    this._zoomControl._createDOMElement();
    this._zoomControl.position(this._zoom_index);

    this.initialized = true;
}

VMapObject.prototypes["mapobject"] = VMapObject.prototype;
VMapObject.prototypes["layer"] = VLayer.prototype;
VMapObject.prototypes["collection"] = VLayer.prototype;
VMapObject.prototypes["label"] = VLabel.prototype;
VMapObject.prototypes["marker"] = VMarker.prototype;
VMapObject.prototypes["info"] = VInfoWindow.prototype;
VMapObject.prototypes["line"] = VLine.prototype;
VMapObject.prototypes["area"] = VArea.prototype;
VMapObject.prototypes["multiline"] = VMultiLine.prototype;
VMapObject.prototypes["rect"] = VRect.prototype;