//
// Ryzom Maps
// Copyright (c) 2009 Meelis Mägi <nimetu@gmail.com>
//
// This file is part of Ryzom Maps.
//
// Ryzom Maps is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Ryzom Maps is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
//
//
if(!Array.prototype.indexOf){
	Array.prototype.indexOf = function(obj, n){
		var len=this.length;
		n=n||0;
		for(var i=n; i<len; i++){
			if(this[i]==obj){
				return i;
			}
		}
		return -1;
	};
}

/*$(function(){
	// cleanup event
	GEvent.addDomListener(window, 'unload', GUnload);
});*/

var MAPS_HOST = 'http://maps.bmsite.net/';
var DEBUG=false;
/**
 * Ryzom ingame <-> pixel coorindate mapper
 * definition below
 */
var RyzomXY = {};

/**
 * Modifies icon URL such that it uses new color
 * @return GIcon
 */
GIcon.prototype.setColor=function(color){
	// if no _name is set, then we dont use RyzomIcon
	if(!this._name) return;
	//var color=r.toString(16)+g.toString(16)+b.toString(16);
	this._color=color;
	this._over=MAPS_HOST+'api/icons.php?icon='+this._name+'_over&color='+color;
	this._original = MAPS_HOST+'api/icons.php?icon='+this._name+'&color='+color;
	this.image=this._original;
	return this;
};

/**
 * Returns own copy
 * @return GIcon
 */
GIcon.prototype.copy=function(){
	var c=new GIcon(this);
	if(this._name){
		c._name=this._name;
		c._over=this._over;
		c._original=this._original;
	}
	return c;
};

/**
 * Swaps marker icon
 * @param boolean state
 */
GMarker.prototype.setOver=function(state){
	// do nothing for hidden marker
	if(this.isHidden()) return;
	var icon=this.getIcon();
	if(state){
		if(icon._over!=undefined){
			this.setImage(icon._over);
		}
	}else{
		if(icon._original!=undefined){
			this.setImage(icon._original);
		}
	}
};

var RyzomIcons = function(){
	var me=this;
	// helper function to get icon full path
	function mkurl(image){
		return MAPS_HOST+'images/icons/'+image;
	};

	/**
	 * Helper to create GIcon instance
	 *
	 * iconAnchor is set to middle of image, 
	 * infoWindowAnchor is set to top-center
	 * dragCrossAnchor is set to 0x0 from iconAnchor.
	 * icon will not rise when it's being dragged
	 */
	this.createIcon=function(name, color, size, shadowSize, imageMap){
		var icon=new GIcon();
		icon._name=name;
		icon.setColor(color); // setColor will also set icon name
		// GIcon properties
		icon.shadow        = mkurl(name+'/shadow.png');
		icon.printImage    = mkurl(name+'/printImage.gif');
		icon.mozPrintImage = mkurl(name+'/mozPrintImage.gif');
		icon.printShadow   = mkurl(name+'/printShadow.gif');
		icon.transparent   = mkurl(name+'/transparent.png');
		icon.iconSize      = size;
		icon.shadowSize    = shadowSize;
		icon.iconAnchor    = new GPoint(size.width/2, size.height/2);
		icon.infoWindowAnchor = new GPoint(size.width/2, 0);
		icon.imageMap = imageMap;
		icon.maxHeight = 0;
		//
		return icon;
	};

	/**
	 * Search icon by it's name and return it
	 * @param icon one of 'lm_marker', 'op_townhall', 'building', etc
	 * @return GIcon instance like RyzomIcons.MARKER, RyzomIcons.OP_TOWNHALL, etc
	 *         FALSE if icon was not found
	 */
	this.find = function(icon){
		for(var a in me){
			if(me[a] instanceof GIcon && me[a]._name==icon){
				return a;
			}
		}
		return false;
	};
	//
	this.MARKER        = createIcon('lm_marker',     'ef4e4e', new GSize(24,24), new GSize(24,24), [23,15,19,23,18,23,6,23,1,15,5,0,19,0]);
	this.MISSION       = this.MARKER.copy().setColor('1b9acf');
	this.OUTPOST       = this.MARKER.copy().setColor('1bcf34');
	//
	this.OP_TOWNHALL   = createIcon('op_townhall',    '', new GSize(40,40), new GSize(60,40), [0,29,12,39,28,40,40,29,28,1,8,1]);
	this.BUILDING      = createIcon('building',       '', new GSize(32,32), new GSize(48,32), [1,11,21,2,31,13,31,24,18,30,3,26]);
	//
	this.MEKTOUB       = createIcon('mektoub',        '', new GSize(24,24), new GSize(36,24), [0,16,7,23,18,24,24,16,20,0,3,0]),
	this.SPAWN         = createIcon('spawn',          '', new GSize(24,24), new GSize(36,24), [0,15,5,24,17,24,24,15,19,0,5,0]);
	this.PORTAL        = createIcon('portal',         '', new GSize( 9, 9), new GSize(36, 9), [0,9,9,9,9,0,0,0]);
	//
	this.KAMI_TP       = createIcon('tp_kami',        '', new GSize(24,24), new GSize(36,24), [3,0,2,12,6,22,17,22,21,13,21,0]);
	this.KARAVAN_TP    = createIcon('tp_karavan',     '', new GSize(24,28), new GSize(38,28), [0,19,7,28,15,28,24,19,24,10,12,0,0,0]);
	//
	this.BANDIT_TRIBE  = createIcon('camp',   'ef4e4e', new GSize(24,24), new GSize(36,24), [0,16,5,22,19,22,24,16,18,0,5,0,0,15]);
	this.KAMI_TRIBE    = this.BANDIT_TRIBE.copy().setColor('ffff00');
	this.KARAVAN_TRIBE = this.BANDIT_TRIBE.copy().setColor('00ffff');
	//
	this.NPC           = createIcon('npc',    'ffffff', new GSize(19,22), new GSize(30,22), [0,16,5,22,14,22,19,16,13,0,5,0,0,16]);
	this.NPC_VENDOR    = this.NPC.copy().setColor('005bff');
	this.NPC_MISSION   = this.NPC_VENDOR.copy().setColor('7efc00');
	this.NPC_TRAINER   = this.NPC_VENDOR.copy().setColor('febe00');
	//
	this.DIG_RESOURCE  = createIcon('dig',    'ffffff', new GSize(24,24), new GSize(36,24), [3,23,11,23,24,18,24,9,15,0,2,0]);
	this.DIG_MISSION   = this.DIG_RESOURCE.copy().setColor('9f9f9f');

	return this;
}();

/**
 * @return GMap2 instance
 */
function RyzomMap(id, defaultZoom, minZoom, maxZoom){
	// our instance for anonymous functions
	var me=this;

	var minZoom = minZoom || 1;
	var maxZoom = maxZoom || 11;
	var defaultZoom = defaultZoom || 5;

	if (!GBrowserIsCompatible()) {
		alert('Sorry. Your browser is not supported by google maps');
		return;
	}

	// default language
	var lang='en';
	me.season='sp';

	var tileserver=MAPS_HOST+'images/';


    //******************************************************************************
	// real world tiles
	var tilelayer = new GTileLayer(new GCopyrightCollection("Ryzom Maps by bmsite.net"), minZoom, maxZoom);
	tilelayer.getCopyright = function(a, b){ return {prefix: "Ryzom maps &copy;", copyrightTexts:["<a href='http://bmsite.net/'>bmsite.net</a>"]}; };
	tilelayer.getTileUrl= function (a,z) {
		return tileserver+'atys/zoom_'+z+'/atys_'+z+'_'+a.x+'x'+a.y+'.jpg';
	};
	tilelayer.isPng = function() { return false; };
	tilelayer.getOpacity = function() { return 1.0;};

    //******************************************************************************
	// ingame 'sattellite' tiles
	var igtiles = new GTileLayer(new GCopyrightCollection("Ryzom IG maps by bmsite.net"), minZoom, maxZoom);
	igtiles.getCopyright = function(a, b){ return {prefix: "Ryzom IG: &copy;", copyrightTexts:["<a href='http://bmsite.net/'>bmsite.net</a>"]}; };
	igtiles.getTileUrl= function (a,z) {
		return tileserver+'atys_'+me.season+'/zoom_'+z+'/atys_'+me.season+'_'+z+'_'+a.x+'x'+a.y+'.jpg';
	};
	igtiles.isPng = function() { return false; };
	igtiles.getOpacity = function() { return 1.0;};
	
    //******************************************************************************
	var projection=new GMercatorProjection(32);

    //******************************************************************************
	var atysmap = new GMapType([tilelayer], projection, "Atys", {
		errorMessage:"No chart data available", 
		minResolution:minZoom, 
		maxResolution:maxZoom,
		textColor:'#555',
		linkColor:'#888'
	});
	
    //******************************************************************************
	var igmap = new GMapType([igtiles], projection, "Satellite", {
		errorMessage:"No chart data available", 
		minResolution:minZoom, 
		maxResolution:maxZoom,
		textColor:'#555',
		linkColor:'#888'
	});
	
    //******************************************************************************
	// GMap instance
	var map = new GMap2(document.getElementById(id), {
		backgroundColor: '#000', 
		mapTypes:[atysmap, igmap]
	});

	map.enableContinuousZoom();
	map.enableScrollWheelZoom();
	map.addControl(new GLargeMapControl(), new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(2,25)));
	map.addControl(new GMapTypeControl());
	map.minZoom=minZoom;
	map.maxZoom=maxZoom;

	// create marker tooltip
	me.tooltip=new RyzomTooltip(new GSize(10, 10));
	map.addOverlay(me.tooltip);

	/**
	 * Event delegation - listen DOM mouseover/out events on main map container
	 */
	GEvent.addDomListener(map.getContainer(), 'mouseover', function(e){
		//draggable marker has __e_
		if(e.target.__marker__ != undefined){
			try{
				e.target.__marker__.setOver(true);
				// show tooltip
				if(e.target.__marker__.tooltip){
					var pos=map.fromLatLngToDivPixel(e.target.__marker__.getLatLng());
					me.tooltip.show(pos.x, pos.y, e.target.__marker__.tooltip);
				}
			}catch(ex){
				// incase there is error
			}
		}
	});
	GEvent.addDomListener(map.getContainer(), 'mouseout', function(e){
		if(e.target.__marker__ != undefined){
			try{
				e.target.__marker__.setOver(false);
				// show tooltip
				me.tooltip.hide();
			}catch(ex){
				// incase there is error
			}
		}
	});
	
	/**
	 * hook up some ryzom methods to GMap2 map instance 
	 */
    // Set map center point using ingame x/y
    // @param ax ingame x
    // @param ay ingame y
    // @param zoom zoom level starting from 1 (automatically adjusted to GMap zoom level
    // @param show_ig set TRUE if satellite view should be activated
    map.igCenter=function(ax, ay, zoom, show_ig){
    	map.setCenter(map.fromIngame(ax, ay), zoom, show_ig ? map.igMapType() : map.worldMapType());
    };
    // Returns GMap map type object for 'atys' view
    map.worldMapType=function(){
    	return atysmap;
    };
    // Returns GMap map type object for 'satellite' view
    map.igMapType=function(){
    	return igmap;
    };
    // returns true if ig tiles are active
    map.isIgMap=function(){
    	return map.getCurrentMapType()==igmap;
    };
    // set IG tile season
	map.setSeason = function(season){
		map.savePosition();
		me.season=season;
		map.returnToSavedPosition();
	};

	//var world=RyzomXY.outgame_coordinates.world;
	//var world_w=world.r-world.l;
	//var world_h=world.b-world.t;

	// default center spot
    map.setCenter(map.fromImage(3540, 3540), defaultZoom, atysmap);

    // Return GMap2 object
    return map;
};

/**
 * Translate Ryzom ingame x/y to LatLng and return GPoint instance
 *
 * @param ax ingame x
 * @param ay ingame y
 * @return GPoint
 */
GMap2.prototype.fromIngame=function(ax, ay){
	var p=RyzomXY.fromIngameToOutgame(ax, ay, 2);
	return this.fromImage(p.x, p.y);
};

/**
 * Convert image pixel coordinates to LatLng
 *
 * @param ax image x
 * @param ay image y
 * @return GPoint
 */
GMap2.prototype.fromImage=function(ax, ay){
	return this.getCurrentMapType().getProjection().fromPixelToLatLng(new GPoint(ax, ay), RyzomXY.BASE_ZOOM);
};

/**
 * Convert GPoint latLng to Ryzom ingame x/y
 *
 * @param latLng GPoint instance
 * @return {x:ig_x, y:ig_y, regions:[ig_street, ig_area, ig_region, ig_zone...]}
 * 		x is ingame x
 * 		y is ingame y
 * 		TODO: region only has lists one zone currently
 * 		regions is array of ingame regions that matched. sorted by region size, so 'smallest first'
 */
GMap2.prototype.toIngame=function(latLng){
	var p=this.getCurrentMapType().getProjection().fromLatLngToPixel(latLng, RyzomXY.BASE_ZOOM);
	var ig=RyzomXY.fromOutgameToIngame(p.x, p.y);
	// TODO: do polygon search (slow!!) for ig_x/y if we need to know exact location
	return {x:ig.x, y:ig.y, regions: ig.regions};
};

//****************************************************************************

/**
 * Translates current mouse coordinates to ingame x/y/zone and displays them
 */
function RyzomNavLabelControl(){}
RyzomNavLabelControl.prototype=new GControl();

/**
 * Create element where info is displayed and add it to map
 * Automatically called
 */
RyzomNavLabelControl.prototype.initialize=function(map){
	var container = document.createElement('div');
	
	this.setStyle(container);
	map.getContainer().appendChild(container);
	
	var start=new Date().getTime();
	GEvent.addListener(map, 'mousemove', function(latlng){
		// if less than 100ms apart, then skip coords update - html update does take a lot of 'power'
		var now=new Date().getTime();
		if(now-start<100){
			return;
		}
		start=now;
		// do latLng->Pixel->Ryzom ingame conversion
		var ig=map.toIngame(latlng);
		if(ig.regions.length == 0){
			// out of zone, so clear status
			container.innerHTML="-";
		}else{
			container.innerHTML=ig.regions.join(';')+': x='+(ig.x.toFixed(2))+', y='+(ig.y.toFixed(2))
		}
	});
	return container;
};

/**
 * Set the default location
 */
RyzomNavLabelControl.prototype.getDefaultPosition = function(){
	return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 30));
};

/**
 * Define textbox style
 */
RyzomNavLabelControl.prototype.setStyle=function(div){
	div.style.color = "#0000cc";
	div.style.backgroundColor="white";
	div.style.font="small Arial";
	div.style.border="1px solid black";
	div.style.padding="2px";
	div.style.width="200px";
	div.style.height="1.2em";
	div.style.textAlign="center";
	div.style.cursor="pointer";
	div.style.fontSize="10px";
};

//****************************************************************************

/*
 * X/Y remapping
 *                              world map size
 * @zoom level  1 -- 1px ~ 512m (   27x27   )
 * @zoom level  2 -- 1px ~ 256m (   55x55   )
 * @zoom level  3 -- 1px ~ 128m (  110x110  )
 * @zoom level  4 -- 1px ~  64m (  221x221  )
 * @zoom level  5 -- 1px ~  32m (  442x442  )
 * @zoom level  6 -- 1px ~  16m (  885x885  )
 * @zoom level  7 -- 1px =   8m ( 1770x1770 )
 * @zoom level  8 -- 1px =   4m ( 3540x3540 )
 * @zoom level  9 -- 1px =   2m ( 7080x7080 )
 * @zoom level 10 -- 1px =   1m (14160x14160)
 * @zoom level 11 -- 1px = 0.5m (28320x28320)
 *
 * FIXME: create php mapxy to javascript exporter
 *
 * this needs to be in sync with tile creator data
 */
RyzomXY.BASE_ZOOM = 9;
RyzomXY.outgame_coordinates = {
	//world         : {l:0, t:0, r:7080, b:7080},
	//
	fyros         : {l:1460, t:318,  w:2240, h:1600},
	matis         : {l:3880, t:176,  w:2960, h:3760},
	tryker        : {l:3858, t:4112, w:3120, h:2720},
	zorai         : {l:  94, t:4442, w:2800, h:2480},
	nexus         : {l:4098, t:2828, w: 960, h:1120},
	bagne         : {l:4196, t:1870, w: 560, h: 800},
	route_gouffre : {l:2896, t:2072, w: 960, h:3680},
	terre         : {l:1186, t:2480, w:1440, h:1360},
	sources       : {l:346,  t:3500, w: 640, h: 800},
	//
	newbieland    : {l:   0, t:8000, w:1600, h:1040},
	kitiniere     : {l:2000, t:8000, w: 640, h: 640},
	corrupted_moor: {l:4000, t:8000, w:1680, h:1130},
	//
	fyros_newbie  : {l:8000, t:   0, w:1120, h: 880},
	matis_newbie  : {l:8000, t:2000, w:1200, h: 960},
	tryker_newbie : {l:8000, t:4000, w:1200, h: 960},
	zorai_newbie  : {l:8000, t:6000, w: 960, h: 720},
	//
	fyros_island  : {l:11000,t:   0, w:2400,  h:560},
	matis_island  : {l:11000,t:2000, w:2160,  h:640},
	tryker_island : {l:11000,t:4000, w:3040,  h:560},
	zorai_island  : {l:11000,t:6000, w:2400,  h:640},
	//
	almati        : {l:11000,t: 2000,w:  640,h:  640},
	olkern        : {l:12840,t: 4080,w:  480,h:  480},
	aelius        : {l:12840,t:    0,w:  560,h:  320},
	//
	testroom      : {l:20000,t:    0,w:3200, h: 2615},
	indoors       : {l:    0,t:10000,w: 640, h:  160},
	//
	r2_desert     : {l:20000,t:    0,w: 4999,h: 5000},
	r2_jungle     : {l:26000,t:    0,w: 4999,h: 5000},
	r2_lakes      : {l:20000,t: 6000,w: 4999,h: 5000},
	r2_forest     : {l:26000,t: 6000,w: 4999,h: 5000},
	r2_roots      : {l:32000,t:    0,w: 5000,h: 5000},




	//mask 'last comma' and also catch coords from full default zoom level grid size
	grid:{l: 0, t:0, w: 131072, h:131072}
};

RyzomXY.ingame_coordinates = {
	// l=left, b=bottom, r=right, t=top, p=parent)
               fyros:{l:15840, b:-27040, r:20320, t:-23840, p:''},
               matis:{l:320,   b:-7840,  r:6240,  t:-320,   p:''},
              tryker:{l:13760, b:-34880, r:20000, t:-29440, p:''},
               zorai:{l:6880,  b:-5920,  r:12480, t:-960,   p:''},
               bagne:{l:480,   b:-11360, r:1600,  t:-9760,  p:''}, // abyss of ichor
       route_gouffre:{l:5440,  b:-16960, r:7360,  t:-9600,  p:''}, // land of umbra
             sources:{l:2560,  b:-11360, r:3840,  t:-9760,  p:''}, // under spring
               terre:{l:160,   b:-15840, r:3040,  t:-13120, p:''}, // wasteland
               nexus:{l:7840,  b:-8320,  r:9760,  t:-6080,  p:''},
	// silan
          newbieland:{l:8160,  b:-12320, r:11360, t:-10240, p:''}, // silan
	// city: fyros
           place_pyr:{l:18400, b:-24720, r:19040, t:-24240, p:'fyros'},
         place_dyron:{l:16480, b:-24800, r:16720, t:-24480, p:'fyros'},
        place_thesos:{l:19520, b:-26400, r:19760, t:-26080, p:'fyros'},
	// city: matis
       place_yrkanis:{l:4640,  b:-3680,  r:4800,  t:-3200,  p:'matis'},
         place_natae:{l:3600,  b:-3840,  r:3840,  t:-3680,  p:'matis'},
         place_davae:{l:4160,  b:-4240,  r:4320,  t:-4000,  p:'matis'},
        place_avalae:{l:4800,  b:-4480,  r:4960,  t:-4320,  p:'matis'},
	// city: tryker
      place_avendale:{l:18000, b:-31200, r:18240, t:-30960, p:'tryker'},
    place_crystabell:{l:17760, b:-32000, r:18000, t:-31760, p:'tryker'},
     place_fairhaven:{l:16960, b:-33280, r:17440, t:-32720, p:'tryker'},
    place_windermeer:{l:15440, b:-33120, r:15760, t:-32880, p:'tryker'},
	// city: zorai
         place_zorai:{l:8480,  b:-3040,  r:8800,  t:-2720,  p:'zorai'},
       place_hoi_cho:{l:9440,  b:-3680,  r:9680,  t:-3360,  p:'zorai'},
       place_jen_lai:{l:8640,  b:-3840,  r:8960,  t:-3520,  p:'zorai'},
       place_min_cho:{l:9760,  b:-4320,  r:10080, t:-4000,  p:'zorai'},
	// old pvp challenge areas
        fyros_island:{l:21120, b:-24960, r:25920, t:-23840, p:''},
       tryker_island:{l:21120, b:-30560, r:27200, t:-29440, p:''},
        matis_island:{l:14080, b:-1600,  r:18400, t:-320,   p:''},
        zorai_island:{l:13920, b:-4800,  r:18720, t:-3520,  p:''},
	// episode 2 areas
              almati:{l:14080, b:-1600,  r:15360, t:-320,   p:'matis_island'},   // Almati
              olkern:{l:24800, b:-30560, r:25760, t:-29600, p:'tryker_island'}, // Olkern
              aelius:{l:24800, b:-24480, r:25920, t:-23840, p:'fyros_island'}, // Aelius
	// old starter zones
        fyros_newbie:{l:20960, b:-27040, r:23200, t:-25280, p:''},
       tryker_newbie:{l:20800, b:-34880, r:23200, t:-32960, p:''},
        matis_newbie:{l:320,   b:-7680,  r:2720,  t:-5760,  p:''},
        zorai_newbie:{l:7040,  b:-5600,  r:8960,  t:-4160,  p:''},
	//
      corrupted_moor:{l:12480, b:-12200, r:15840, t:-9940,  p:''},
           kitiniere:{l:1760,  b:-17440, r:3040,  t:-16160, p:''}, // cont_kitiniere, 'kitiniere' has 160,-17600 .. 3040,-13120
	//
			 indoors:{l:20000, b:-640,   r:21280, t:-320,   p:''},
			testroom:{l:33000, b:-5630,  r:39400, t: -400,  p:''},
			// note: r2 t/b is reversed
			r2_roots:{l:30960, t:-20800, r:40960, b:-30800, p:''},
		   r2_desert:{l:20960, t:-800,   r:30959, b:-10800, p:''},
		    r2_lakes:{l:20960, t:-10800, r:30959, b:-20800, p:''},
		   r2_forest:{l:30960, t:-800,   r:40959, b:-10800, p:''},
		   r2_jungle:{l:30960, t:-10800, r:40959, b:-20800, p:''},

	// mask 'last comma'
    grid: {l:-262144, b:-262144, r:0, t:0}
};

/**
 * Searches in which region(s) X,Y belongs to
 * @return array of regions that match, first will always be smallest match (first is pyr, second is fyros for example)
 */
RyzomXY.belongsToIngame=function(x, y){
	var me=this;
	function compare(i, j){
		var a=me.ingame_coordinates[i], b=me.ingame_coordinates[j];
		var aw=a.r-a.l, ah=a.t-a.b;
		var bw=b.r-b.l, bh=b.t-b.b;
		if(aw<bw && ah<bh) return -1;
		else if(aw>bw || ah>bh) return 1;
		else return 0;
	}
	var matches=[];
	for(var i in this.ingame_coordinates){
		var z=this.ingame_coordinates[i];
		if(x>=z.l && x<=z.r && y>=z.b && y<=z.t) { // (y>z.b && y<z.y) is CORRECT
			matches.push(i);
		}
	}
	// sort by zone size
	matches.sort(compare);
	return matches;
};

/**
 * Searches outgame regions where x/y belongs to
 * Finds all zones that match, sorts them by size and returns smallest (Matis covers AoI/Nexus)
 *
 * @return outgame region name
 */
RyzomXY.belongsToOutgame=function(x, y){
	var me=this;
	function compare(i, j){
		var a=me.outgame_coordinates[i], b=me.outgame_coordinates[j];
		if(a.w<b.w && a.h<b.h) return -1;
		else if(a.w>b.w || a.h>b.h) return 1;
		else return 0;
	}
	var matches=[];
	for(var i in this.outgame_coordinates){
		var z=this.outgame_coordinates[i];
		// skip those thos l/t is 0
		//if(z.l==0 &&z.t==0) continue;
		var r=z.l+z.w;
		var b=z.t+z.h;
		if(x>=z.l && x<=r && y>=z.t && y<=b){
			matches.push(i);
		}
	}
	matches.sort(compare);
	if(matches.length>0){
		// first is smallest
		return matches[0];
	}else{
		return 'grid'; // should reach here anyway
	}
};

/**
 * Converts Outgame coordinates to ingame ones
 * TODO: dive into towns and other inner regions / zones
 *
 * @param z  ingame zone
 * @param x  pixel coordinate
 * @param y  pixel coordinate
 */
RyzomXY.fromOutgameToIngame = function(x, y, ig_region){
	if(ig_region==undefined){
		// if ig_region is not set, then try to find out where the x/y belongs to
		ig_region=this.belongsToOutgame(x, y);
	}
	var zone = this.outgame_coordinates[ig_region];
	if( zone === undefined ){
		return {x:0, y:0, regions:[]};
	}

	if(DEBUG){
		console.debug('fromOutGameToIngame: [', ig_region, ']; x=',x, 'y=',y);
	}

	// if zone is world level 'cont_*', then move it to zone level, modify x and y accordingly
	if(ig_region.match(/^cont_.*/)){
		//console.debug('world level detected [',z,']');
		// world level zone area might be different size so need to do careful x/y conversion
		// zone's continent coordinates
		var cont=this.outgame_coordinates[ig_region];
		if(typeof cont.w == 'undefined'){
			// fill in the blanks
			cont.w=cont.r-cont.l;
			cont.h=cont.b-cont.t;
		}
		var cont_px=(x-cont.l)/cont.w;
		var cont_py=(y-cont.t)/cont.h;

		if(DEBUG){
			console.debug('cont = ', cont);
			console.debug('cont_w = ', cont.w, '; cont_h = ', cont.h);
			console.debug('cont_px = ', cont_px, '; cont_py = ', cont_py);
		}
		
		// convert cont_* to zone level
		ig_region = ig_region.replace(/^cont_/, '');
		if(DEBUG){
			console.debug('zone level [',ig_region,']');
		}
		// take new zone now
		zone = this.outgame_coordinates[ig_region];
		if( zone === undefined ){
			// zone not found ? FIXME: break or continue with cont ??
			return {x:0, y:0, regions:[]};
		}
		// .. and new x/y is...
		x=zone.l+cont_px*zone.w;
		y=zone.t+cont_py*zone.h;
		if(DEBUG){
			console.debug('zone = ', zone);
			console.debug('zone_w = ', zone.w, '; zone_h = ', zone.h);
			console.debug('zone_x = ', x, '; zone_y = ', y);
			console.debug(' --- ');
		}
	}
	// fill in the blanks
	if(typeof zone.w == 'undefined'){
		// fill in the blanks
		zone.w=zone.r-zone.l;
		zone.h=zone.b-zone.t;
	}

	// outgame zone - x/y in 0x0 to WxH box
	var px=(x-zone.l)/zone.w;
	var py=(y-zone.t)/zone.h;
	if(DEBUG){
		console.debug('zw = ', zone.w, '; zh = ', zone.h);
		console.debug('px = ', px, '; py = ', py);
	}
	
	// ingame zone
	var ig = this.ingame_coordinates[ig_region];
	if( ig === undefined){
		// fall back to full grid space
		ig_region='grid';
		// ingame zone not found, map to 'grid'
		ig=this.ingame_coordinates[ig_region];
		//return {x:0,y:0,regions:[]};
	}
	if(DEBUG) console.debug('matched ig', ig_region, ig);
	var ig_w=ig.r-ig.l;
	var ig_h=Math.abs(ig.b-ig.t);
	var new_x=(ig.l+px*ig_w);
	var new_y=(ig.t-py*ig_h); // FIXME: verify this

	if(DEBUG){
		console.debug('ig = ', ig_region, ig);
		console.debug('ig_w = ', ig_w, '; ig_h = ', ig_h);
		console.debug('new_x = ', new_x, '; new_y = ', new_y);
	
		console.debug(x, 'x', y, ' translated to outgame ', ig_region, zone);
	}
	// get ig regions where this spot really belongs to
	var igr=this.belongsToIngame(new_x, new_y);
	return { x: new_x, y: new_y, regions: igr };
};

/*
 * Maps ingame coordinates to pixel coordinates
 *
 * @param x
 * @param y
 *
 * @return object with {name, x, y} parameters where name is map name and x/y map coords
 *           
 */
RyzomXY.fromIngameToOutgame = function(x, y){
	var zones = this.belongsToIngame(x, y);
	if(DEBUG){
		console.debug('ingame ', x, y, ' belongsTo ingame ', zones);
	}
	
	// no match ??
	if(zones.length==0){
		// try to find closest zone and attach the point
		var prev_dx=Infinity;
		var prev_dy=Infinity;
		var closest='';
		for(var i in this.ingame_coordinates){
			var z=this.ingame_coordinates[i];
			// distance for 4 sides
			var dxl=Math.abs(z.l-x);
			var dyb=Math.abs(z.b-y);
			var dxr=Math.abs(z.r-x);
			var dyt=Math.abs(z.t-y);

			var dx, dy;
			if(dxl<dxr) dx=dxl; else dx=dxr;
			if(dyt<dyb) dy=dyt; else dy=dyb;

			if(dx<prev_dx && dy<prev_dy){
				closest=i;
				prev_dx=dx;
				prev_dy=dy;
				if(DEBUG){
					console.debug(i, dx, dy);
				}
			}
		}
		// we found something - return this region
		if(closest!=''){
			if(DEBUG){
				console.debug('Closest zone should be ', closest);
			}
			zones=[closest];
		}else{
			// should never happen. we should always find something...
			return -1;
		}
	}

	var point_id = zones[zones.length-1]; // last from the list
	var oprefix='cont_';

	// find the outgame parent zone. for 'Pyr', parent zone would be 'fyros'
	var point;
	for(var i=0;i<zones.length;i++){
		point_id=zones[i];
		point=this.ingame_coordinates[point_id];
		if(point.p==''){
			if(DEBUG) console.debug('use ', point_id, point,', break');
			break;
		}
		if(DEBUG) console.debug('ignore ', point_id, point, ', take next one');
	}

	//var point = this.ingame_coordinates[point_id];
	if(DEBUG){
		console.debug('point [',point_id,'] = ', point, '; oprefix=[',oprefix,']');
	}

	// find outgame zone
	if(typeof(this.outgame_coordinates[oprefix+point_id]) != 'undefined'){
		outgame_zone = oprefix+point_id;
	}else if(typeof(this.outgame_coordinates[point_id]) != 'undefined'){
		outgame_zone = point_id;
	}else{
		// no outgame zone found
		if(DEBUG){
			console.debug('outgame zone not found [',oprefix,point_id,']', zones);
			console.debug('fall back to "grid"');
		}
		outgame_zone='grid';
	}
	var opoint = this.outgame_coordinates[outgame_zone];
	if(DEBUG) console.debug('opoint[',outgame_zone,'] = ', opoint);

	// calculate Point's position in % for given zone
	var zw=point.r-point.l;
	var zh=point.t-point.b; // this will work like -1000-(-1200)=200
	if(DEBUG) console.debug('zw = ', zw, '; zh = ', zh);
	var px=(x-point.l)/zw;
	var py=Math.abs(y-point.b)/zh; // use Bottom and not Top
	if(DEBUG) console.debug('px = ', px, '; py = ', py);
	// new x/y location inside outgame map area
	var R=1;
	var new_x=(opoint.l*R+opoint.w*R*px);
	var new_y=(opoint.t*R+opoint.h*R-opoint.h*R*py);
	if(DEBUG) console.debug('zone: ', outgame_zone, '; new_x = ', new_x, '; new_y = ', new_y);
	return { name: outgame_zone, x: new_x, y: new_y };
};

//
// Ryzom Maps
// Copyright (c) 2009 Meelis Mägi <nimetu@gmail.com>
//
// This file is part of Ryzom Maps at http://maps.bmsite.net
//

function RyzomTooltip(padding){
	this.x=0;
	this.y=0;
	this.padding_=padding;
}
RyzomTooltip.prototype= new GOverlay();

RyzomTooltip.prototype.initialize=function(map){
	var div=document.createElement('div');
	div.className='ryzom-ui-tooltip';
	div.style.position='absolute';
	div.style.display='none';
	
	map.getPane(G_MAP_FLOAT_PANE).appendChild(div);

	this.div_=div;
	this.map_=map;
};

RyzomTooltip.prototype.remove = function(){
	this.div_.parentNode.removeChild(this.div_);
};

RyzomTooltip.prototype.copy = function(){
	return new Tooltip(this.padding_);
};

RyzomTooltip.prototype.redraw = function(force){
	if(!force) return;

	var xPos=this.x+this.padding_.width;
	var yPos=this.y+this.padding_.height;

	this.div_.style.left = xPos+'px';
	this.div_.style.top  = yPos+'px';
};

RyzomTooltip.prototype.show = function(x, y, text){
	if(x !== undefined && y !== undefined){
		this.x=x; this.y=y;
	}
	if(text !== undefined){
		this.div_.innerHTML=text;
	}
	this.div_.style.display='';
	this.redraw(true);
};

RyzomTooltip.prototype.hide = function(){
	this.div_.style.display='none';
};

var ryzomLabels=[
{x:18810,y:-24373,type:6, level:0, icon:"", name:{en:"Cheapside Market",fr:"Marché des Bonnes Affaires",de:"Fremdenbasar",ru:"Рынок Чипсайд "}},
{x:18891,y:-24346,type:6, level:0, icon:"", name:{en:"Cerakos Gate",fr:"Porte de Cerakos",de:"Cerakos-Tor",ru:"Врата Церакос"}},
{x:18673,y:-24414,type:6, level:0, icon:"", name:{en:"Highmarket",fr:"Grand Marché",de:"Kalus-Basar",ru:"Большой Базар"}},
{x:18566,y:-24448,type:6, level:0, icon:"", name:{en:"Academy Place",fr:"Place des Académies",de:"Platz der Akademie",ru:"Военная Академия"}},
{x:18688,y:-24503,type:6, level:0, icon:"", name:{en:"Oasis Fountain Square",fr:"Carré à la Fontaine",de:"Brunnenplatz",ru:"Площадь Фонтана Оазиса"}},
{x:18624,y:-24505,type:6, level:0, icon:"", name:{en:"Guardhouse Place",fr:"Place des Corps de Garde",de:"Turm der Schildwacht",ru:"Городсткие Казармы"}},
{x:18489,y:-24439,type:6, level:0, icon:"", name:{en:"Imperial Hall Square",fr:"Square Impérial",de:"Platz der Imperialen Halle",ru:"Площадь Имперских Залов"}},
{x:18511,y:-24574,type:6, level:0, icon:"", name:{en:"Karavia Square",fr:"Square de Karavia",de:"Karavia-Platz",ru:"Площадь Каравии"}},
{x:18444,y:-24515,type:6, level:0, icon:"", name:{en:"Agora",fr:"Agora",de:"Agora",ru:"Агора"}},
{x:18569,y:-24617,type:6, level:0, icon:"", name:{en:"Forge",fr:"Forge",de:"Schmiede",ru:"Кузня"}},
{x:18729,y:-24382,type:6, level:0, icon:"", name:{en:"Market Lane",fr:"Ruelle des Marchands",de:"Marktgasse",ru:"Рыночный пееулок"}},
{x:18744,y:-24431,type:6, level:0, icon:"", name:{en:"Cheapside Way",fr:"Chemin des Bonnes Affaires",de:"Basarstraße",ru:"Улица Чипсайд "}},
{x:18609,y:-24408,type:6, level:0, icon:"", name:{en:"Trader&#039;s Way",fr:"Chemin des Négoces",de:"Händlergasse",ru:"Улица Торговцев"}},
{x:18614,y:-24447,type:6, level:0, icon:"", name:{en:"Pyr High Street",fr:"Grande Rue de Pyr",de:"Pyr-Hochstraße",ru:"Улица Высоты Пира"}},
{x:18571,y:-24381,type:6, level:0, icon:"", name:{en:"Northgate Road",fr:"Route du Nord",de:"Nordtorstraße",ru:"Путь Северных Врат"}},
{x:18862,y:-24441,type:6, level:0, icon:"", name:{en:"Cerakos Avenue",fr:"Avenue Cerakos",de:"Cerakos-Allee",ru:"Церакос авеню"}},
{x:18872,y:-24459,type:6, level:0, icon:"", name:{en:"Arispotle Avenue",fr:"Avenue Arispotle",de:"Arispotle-Allee",ru:"Ариспотл авеню"}},
{x:18808,y:-24509,type:6, level:0, icon:"", name:{en:"Coriolis Circus",fr:"Cirque de Coriolis",de:"Coriolis-Ring",ru:"Круг Кориолиса"}},
{x:18766,y:-24511,type:6, level:0, icon:"", name:{en:"Rydon&#039;s Walk",fr:"Promenade de Rydon",de:"Rydon-Weg",ru:"Переулок Райдона"}},
{x:18724,y:-24490,type:6, level:0, icon:"", name:{en:"Central Lane",fr:"Ruelle du Centre",de:"Zentralweg",ru:"Улица Центральная"}},
{x:18654,y:-24485,type:6, level:0, icon:"", name:{en:"Leanon Street",fr:"Rue Leanon",de:"Leanon-Straße",ru:"Улица Леанон"}},
{x:18654,y:-24526,type:6, level:0, icon:"", name:{en:"Dexton Street",fr:"Rue Dexton",de:"Dexton-Straße",ru:"Улица Декстона"}},
{x:18597,y:-24504,type:6, level:0, icon:"", name:{en:"Guardhouse Way",fr:"Chemin des Corps de Garde",de:"Weg der Schildwacht",ru:"Казарменный переулок"}},
{x:18580,y:-24538,type:6, level:0, icon:"", name:{en:"Upper Piros Street",fr:"Rue du Haut-Piros",de:"Obere Piros-Straße",ru:"Улица Верхний Пайрос"}},
{x:18561,y:-24557,type:6, level:0, icon:"", name:{en:"Karavia Road",fr:"Route de Karavia",de:"Karavia-Straße",ru:"Каравия авеню"}},
{x:18596,y:-24601,type:6, level:0, icon:"", name:{en:"Forge and Hammer Way",fr:"Route de la Forge et du Marteau",de:"Weg von Hammer und Schmiede",ru:"Переулок Молота и Наковальни"}},
{x:18618,y:-24575,type:6, level:0, icon:"", name:{en:"Lower Piros Street",fr:"Rue du Bas-Piros",de:"Untere Piros-Straße",ru:"Улица Нижний Пайрос"}},
{x:18684,y:-24583,type:6, level:0, icon:"", name:{en:"Southgate Road",fr:"Route du Sud",de:"Südtorstraße",ru:"Путь Южных врат"}},
{x:18669,y:-24548,type:6, level:0, icon:"", name:{en:"Bath Lane",fr:"Chemin de la Cuvette",de:"Badegasse",ru:"Улица Терм"}},
{x:18702,y:-24560,type:6, level:0, icon:"", name:{en:"Abylus Circus",fr:"Cirque d&#039;Abylus",de:"Abylus-Ring",ru:"Круг Абилуса"}},
{x:18711,y:-24524,type:6, level:0, icon:"", name:{en:"Fountain Road",fr:"Route à la Fontaine",de:"Quellweg",ru:"Улица Фонтанов"}},
{x:18741,y:-24555,type:6, level:0, icon:"", name:{en:"Miners&#039; Row",fr:"Venelle des Mineurs",de:"Bergarbeiterviertel",ru:"Шахтерские ряды"}},
{x:18729,y:-24468,type:6, level:0, icon:"", name:{en:"Merchant&#039;s Walk",fr:"Promenade des Marchands",de:"Kaufmannsgasse",ru:"Купеческий переулок"}},
{x:4720,y:-3282,type:6, level:0, icon:"", name:{en:"Zachini Ward",fr:"District de Zachini",de:"Zachini-Bezirk",ru:"Округ Зачини"}},
{x:4720,y:-3418,type:6, level:0, icon:"", name:{en:"Yasson Ward",fr:"District de Yasson",de:"Yasson-Bezirk",ru:"Округ Яссон"}},
{x:4718,y:-3536,type:6, level:0, icon:"", name:{en:"Aniro Ward",fr:"District d&#039;Aniro",de:"Aniro-Bezirk",ru:"Округ Аниро"}},
{x:4751,y:-3635,type:6, level:0, icon:"", name:{en:"Tylini Ward",fr:"District de Tylini",de:"Tylini-Bezirk",ru:"Округ Тайлини"}},
{x:4677,y:-3637,type:6, level:0, icon:"", name:{en:"Libia Ward",fr:"District de Libia",de:"Libia-Bezirk",ru:"Округ Либия"}},
{x:17169,y:-32928,type:6, level:0, icon:"", name:{en:"Fairmarket Fork",fr:"Fourche du Bon Marché",de:"Fairhaven Trödelmarkt",ru:"Развилка Фэйрмаркет"}},
{x:17106,y:-32777,type:6, level:0, icon:"", name:{en:"Land&#039;s End",fr:"Bout des Terres",de:"Landende",ru:"Край Света"}},
{x:17028,y:-32965,type:6, level:0, icon:"", name:{en:"Windy Head",fr:"Façade des Vents",de:"Umwehte Höhe",ru:"Ветреные головы"}},
{x:17107,y:-33094,type:6, level:0, icon:"", name:{en:"Hartley Point",fr:"Hartley Point",de:"Hartley-Aussichtspunkt",ru:"Хартли Поинт"}},
{x:17236,y:-33001,type:6, level:0, icon:"", name:{en:"Council Chamber Square",fr:"Square de la Chambre du Conseil",de:"Ratskammerplatz",ru:"Площадь Палат Совета"}},
{x:17334,y:-32974,type:6, level:0, icon:"", name:{en:"Eastmarket",fr:"Marché Est",de:"Ostmarkt",ru:"Восточный Рынок"}},
{x:17302,y:-33068,type:6, level:0, icon:"", name:{en:"Frogmore Place",fr:"Place Frogmore",de:"Frogmore-Platz",ru:"Площадь Лягушек"}},
{x:17270,y:-33202,type:6, level:0, icon:"", name:{en:"Loria&#039;s Rise",fr:"Côte de Loria",de:"Lorias Aufstieg",ru:"Возвышение Лории"}},
{x:17100,y:-32945,type:6, level:0, icon:"", name:{en:"Rigan Road",fr:"Route de Rigan",de:"Rigan-Strasse",ru:"Риган Роуд"}},
{x:17154,y:-32850,type:6, level:0, icon:"", name:{en:"Bell Lane",fr:"Chemin du Clocher",de:"Glockengasse",ru:"Колокольная Улица"}},
{x:17200,y:-32962,type:6, level:0, icon:"", name:{en:"Upper Chamber Avenue",fr:"Avenue de la Chambre Haute",de:"Obere Ratsstraße",ru:"Авеню Верхних палат"}},
{x:17272,y:-33035,type:6, level:0, icon:"", name:{en:"Lower Chamber Avenue",fr:"Avenue de la Chambre Basse",de:"Untere Ratsstraße",ru:"Авеню Нижних палат"}},
{x:17162,y:-33033,type:6, level:0, icon:"", name:{en:"Hartley&#039;s Way",fr:"Chemin de Hartley",de:"Hartley-Weg",ru:"Проулок Хартли "}},
{x:17284,y:-32984,type:6, level:0, icon:"", name:{en:"Eastmarket Walk",fr:"Promenade du Marché Est",de:"Ostmarkt-Gang",ru:"Переулок Восточного Рынка"}},
{x:17317,y:-33021,type:6, level:0, icon:"", name:{en:"Hobelen Walk",fr:"Promenade d&#039;Hobelen",de:"Hobelen-Gang",ru:"Переулок Гобеленов"}},
{x:17287,y:-33134,type:6, level:0, icon:"", name:{en:"Frogmore Lane",fr:"Chemin de Frogmore",de:"Frogmore-Weg",ru:"Лягушачья улица"}},
{x:8801,y:-2957,type:6, level:0, icon:"", name:{en:"Cemetery District",fr:"Cimetière",de:"Friedhof",ru:"Кладбищенский Район"}},
{x:8737,y:-2942,type:6, level:0, icon:"", name:{en:"Goo Chase District",fr:"District de Goo Chase",de:"Goo-Chase-Distrikt",ru:"Район Изгнания Гуу"}},
{x:8662,y:-2945,type:6, level:0, icon:"", name:{en:"Highmart District",fr:"Highmart",de:"Vorderer Markt",ru:"Район Большого Базара"}},
{x:8667,y:-2836,type:6, level:0, icon:"", name:{en:"Temple Hall District",fr:"District de Temple Hall",de:"Tempelhallen-Distrikt",ru:"Район Храмовых залов"}},
{x:8564,y:-2933,type:6, level:0, icon:"", name:{en:"West Hollow District",fr:"District de West Hollow",de:"West-Hollow-Distrikt",ru:"Район Чистого колодца"}},
{x:8712,y:-2783,type:6, level:0, icon:"", name:{en:"Daïsha Estate",fr:"Domaine de Daïsha",de:"Daïsha-Viertel",ru:"Имение Даиши"}},
{x:8567,y:-2798,type:6, level:0, icon:"", name:{en:"Win-Cho Estate",fr:"Domaine de Win-Cho",de:"Win-Cho-Viertel",ru:"Имение Вин-Чо"}},
{x:1365,y:-10167,type:5, level:0, icon:"", name:{en:"Hallowed Mountain",fr:"Montagne Sacrée",de:"Heiliger Berg",ru:"Священная Гора"}},
{x:1041,y:-10478,type:5, level:0, icon:"", name:{en:"Saprun Watch",fr:"Guet de la Montée de Sève",de:"Sapquellen-Wacht",ru:"Дозор Потока сэп"}},
{x:1359,y:-10720,type:5, level:0, icon:"", name:{en:"Templedoom",fr:"Temple Maudit",de:"Tempel der Verdammnis",ru:"Храм Обреченных"}},
{x:19099,y:-24020,type:5, level:0, icon:"", name:{en:"Valley of Emperors",fr:"Vallée des Empereurs",de:"Herrscherebene",ru:"Долина Императоров"}},
{x:18180,y:-24689,type:5, level:0, icon:"", name:{en:"Fourways",fr:"Quatre Chemins",de:"Vier Wege",ru:"Четырепутье"}},
{x:18458,y:-25079,type:5, level:0, icon:"", name:{en:"Canyon Pass",fr:"Col aux Canyons",de:"Schluchtenpass",ru:"Горловина Каньона"}},
{x:17582,y:-25006,type:5, level:0, icon:"", name:{en:"Oasis Rift",fr:"Crevasse des Oasis",de:"Oasenspalte",ru:"Разлом Оазиса"}},
{x:17820,y:-24850,type:5, level:0, icon:"", name:{en:"Clopper Hill",fr:"Colline des Cloppers",de:"Clopperhügel",ru:"Холм Клопперов"}},
{x:17753,y:-25041,type:5, level:0, icon:"", name:{en:"Midpeaks",fr:"Pics du Milieu",de:"Mittlere Gipfel",ru:"Средигорье"}},
{x:18046,y:-24853,type:5, level:0, icon:"", name:{en:"Barkgully",fr:"Couloir aux Ecorces",de:"Rindenriss",ru:"Корковражье"}},
{x:17905,y:-25462,type:5, level:0, icon:"", name:{en:"Route of the Flaming Forest",fr:"Route de la Forêt Enflammée",de:"Weg des verbrannten Waldes",ru:"Путь в пылающий лес"}},
{x:17358,y:-24701,type:5, level:0, icon:"", name:{en:"Lonesome Pass",fr:"Col de la Solitude",de:"Einsamer Pass",ru:"Дорога Одиночества"}},
{x:17281,y:-25785,type:5, level:0, icon:"", name:{en:"The Vast Expanse",fr:"Vaste Etendue",de:"Die enorme Weite",ru:"Широкие просторы"}},
{x:16877,y:-25704,type:5, level:0, icon:"", name:{en:"Hightowers",fr:"Hautes Tours",de:"Hochtürme",ru:"Высокие Башни"}},
{x:19276,y:-25596,type:5, level:0, icon:"", name:{en:"The Waste",fr:"Terres Désolées",de:"Der Untergang",ru:"Пустошь"}},
{x:19554,y:-25540,type:5, level:0, icon:"", name:{en:"Highridges",fr:"Hauts Récifs",de:"Hohes Riff",ru:"Вышние Хребты"}},
{x:20160,y:-25123,type:5, level:0, icon:"", name:{en:"Barker Dunes",fr:"Dunes de Barker",de:"Rindendünen",ru:"Дюны Искусников"}},
{x:19929,y:-25537,type:5, level:0, icon:"", name:{en:"Varynx Rise",fr:"Côte des Varinx",de:"Varinx-Hänge",ru:"Возвышение Варинксов"}},
{x:19809,y:-25297,type:5, level:0, icon:"", name:{en:"Troubled Water",fr:"Eaux Tourmentées",de:"Wildes Wasser",ru:"Неспокойные воды"}},
{x:16475,y:-24407,type:5, level:0, icon:"", name:{en:"Dyron Hill",fr:"Colline de Dyron",de:"Dyron-Anhöhe",ru:"Холм Дайрона"}},
{x:16269,y:-25058,type:5, level:0, icon:"", name:{en:"Dragon&#039;s Tail",fr:"Queue du Dragon",de:"Drachenschwanz",ru:"Хвост Дракона"}},
{x:16578,y:-25176,type:5, level:0, icon:"", name:{en:"Malmont",fr:"Malmontagne",de:"Malmont",ru:"Мальмонт"}},
{x:16725,y:-25190,type:5, level:0, icon:"", name:{en:"Ruins of Mossok",fr:"Ruines de Mossok",de:"Ruinen von Mossok",ru:"Руины Моссока"}},
{x:18414,y:-26519,type:5, level:0, icon:"", name:{en:"Ash Gorge",fr:"Gorge des Cendres",de:"Schlucht der Asche",ru:"Пепельная Пасть"}},
{x:18794,y:-26399,type:5, level:0, icon:"", name:{en:"Thesos Springs",fr:"Sources de Thesos",de:"Thesos-Quelle",ru:"Источники Фессоса"}},
{x:19100,y:-26487,type:5, level:0, icon:"", name:{en:"Thesos Billabong",fr:"Mare de Thesos",de:"Thesos-Flussbett",ru:"Заводи Фессоса"}},
{x:19225,y:-26668,type:5, level:0, icon:"", name:{en:"South Range",fr:"Chaîne Sud",de:"Südliche Hügelkette",ru:"Южная Цепь"}},
{x:19561,y:-26434,type:5, level:0, icon:"", name:{en:"Thesos Falls Tor",fr:"Butte des Chutes de Thesos",de:"Thesos-Wasserfallpforte",ru:"Скала Водопада Фессоса"}},
{x:20128,y:-26885,type:5, level:0, icon:"", name:{en:"Kipucka Plain",fr:"Plaine des Kipuckas",de:"Kipucka-Ebene",ru:"Равнина Кипакки"}},
{x:19224,y:-25282,type:5, level:0, icon:"", name:{en:"Dragon&#039;s Spine",fr:"Colonne du Dragon",de:"Stachel des Drachen",ru:"Драконий Хребет"}},
{x:18837,y:-25162,type:5, level:0, icon:"", name:{en:"Lucenthead Hurst",fr:"Horst Etincelant",de:"Grat des Funkelnden Waldes",ru:"Сопка Сверкающая Голова"}},
{x:19276,y:-26002,type:5, level:0, icon:"", name:{en:"Dragon&#039;s Gullet Impasse",fr:"Impasse de la Gorge du Dragon",de:"Drachenschlund-Klamm",ru:"Глотка Дракона "}},
{x:18101,y:-25620,type:5, level:0, icon:"", name:{en:"Flaming Forest",fr:"Forêt Enflammée",de:"Flammender Wald",ru:"Пылающий лес"}},
{x:17860,y:-26047,type:5, level:0, icon:"", name:{en:"Varynx Haunt",fr:"Repère des Varinx",de:"Varinx-Revier",ru:"Обиталище Варинксов"}},
{x:17506,y:-26304,type:5, level:0, icon:"", name:{en:"Secret Kami Oasis",fr:"Oasis Secret des Kamis",de:"Geheime Kami-Oase",ru:"Тайный Оазис Ками"}},
{x:25837,y:-24278,type:5, level:0, icon:"", name:{en:"Karavan Camp of Aelius Dunes",fr:"Camp Karavan des Dunes d&#039;Aelius",de:"Karavan-Lager der Aelius-Dünen",ru:"Лагерь Каравана в дюнах Аэлиуса"}},
{x:24902,y:-24011,type:5, level:0, icon:"", name:{en:"Kami Camp of Aelius Dunes",fr:"Camp Kami des Dunes d&#039;Aelius",de:"Kami-Lager der Aelius-Dünen",ru:"Лагерь Ками в дюнах Аэлиуса"}},
{x:2735,y:-17124,type:5, level:0, icon:"", name:{en:"Cattle Room",fr:"Salle d&#039;élevage",de:"Zuchtkammer",ru:"cattle room"}},
{x:2016,y:-16704,type:5, level:0, icon:"", name:{en:"Dark Mines",fr:"Mines Obscures",de:"Dunkle Minen",ru:"NotFound:(place)ru.kitiniere_dark_mines.place"}},
{x:2027,y:-16979,type:5, level:0, icon:"", name:{en:"Prime Eggs Room",fr:"Chambre des oeufs primaires",de:"Haupt-Gelegeraum",ru:"NotFound:(place)ru.kitiniere_eggs_room.place"}},
{x:2699,y:-16881,type:5, level:0, icon:"", name:{en:"Old Mines",fr:"Mines Anciennes",de:"Alte Minen",ru:"NotFound:(place)ru.kitiniere_old_mines.place"}},
{x:2399,y:-16500,type:5, level:0, icon:"", name:{en:"Princes&#039; Room",fr:"Salle des princes",de:"Raum der Prinzen",ru:"NotFound:(place)ru.kitiniere_princes_room.place"}},
{x:2372,y:-16956,type:5, level:0, icon:"", name:{en:"Queen&#039;s Room",fr:"Salle de la reine",de:"Halle der Königin",ru:"NotFound:(place)ru.kitiniere_queen_room.place"}},
{x:4653,y:-836,type:5, level:0, icon:"", name:{en:"Mullgrove",fr:"Bosquet Inférieur",de:"Kapwald",ru:"Маллгроув"}},
{x:4499,y:-904,type:5, level:0, icon:"", name:{en:"Eastgrove Gate",fr:"Porte du Bosquet Est",de:"Ostwaldpforte",ru:"Врата Восточной рощи"}},
{x:4119,y:-1086,type:5, level:0, icon:"", name:{en:"Westgrove Gate",fr:"Porte du Bosquet Ouest",de:"Westwaldpforte",ru:"Врата Западной рощи"}},
{x:4974,y:-625,type:5, level:0, icon:"", name:{en:"Highgrove",fr:"Bosquet Supérieur",de:"Hochwald",ru:"Вышняя роща"}},
{x:1868,y:-1664,type:5, level:0, icon:"", name:{en:"Little Mountain",fr:"Petite Montagne",de:"kleiner Berg",ru:"Маленькая гора"}},
{x:1272,y:-806,type:5, level:0, icon:"", name:{en:"Virginia Falls",fr:"Chutes de Virginia",de:"Virginia-Wasserfall",ru:"Водопад Виргиния"}},
{x:710,y:-821,type:5, level:0, icon:"", name:{en:"Tylini Gate",fr:"Porte de Tylini",de:"Tylini-Pforte",ru:"Врата Тайлини"}},
{x:5524,y:-7762,type:5, level:0, icon:"", name:{en:"Maze of Sprite",fr:"Labyrinthe des Lutins",de:"Geisterlabyrinth",ru:"Лабиринт Фей"}},
{x:5903,y:-7516,type:5, level:0, icon:"", name:{en:"Chilling Swamp",fr:"Marais des Frissons",de:"Sumpf der Angst",ru:"Знобкое Болото "}},
{x:5828,y:-6802,type:5, level:0, icon:"", name:{en:"Falls of the Two Totems",fr:"Chutes des Deux Totems",de:"Wasserfall der zwei Totems",ru:"Водопады двух Тотемов"}},
{x:5518,y:-6831,type:5, level:0, icon:"", name:{en:"Folly plain",fr:"Plaine de la Folie",de:"Ebene der Torheit",ru:"Равнина недомыслия"}},
{x:3915,y:-5672,type:5, level:0, icon:"", name:{en:"Fencoomb Ponds ",fr:"Etangs de Fencoomb",de:"Moorteiche",ru:"Пруды Фенкумба"}},
{x:4165,y:-5926,type:5, level:0, icon:"", name:{en:"Fearing Fens",fr:"Marécages de l&#039;Angoisse",de:"gefürchteter Sumpf",ru:"Трясина испуга"}},
{x:4245,y:-6159,type:5, level:0, icon:"", name:{en:"Slough of the Demon",fr:"Abîme du Démon",de:"Dämonenmoor",ru:"Топи Демона"}},
{x:4154,y:-6673,type:5, level:0, icon:"", name:{en:"Panting Pass",fr:"Col des Palpitations",de:"schwieriger Pfad",ru:"Удушливый перевал"}},
{x:2401,y:-2084,type:5, level:0, icon:"", name:{en:"Tunnel of Woe",fr:"Tunnel des Malheurs",de:"Unglückstunnel",ru:"Тоннель страданий"}},
{x:2875,y:-2079,type:5, level:0, icon:"", name:{en:"Runninghill Pass",fr:"Col Vallonné",de:"Gebirgspass",ru:"Ущелье Бегущего холма"}},
{x:3281,y:-2479,type:5, level:0, icon:"", name:{en:"Towerbridge Way",fr:"Route de Towerbridge",de:"Turmbrückenweg",ru:"Дороги у Башенного Моста"}},
{x:3198,y:-1756,type:5, level:0, icon:"", name:{en:"Torbak Trail Hill",fr:"Sentier des Torbaks",de:"Anhöhe der Torbaks",ru:"Холм Тропы Торбаков"}},
{x:3606,y:-1849,type:5, level:0, icon:"", name:{en:"Killing Tree",fr:"Arbre au Massacre",de:"Todesbaum",ru:"Дерево - убийца"}},
{x:3844,y:-1758,type:5, level:0, icon:"", name:{en:"Smitten Mount",fr:"Montagne du Tourment",de:"Berg der Pein",ru:"Обрушенная гора"}},
{x:3679,y:-2869,type:5, level:0, icon:"", name:{en:"Psykopla Knoll",fr:"Monticule des Psykoplas",de:"Psykopla-Hügel",ru:"Холмы Псикопл "}},
{x:4158,y:-3520,type:5, level:0, icon:"", name:{en:"Matis Arena",fr:"Arène Matis",de:"Matis-Arena",ru:"Арена матисов"}},
{x:4835,y:-4781,type:5, level:0, icon:"", name:{en:"Charmers&#039; Way",fr:"Route des Charmeurs",de:"Weg der Charmeure",ru:"Дорога Чародеев"}},
{x:5935,y:-5906,type:5, level:0, icon:"", name:{en:"Kami Circle",fr:"Cercle Kamic",de:"Kami-Kreis",ru:"Круг Ками"}},
{x:5356,y:-5925,type:5, level:0, icon:"", name:{en:"The Trove",fr:"Trésor",de:"Der Hort",ru:"Сокровищница"}},
{x:4488,y:-5523,type:5, level:0, icon:"", name:{en:"Den of Fury",fr:"Antre de la Fureur",de:"Höhle des Zorns",ru:"Логово ярости"}},
{x:3964,y:-4999,type:5, level:0, icon:"", name:{en:"The Rising",fr:"Poussée",de:"Erhebung",ru:"Возвышение"}},
{x:15109,y:-1274,type:5, level:0, icon:"", name:{en:"Kami Camp of Almati Wood",fr:"Camp Kami du Bois d&#039;Almati",de:"Kami-Lager des Almati-Waldes",ru:"Лагерь Ками в лесу Алмати"}},
{x:14347,y:-641,type:5, level:0, icon:"", name:{en:"Karavan Camp of Almati Wood",fr:"Camp Karavan du Bois d&#039;Almati",de:"Karavan-Lager des Almati-Waldes",ru:"Лагерь Каравана в дюнах Аэлиуса"}},
{x:10572,y:-11115,type:5, level:0, icon:"", name:{en:"Hunting grounds",fr:"Terrains de chasse",de:"Jagdgründe",ru:"Охотничьи Угодья"}},
{x:9795,y:-10994,type:5, level:0, icon:"", name:{en:"Shattered ruins",fr:"Ruines dévastées",de:"Zerstörte Ruinen",ru:"Разрушенный город"}},
{x:9860,y:-10966,type:5, level:0, icon:"", name:{en:"Ruins of Silan",fr:"Ruines de Silan",de:"Ruinen von Silan",ru:"Руины Силана"}},
{x:9672,y:-10784,type:5, level:0, icon:"", name:{en:"Throne room",fr:"Salle du trône",de:"Thronsaal",ru:"Тронный зал"}},
{x:10265,y:-11671,type:5, level:0, icon:"", name:{en:"Rangers&#039; land",fr:"contrée des Rangers",de:"Gebiet der Ranger",ru:"Земля Рэйнджеров"}},
{x:10160,y:-11599,type:5, level:0, icon:"", name:{en:"Arena",fr:"Arène",de:"Arena",ru:"Арена"}},
{x:9529,y:-11411,type:5, level:0, icon:"", name:{en:"Blight zone",fr:"Pays malade",de:"Krankes Land",ru:"Зона поражения"}},
{x:9043,y:-10851,type:5, level:0, icon:"", name:{en:"Shining Lake",fr:"Lac étincelant",de:"Funkelnder See",ru:"Сверкающее озеро"}},
{x:8398,y:-10636,type:5, level:0, icon:"", name:{en:"Kitins Jungle",fr:"Jungle aux Kitins",de:"Kitin-Dschungel",ru:"Джунгли Китинов"}},
{x:10407,y:-11864,type:5, level:0, icon:"", name:{en:"Karavan Embassy",fr:"Ambassade Karavan",de:"Karavan-Botschaft",ru:"Посольство Каравана"}},
{x:10423,y:-11683,type:5, level:0, icon:"", name:{en:"Kami Enclave",fr:"Enclave Kami",de:"Kami-Enklave",ru:"Анклав Ками"}},
{x:9518,y:-6627,type:5, level:0, icon:"", name:{en:"The Concha",fr:"Conque",de:"Die Muschel",ru:"Конха"}},
{x:9127,y:-6869,type:5, level:0, icon:"", name:{en:"Destiny&#039;s End",fr:"Fin de la Destinée",de:"Schicksalsende",ru:"Роковой Предел"}},
{x:8560,y:-7088,type:5, level:0, icon:"", name:{en:"Hammer Hills",fr:"Colline Martelée",de:"Hammerhöhen",ru:"Холмы - Молоты"}},
{x:9054,y:-7667,type:5, level:0, icon:"", name:{en:"The Forsaken Camp",fr:"Campement Abandonné",de:"Das verlassene Lager",ru:"Покинутый лагерь"}},
{x:5782,y:-10248,type:5, level:0, icon:"", name:{en:"Ruins of Leron",fr:"Ruines de Léron",de:"Ruinen von Leron",ru:"Руины Лероны"}},
{x:5648,y:-10617,type:5, level:0, icon:"", name:{en:"Halls of Darkring",fr:"Salles de la Bague Noire",de:"Hallen des Schwarzen Rings",ru:"Залы Темного Кольца"}},
{x:6080,y:-11341,type:5, level:0, icon:"", name:{en:"The Fallen Tree",fr:"Arbre Abattu",de:"Der gefallene Baum",ru:"Падшее Дерево"}},
{x:6058,y:-11905,type:5, level:0, icon:"", name:{en:"Windygate Column",fr:"Colonne de la Porte des Vents",de:"Säule der Sturmpforte",ru:"Колонна Ветряных Врат"}},
{x:5869,y:-12418,type:5, level:0, icon:"", name:{en:"Land of Plenty",fr:"Terre d&#039;Abondance",de:"Land des Überflusses",ru:"Земли Изобилия"}},
{x:6280,y:-12217,type:5, level:0, icon:"", name:{en:"Highwell",fr:"Flaque Supérieur",de:"Oberer Schacht",ru:"Верхние Колодцы"}},
{x:7069,y:-12639,type:5, level:0, icon:"", name:{en:"Underwood Woe",fr:"Sous-Bois du Malheur",de:"Unterholz des Unglücks",ru:"Горе Подлесное"}},
{x:6639,y:-12958,type:5, level:0, icon:"", name:{en:"Grotto Pass",fr:"Col des Grottes",de:"Höhlenpass",ru:"Проход Грота"}},
{x:6091,y:-13251,type:5, level:0, icon:"", name:{en:"Three Totems Pass",fr:"Col des Trois Totems",de:"Dreitotempass",ru:"Проход Трех Тотемов"}},
{x:6524,y:-13305,type:5, level:0, icon:"", name:{en:"Giant Mushroom Pass",fr:"Col du Champignon Géant",de:"Riesenpilzpass",ru:"Проход Грибов Гигантов"}},
{x:6071,y:-13755,type:5, level:0, icon:"", name:{en:"Alleyways Bluff",fr:"Ruelle",de:"Klippendurchgang",ru:"Утес трех переулков"}},
{x:6085,y:-14271,type:5, level:0, icon:"", name:{en:"High Henge",fr:"Cromlech Supérieur",de:"Oberer Steinkreis",ru:"Высокие Мезолиты"}},
{x:6480,y:-14963,type:5, level:0, icon:"", name:{en:"Lowhills",fr:"Basses Collines",de:"Kleine Hügel",ru:"Малохолмье"}},
{x:6635,y:-15688,type:5, level:0, icon:"", name:{en:"Darkenwell",fr:"Flaque Sombre",de:"Dunkler Schacht",ru:"Черный омут"}},
{x:6263,y:-15999,type:5, level:0, icon:"", name:{en:"Eerie Valley",fr:"Vallée Sinistre",de:"Schauriges Tal",ru:"Долина Ужаса"}},
{x:5754,y:-16483,type:5, level:0, icon:"", name:{en:"Yelkgrove",fr:"Bosquet des Yelks",de:"Yelkwäldchen",ru:"Роща Йелков"}},
{x:7024,y:-16845,type:5, level:0, icon:"", name:{en:"Spooky End",fr:"Limite de l&#039;Angoisse",de:"Seltsames Ende",ru:"Мрачный Предел"}},
{x:3278,y:-10319,type:5, level:0, icon:"", name:{en:"Deadtree Hole",fr:"Trou aux Arbres Morts",de:"Grube des toten Baumes",ru:"Яма Мертводрева"}},
{x:3600,y:-10475,type:5, level:0, icon:"", name:{en:"Sap Marsh",fr:"Marécage de la Sève",de:"Sapsumpf",ru:"Болота сэп"}},
{x:3128,y:-10639,type:5, level:0, icon:"", name:{en:"Furtive Waters",fr:"Eaux Furtives",de:"Tückische Wasser",ru:"Потаенные Воды"}},
{x:3281,y:-10641,type:5, level:0, icon:"", name:{en:"Ruins of Tryde",fr:"Ruines de Tryde",de:"Ruinen von Tryde",ru:"Руины Трайда"}},
{x:2798,y:-10576,type:5, level:0, icon:"", name:{en:"Shooting Meadow",fr:"Pré Lancinant",de:"Stechende Wiese",ru:"Луг Застрельщиков"}},
{x:699,y:-14462,type:5, level:0, icon:"", name:{en:"Wings of Spite",fr:"Ailes du Dépit",de:"Flügel der Feindschaft",ru:"Крылья Досады"}},
{x:485,y:-13887,type:5, level:0, icon:"", name:{en:"Great Mushroom Wood",fr:"Bois du Champignon Géant",de:"Großer Pilzwald",ru:"Великие Грибные Леса "}},
{x:987,y:-13590,type:5, level:0, icon:"", name:{en:"The Cursed Cavern",fr:"Caverne Maudite",de:"Die verfluchte Höhle",ru:"Проклятая Пещера"}},
{x:959,y:-13279,type:5, level:0, icon:"", name:{en:"Grotto Walk",fr:"Promenade des Grottes",de:"Grottenweg",ru:"Тропа Грота"}},
{x:1599,y:-14402,type:5, level:0, icon:"", name:{en:"Midway Point",fr:"Point du Milieu",de:"Mittelpunkt",ru:"Срединное Место"}},
{x:1983,y:-13585,type:5, level:0, icon:"", name:{en:"Saplake Major",fr:"Lac Majeur de Sève",de:"Großer Sapsee",ru:"Большое озеро сэп"}},
{x:2715,y:-13974,type:5, level:0, icon:"", name:{en:"Haunted Gorge",fr:"Gorge Hantée",de:"Schlucht des Spuks",ru:"Ущелье Призраков"}},
{x:2251,y:-14553,type:5, level:0, icon:"", name:{en:"Dragon Horns Valley",fr:"Vallée des Cornes du Dragon",de:"Drachenhorntal",ru:"Долина Драконова Рога"}},
{x:1925,y:-14722,type:5, level:0, icon:"", name:{en:"Cursed Impasse",fr:"Impasse Maudite",de:"Verfluchte Sackgasse",ru:"Проклятый Тупик"}},
{x:2576,y:-15193,type:5, level:0, icon:"", name:{en:"Hall of the Mountain Bane",fr:"Caverne de la Montagne Empoisonnée",de:"Halle des vergifteten Berges",ru:"Зал Горного Бедствия "}},
{x:2245,y:-15512,type:5, level:0, icon:"", name:{en:"Demon&#039;s Lap",fr:"Giron du Démon",de:"Dämonenschoß",ru:"Ложбина Демона"}},
{x:19285,y:-31925,type:5, level:0, icon:"", name:{en:"Gusty Gorge",fr:"Gorge des Vents",de:"Stürmische Schlucht",ru:"Теснина Ветров"}},
{x:18558,y:-29913,type:5, level:0, icon:"", name:{en:"Lake Superior",fr:"Lac Supérieur",de:"Großer See",ru:"Озеро Верхнее"}},
{x:17472,y:-30310,type:5, level:0, icon:"", name:{en:"Hush Hole",fr:"Trou du Silence",de:"Grube der Stille",ru:"Нора Тссс!"}},
{x:14755,y:-30463,type:5, level:0, icon:"", name:{en:"Witchy Coves",fr:"Criques Ensorcelées",de:"Verzauberte Bucht",ru:"Пещеры Ведьм "}},
{x:15434,y:-30345,type:5, level:0, icon:"", name:{en:"Muse Watch Lake",fr:"Lac des Veilleurs de Songe",de:"See der Traumwächter",ru:"Озеро Наблюдающих за Музами"}},
{x:16256,y:-30153,type:5, level:0, icon:"", name:{en:"Big Sleep Mountain",fr:"Montagne du Profond Sommeil",de:"Schlafender Berg",ru:"Гора Великого Сна"}},
{x:16054,y:-30577,type:5, level:0, icon:"", name:{en:"Windy Way",fr:"Route des Vents",de:"Weg der Winde",ru:"Дороги Ветров"}},
{x:13950,y:-31682,type:5, level:0, icon:"", name:{en:"Torbak Spread",fr:"Vallée des Torbaks",de:"Tal der Torbaks",ru:"Торбацкие Просторы"}},
{x:14248,y:-31845,type:5, level:0, icon:"", name:{en:"Cute Springs",fr:"Sources des Cutes",de:"Cute-Quellsee",ru:"Ключи Кьют"}},
{x:14312,y:-32089,type:5, level:0, icon:"", name:{en:"Cute Springs Twining",fr:"Sources Agitées des Cutes",de:"Sprudelnde Cute-Quellen",ru:"Плетение Кьют"}},
{x:14954,y:-31931,type:5, level:0, icon:"", name:{en:"Liberty Gateway",fr:"Porte de la Liberté",de:"Tor zur Freiheit",ru:"Врата Вольности"}},
{x:14819,y:-32737,type:5, level:0, icon:"", name:{en:"Twin Tops",fr:"Cimes Jumelles",de:"Zwillingsgipfel",ru:"Пики Близнецы"}},
{x:15037,y:-33108,type:5, level:0, icon:"", name:{en:"Rift of Strife",fr:"Crevasse de la Dissension",de:"Spalte des Unfriedens",ru:"Разлом Противостояния"}},
{x:15367,y:-33770,type:5, level:0, icon:"", name:{en:"Deathfly Plain",fr:"Plaine Perdue",de:"Verlassene Ebene",ru:"Роковая Равнина"}},
{x:15266,y:-33999,type:5, level:0, icon:"", name:{en:"Fount Porch",fr:"Porche des Sources",de:"Quellhalle",ru:"Террасы Родника"}},
{x:15023,y:-33968,type:5, level:0, icon:"", name:{en:"Fount Lake",fr:"Lac des Sources",de:"Quellsee",ru:"Озеро Родника"}},
{x:15600,y:-34633,type:5, level:0, icon:"", name:{en:"Southlakes",fr:"Lacs du Sud",de:"Seen des Südens",ru:"Южные Озера"}},
{x:15925,y:-34627,type:5, level:0, icon:"", name:{en:"Ponders End Pillars",fr:"Colonnes de la Méditation",de:"Säulen der Besinnung",ru:"Колонны Предела Раздумий"}},
{x:18056,y:-33597,type:5, level:0, icon:"", name:{en:"Tippy Peaks",fr:"Pics Instables",de:"Brüchige Gipfel",ru:"Острые Пики"}},
{x:17924,y:-33925,type:5, level:0, icon:"", name:{en:"Baily Lagoon",fr:"Lagon à la Dérive",de:"Lagune des Driftens",ru:"Лагуна Бэйли"}},
{x:17358,y:-34558,type:5, level:0, icon:"", name:{en:"Mire of the Dire",fr:"Bourbier de la Misère",de:"Grässlicher Morast",ru:"Зловещая Трясина"}},
{x:17724,y:-34643,type:5, level:0, icon:"", name:{en:"Graveyard Walk",fr:"Promenade Caverneuse",de:"Friedhofsweg",ru:"Кладбищенская тропа"}},
{x:18299,y:-34019,type:5, level:0, icon:"", name:{en:"Enchanted Circle",fr:"Cercle Enchanté",de:"Verzauberter Kreis",ru:"Зачарованный круг"}},
{x:16140,y:-31892,type:5, level:0, icon:"", name:{en:"Darkmoor",fr:"Landes Obscures",de:"Schwarzmoor",ru:"Мрачные Топи"}},
{x:17607,y:-32201,type:5, level:0, icon:"", name:{en:"Lakenisle",fr:"Lac de l&#039;Ile",de:"Seeninsel",ru:"Озерная отмель"}},
{x:18383,y:-32883,type:5, level:0, icon:"", name:{en:"Blackwater",fr:"Eaux Obscures",de:"Dunkles Wasser",ru:"Черноводье"}},
{x:25664,y:-30468,type:5, level:0, icon:"", name:{en:"Karavan Camp of Olkern Lake",fr:"Camp Karavan du Lac d&#039;Olkern",de:"Karavan-Lager des Olkern-Sees",ru:"Лагерь Каравана у озера Олкерн"}},
{x:24912,y:-29705,type:5, level:0, icon:"", name:{en:"Kami Camp of Olkern Lake",fr:"Camp Kami du Lac d&#039;Olkern",de:"Kami-Lager des Olkern-Sees",ru:"Лагерь Ками у озера Олкерн"}},
{x:9849,y:-4552,type:5, level:0, icon:"", name:{en:"Kipee Mull",fr:"Promontoire des Kipees",de:"Kap der Kipees",ru:"Мыс Киппи"}},
{x:10318,y:-4244,type:5, level:0, icon:"", name:{en:"Sly Lake",fr:"Lac Dissimulé",de:"Verborgener See",ru:"Озеро Коварства"}},
{x:10866,y:-4563,type:5, level:0, icon:"", name:{en:"Underway Arch",fr:"Arc Caché",de:"Wölbung",ru:"Свод Подземелий"}},
{x:11666,y:-5120,type:5, level:0, icon:"", name:{en:"Withered Stock",fr:"Souche Flétrie",de:"Verdorrter Stamm",ru:"Заброшенный Кладезь "}},
{x:8883,y:-3285,type:5, level:0, icon:"", name:{en:"Still Waters",fr:"Eaux Paisibles",de:"Stille Wasser",ru:"Недвижные воды"}},
{x:9203,y:-3865,type:5, level:0, icon:"", name:{en:"Mossy Scarp",fr:"Escarpement Moussu",de:"moosige Böschung",ru:"Замшелый откос"}},
{x:9841,y:-3760,type:5, level:0, icon:"", name:{en:"Mektoub Waterhole",fr:"Mare des Mektoubs",de:"Mektoub-Wasserloch",ru:"Водопой Мектубов"}},
{x:9524,y:-4232,type:5, level:0, icon:"", name:{en:"Mystic Falls",fr:"Chutes Mystiques",de:"mystischer Wasserfall",ru:"Водопады Тайн"}},
{x:10453,y:-3380,type:5, level:0, icon:"", name:{en:"Palm Valley",fr:"Vallée des Palmiers",de:"Tal der Palmen",ru:"Долина Пальм"}},
{x:10760,y:-3761,type:5, level:0, icon:"", name:{en:"Maiden Pond",fr:"Etang Vierge",de:"Jungfernteich",ru:"Девственный Пруд"}},
{x:10496,y:-4061,type:5, level:0, icon:"", name:{en:"Slavenhurst",fr:"Boissclave",de:"Sklavenwald",ru:"Холм Славини"}},
{x:11611,y:-3449,type:5, level:0, icon:"", name:{en:"Fowling Lake",fr:"Lac aux Oiseaux",de:"Vogelsee",ru:"Озеро Птицеловов"}},
{x:11692,y:-3117,type:5, level:0, icon:"", name:{en:"Great Boulder Tunnel",fr:"Grand Tunnel Rocailleux",de:"Großer Brocken-Tunnel",ru:"Тоннель Великой глыбы"}},
{x:11506,y:-1917,type:5, level:0, icon:"", name:{en:"Paramount Stock",fr:"Souche Capitale",de:"Stamm der Aussicht",ru:"Верховный Кладезь"}},
{x:10107,y:-2132,type:5, level:0, icon:"", name:{en:"The Lost Valley",fr:"Vallée Perdue",de:"Das verlorene Tal",ru:"Затерянная долина"}},
{x:11808,y:-2513,type:5, level:0, icon:"", name:{en:"Mull of Despond",fr:"Promontoire du Désespoir",de:"Kap der Verzweiflung",ru:"Чащобы безнадежности"}},
{x:9511,y:-2293,type:5, level:0, icon:"", name:{en:"Bighorn Mountain",fr:"Montagne aux Cornes",de:"Hornberg",ru:"Гора Бигхорн"}},
{x:9355,y:-2479,type:5, level:0, icon:"", name:{en:"Fustylake",fr:"Lac Poussiéreux",de:"Stinkender See",ru:"Прогорклое озеро"}},
{x:9483,y:-2964,type:5, level:0, icon:"", name:{en:"Kincher Arch",fr:"Arc des Kinchers",de:"Kincherbogen",ru:"Арка Кинчеров"}},
{x:7190,y:-1996,type:5, level:0, icon:"", name:{en:"The Great Outback",fr:"Arrière Pays",de:"Großes Buschland",ru:"Великая Целина"}},
{x:7513,y:-2236,type:5, level:0, icon:"", name:{en:"Desertstock",fr:"Désertstock",de:"Trockener Stamm",ru:"Пустынный кладезь"}},
{x:7868,y:-2411,type:5, level:0, icon:"", name:{en:"Stainmoor",fr:"Lande Tachetée",de:"Fleckenmoor",ru:"Торфяники Стэйнмура"}},
{x:8081,y:-2325,type:5, level:0, icon:"", name:{en:"Stainmoor Gate",fr:"Porte de la Lande Tachetée",de:"Fleckenmoorpforte",ru:"Врата Стэйнмура"}},
{x:8245,y:-1835,type:5, level:0, icon:"", name:{en:"Highland Impasse",fr:"Impasse des Hautes Terres",de:"Hochland-Pass",ru:"Непроходимое Нагорье"}},
{x:8074,y:-3286,type:5, level:0, icon:"", name:{en:"Hopedell",fr:"Vallon des Espérances",de:"Tal der Hoffnung",ru:"Хоупделл"}},
{x:1039,y:-10535,type:4, level:200, icon:"", name:{en:"Abyss of Ichor",fr:"Gouffre d&#039;Ichor",de:"Abgrund von Ichor",ru:"Кровоточащая бездна"}},
{x:18853,y:-24670,type:4, level:50, icon:"", name:{en:"Imperial Dunes",fr:"Dune Impériale",de:"Imperiale Dünen",ru:"Имперские дюны"}},
{x:17788,y:-25026,type:4, level:100, icon:"", name:{en:"Oflovak&#039;s Oasis",fr:"Oasis d&#039;Oflovak",de:"Oflovaks Oase",ru:"Оазис Офловака"}},
{x:17118,y:-25188,type:4, level:150, icon:"", name:{en:"Frahar Towers",fr:"Tours de Frahar",de:"Frahar-Türme",ru:"Башни фрахаров"}},
{x:19692,y:-25354,type:4, level:150, icon:"", name:{en:"Sawdust Mines",fr:"Mines de Sciure",de:"Sägemehlminen",ru:"Древесная шахта"}},
{x:16399,y:-24959,type:4, level:200, icon:"", name:{en:"Dunes of Exile",fr:"Dunes de l&#039;Exil",de:"Dünen der Verbannung",ru:"Дюны Изгнания"}},
{x:19118,y:-26510,type:4, level:200, icon:"", name:{en:"Savage Dunes",fr:"Dunes Sauvages",de:"Wilde Dünen",ru:"Дикие дюны"}},
{x:19419,y:-24957,type:4, level:200, icon:"", name:{en:"Outlaw Canyon",fr:"Canyon Interdit",de:"Canyon der Gesetzlosen",ru:"Каньон Беззакония"}},
{x:17387,y:-26107,type:4, level:250, icon:"", name:{en:"Scorched Corridor",fr:"Couloir Brûlé",de:"Sengende Schlucht",ru:"Выжженный коридор"}},
{x:25358,y:-24164,type:4, level:0, icon:"", name:{en:"Aelius Dunes",fr:"Dunes d&#039;Aelius",de:"Aelius-Dünen",ru:"Дюны Аэлиуса"}},
{x:22085,y:-26165,type:4, level:0, icon:"", name:{en:"Fyros Island",fr:"Ile Fyros",de:"Fyros-Insel",ru:"Континент файросов"}},
{x:2356,y:-16864,type:4, level:0, icon:"", name:{en:"Kitins&#039; Lair",fr:"Kitinière",de:"Kitin-Nest",ru:"Логово китинов"}},
{x:4520,y:-1106,type:4, level:250, icon:"", name:{en:"Grove of Confusion",fr:"Bosquet de la Confusion",de:"Hain der Verwirrung",ru:"Смятенная роща"}},
{x:1411,y:-1184,type:4, level:200, icon:"", name:{en:"Hidden Source",fr:"Source Cachée",de:"Versteckte Quelle",ru:"Потаенный источник"}},
{x:5484,y:-7053,type:4, level:200, icon:"", name:{en:"Heretic&#039;s Hovel",fr:"Masure de l&#039;Hérétique",de:"Ketzers Hütte",ru:"Логово еретиков"}},
{x:3961,y:-5968,type:4, level:200, icon:"", name:{en:"Upper Bog",fr:"Marais Supérieur",de:"Oberer Sumpf",ru:"Верховые топи"}},
{x:2834,y:-2092,type:4, level:150, icon:"", name:{en:"Knoll of Dissent",fr:"Tertre de la Dissidence",de:"Hügel der Abtrünnigen",ru:"Холмы раскола"}},
{x:4142,y:-3703,type:4, level:50, icon:"", name:{en:"Majestic Garden",fr:"Jardin Majestueux",de:"Majestätischer Garten",ru:"Сады Величия"}},
{x:4996,y:-5649,type:4, level:100, icon:"", name:{en:"Fleeting Garden",fr:"Jardin Fugace",de:"Vergänglicher Garten",ru:"Ускользающий сад"}},
{x:14710,y:-968,type:4, level:0, icon:"", name:{en:"Almati Wood",fr:"Bois d&#039;Almati",de:"Almati-Wald",ru:"Леса Алмати"}},
{x:1462,y:-6632,type:4, level:0, icon:"", name:{en:"Matis Island",fr:"Ile Matis",de:"Matis-Insel",ru:"Континент матисов"}},
{x:9696,y:-11195,type:4, level:0, icon:"", name:{en:"Lands to Explore",fr:"Contrées à explorer",de:"Die Entdeckenswerten Gefilde",ru:"Малоизученные Земли "}},
{x:8786,y:-7196,type:4, level:200, icon:"", name:{en:"Nexus",fr:"Nexus",de:"Nexus",ru:"Нексус"}},
{x:5921,y:-10726,type:4, level:150, icon:"", name:{en:"Windy Gate",fr:"Porte des Vents",de:"Pforte des Sturms",ru:"Врата Ветров"}},
{x:6479,y:-12802,type:4, level:200, icon:"", name:{en:"Elusive Forest",fr:"Forêt Insaisissable",de:"Unergründlicher Wald",ru:"Эфемерный лес"}},
{x:6399,y:-14720,type:4, level:150, icon:"", name:{en:"Gate of Obscurity",fr:"Porte de l&#039;Obscurité",de:"Tor der Dunkelheit",ru:"Врата неопределенности"}},
{x:6240,y:-16324,type:4, level:200, icon:"", name:{en:"Trench of Trials",fr:"La Fosse aux Epreuves",de:"Graben der Heimsuchung",ru:"Ров Испытаний"}},
{x:3200,y:-10522,type:4, level:250, icon:"", name:{en:"Under Spring",fr:"Sources Interdites",de:"Verbotene Quellen",ru:"Подземный источник"}},
{x:1009,y:-14080,type:4, level:250, icon:"", name:{en:"Land of Continuity",fr:"Terre de la Continuité",de:"Land der Kontinuität",ru:"Земли непрерывности"}},
{x:2229,y:-13846,type:4, level:250, icon:"", name:{en:"Sunken City",fr:"Cité Engloutie",de:"Versunkene Stadt",ru:"Погребенный город"}},
{x:2147,y:-15178,type:4, level:250, icon:"", name:{en:"Forbidden Depths",fr:"Profondeurs Interdites",de:"Verbotene Tiefen",ru:"Запретные Глубины"}},
{x:18557,y:-30800,type:4, level:250, icon:"", name:{en:"Lagoons of Loria",fr:"Lagons de Loria",de:"Lagunen von Loria",ru:"Лагуны Лории"}},
{x:15675,y:-30635,type:4, level:100, icon:"", name:{en:"Winds of Muse",fr:"Vents du Songe",de:"Winde der Musen",ru:"Ветра Муз"}},
{x:14480,y:-32391,type:4, level:200, icon:"", name:{en:"Bounty Beaches",fr:"Plages d&#039;Abondance",de:"Strände des Überflusses",ru:"Отмели Изобилия"}},
{x:15279,y:-34153,type:4, level:150, icon:"", name:{en:"Fount",fr:"Source",de:"Ursprung",ru:"Родник"}},
{x:17763,y:-34160,type:4, level:200, icon:"", name:{en:"Enchanted Isle",fr:"Ile Enchantée",de:"Verzauberte Insel",ru:"Зачарованный Остров"}},
{x:16971,y:-32222,type:4, level:50, icon:"", name:{en:"Liberty Lake",fr:"Lac de la Liberté",de:"See der Freiheit",ru:"Озеро Свободы"}},
{x:17055,y:-31481,type:4, level:100, icon:"", name:{en:"Dew Drops",fr:"Chutes de la Rosée",de:"Tautropfen",ru:"Капли Росы"}},
{x:16396,y:-33785,type:4, level:150, icon:"", name:{en:"Resting Water",fr:"Eau du Repos",de:"Ruhiges Wasser",ru:"Воды Спокойствия"}},
{x:25276,y:-30076,type:4, level:0, icon:"", name:{en:"Olkern Lake",fr:"Lac D&#039;Olkern",de:"Olkern-See",ru:"Озеро Олкерн"}},
{x:22004,y:-33918,type:4, level:0, icon:"", name:{en:"Tryker Island",fr:"Ile Tryker",de:"Tryker-Insel",ru:"Континент трайкеров"}},
{x:10957,y:-4721,type:4, level:200, icon:"", name:{en:"Grove of Umbra",fr:"Bosquet de l&#039;Ombre",de:"Hain von Umbra",ru:"Сумеречная роща"}},
{x:9460,y:-3499,type:4, level:50, icon:"", name:{en:"Cities of Intuition",fr:"Cités de l&#039;Intuition",de:"Städte der Einsicht",ru:"Города Провидцев"}},
{x:11017,y:-3359,type:4, level:100, icon:"", name:{en:"Maiden Grove",fr:"Bosquet Vierge",de:"Jungfräuliches Wäldchen",ru:"Роща Дев"}},
{x:10959,y:-2344,type:4, level:250, icon:"", name:{en:"Void",fr:"Vide",de:"Das Nichts",ru:"Великая Пустота"}},
{x:9493,y:-2640,type:4, level:150, icon:"", name:{en:"Haven of Purity",fr:"Havre de Pureté",de:"Hafen der Reinheit",ru:"Прибежище Благочестия"}},
{x:7917,y:-2559,type:4, level:200, icon:"", name:{en:"Knot of Dementia",fr:"Noeud de la Démence",de:"Knoten der Demenz",ru:"Узел безумия"}},
{x:8063,y:-4910,type:4, level:0, icon:"", name:{en:"Zoraï Island",fr:"Ile Zoraï",de:"Zorai-Insel",ru:"Остров Зораи"}},
{x:18579,y:-24305,type:3, level:0, icon:"lm_continent.png", name:{en:"North Pyr Stables",fr:"Etables Nord de Pyr",de:"Pyr Nord-Ställe",ru:"Стойла Северного Пайра"}},
{x:18789,y:-24620,type:3, level:0, icon:"lm_continent.png", name:{en:"South Pyr Stables",fr:"Etables Sud de Pyr",de:"Pyr Süd-Ställe",ru:"Стойла Южного Пайра"}},
{x:18968,y:-24415,type:3, level:0, icon:"lm_continent.png", name:{en:"East Pyr Stables",fr:"Etables Est de Pyr",de:"Pyr Ost-Ställe",ru:"Стойла Восточного Пайра"}},
{x:16562,y:-24732,type:3, level:0, icon:"lm_continent.png", name:{en:"Dyron Stables",fr:"Etables de Dyron",de:"Dyron Ställe",ru:"Стойла Дайрона"}},
{x:19513,y:-26143,type:3, level:0, icon:"lm_continent.png", name:{en:"Thesos Stables",fr:"Etables de Thesos",de:"Thesos Ställe",ru:"Стойла Фессоса"}},
{x:4715,y:-3220,type:3, level:0, icon:"lm_continent.png", name:{en:"Yrkanis Stables",fr:"Etables d&#039;Yrkanis",de:"Stall",ru:"Стойла Мектубов Ирканиса"}},
{x:3808,y:-3745,type:3, level:0, icon:"lm_continent.png", name:{en:"Natae Stables",fr:"Etables de Natae",de:"Natae Ställe",ru:"Стойла Нэтея"}},
{x:4248,y:-4195,type:3, level:0, icon:"lm_continent.png", name:{en:"Davae Stables",fr:"Etables de Davae",de:"Davae Ställe",ru:"Стойла Дэвея"}},
{x:4867,y:-4367,type:3, level:0, icon:"lm_continent.png", name:{en:"Avalae Stables",fr:"Etables d&#039;Avalae",de:"Avalae Ställe",ru:"Стойла Эвелея"}},
{x:15657,y:-33002,type:3, level:0, icon:"lm_continent.png", name:{en:"Windermeer Stables",fr:"Etables de Windermeer",de:"Windermeer Ställe",ru:"Стойла Виндермеера"}},
{x:17260,y:-32937,type:3, level:0, icon:"lm_continent.png", name:{en:"Fairhaven Stables",fr:"Etables de Fairhaven",de:"Fairhaven Ställe",ru:"Стойла Фэрхевена"}},
{x:17925,y:-31995,type:3, level:0, icon:"lm_continent.png", name:{en:"Crystabell Stables",fr:"Etables de Crystabell",de:"Crystabell Ställe",ru:"Стойла Кристабелла"}},
{x:18186,y:-31097,type:3, level:0, icon:"lm_continent.png", name:{en:"Avendale Stables",fr:"Etables d&#039;Avendale",de:"Avendale Ställe",ru:"Стойла Эвендейла"}},
{x:8732,y:-2987,type:3, level:0, icon:"lm_continent.png", name:{en:"Zora Stables",fr:"Etables de Zora",de:"Stall",ru:"Стойла Мектубов Зоры"}},
{x:9546,y:-3633,type:3, level:0, icon:"lm_continent.png", name:{en:"Hoï-Cho Stables",fr:"Etables d&#039;Hoi Cho",de:"Hoi-Cho-Ställe",ru:"Стойла Хоа Шо"}},
{x:8699,y:-3735,type:3, level:0, icon:"lm_continent.png", name:{en:"Jen-Laï Stables",fr:"Etables de Jen Lai",de:"Jen-Lai-Ställe",ru:"Стойла Джен Лай"}},
{x:9822,y:-4261,type:3, level:0, icon:"lm_continent.png", name:{en:"Min-Cho Stables",fr:"Etables de Min Cho",de:"Min-Cho-Ställe",ru:"Стойла Мин Хо"}},
{x:887,y:-10316,type:2, level:0, icon:"lm_continent.png", name:{en:"West Saprun Research Center",fr:"Centre de Recherche de la Montée de Sève Ouest",de:"Forschungszentrum der Westlichen Sapquelle",ru:"Исследовательский центр западного Сэпотока"}},
{x:1201,y:-10477,type:2, level:0, icon:"lm_continent.png", name:{en:"East Saprun Workshop",fr:"Atelier de la Montée de Sève Est",de:"Werkstatt der Östlichen Sapquelle",ru:"Мастерская восточного Сэпотока"}},
{x:702,y:-10800,type:2, level:0, icon:"lm_continent.png", name:{en:"Nexusgate Stronghold",fr:"Forteresse de la Porte du Nexus",de:"Festung der Nexuspforte",ru:"Форт Врата Нексуса"}},
{x:18313,y:-25333,type:2, level:0, icon:"lm_continent.png", name:{en:"Blackburn Trade Post",fr:"Poste d&#039;Echange de la Combustion",de:"Schwarzbrand-Handelsposten",ru:"Фактория Кострище"}},
{x:17672,y:-24559,type:2, level:0, icon:"lm_continent.png", name:{en:"Northlake Farm",fr:"Ferme du Lac du Nord",de:"Hof am Nördlichen See",ru:"Ферма у Северного Озера"}},
{x:17993,y:-24585,type:2, level:0, icon:"lm_continent.png", name:{en:"Northlake Trade Post",fr:"Poste d&#039;Echange du Lac du Nord",de:"Handelsposten am Nördlichen See",ru:"Фактория у Севеерного Озера"}},
{x:18007,y:-25043,type:2, level:0, icon:"lm_continent.png", name:{en:"Barkgully Workshop",fr:"Atelier du Couloir aux Ecorces",de:"Rindenspalt-Werkstatt",ru:"Мастерская Корковражье"}},
{x:17490,y:-25682,type:2, level:0, icon:"lm_continent.png", name:{en:"West Blackburn Border Post",fr:"Poste Frontière Ouest de la Combustion",de:"West-Schwarzbrand-Grenzposten",ru:"Пограничная застава Западное Кострище"}},
{x:16906,y:-24707,type:2, level:0, icon:"lm_continent.png", name:{en:"East Dyron Workshop",fr:"Atelier de Dyron Est",de:"Dyron-Ost-Werkstatt",ru:"Мастерская Восточный Дайрон"}},
{x:17186,y:-25388,type:2, level:0, icon:"lm_continent.png", name:{en:"Frahar Towers Border Post",fr:"Poste Frontière des Tours de Frahar",de:"Frahar-Türme-Grenzposten",ru:"Пограничная застава Башни Фрахар"}},
{x:17047,y:-25682,type:2, level:0, icon:"lm_continent.png", name:{en:"Hightowers Stronghold",fr:"Forteresse des Hautes Tours",de:"Hochtürme-Festung",ru:"Форт Высоких Башен"}},
{x:16720,y:-25860,type:2, level:0, icon:"lm_continent.png", name:{en:"Hightowers Farm",fr:"Ferme des Hautes Tours",de:"Hochtürme-Hof",ru:"Ферма Высоких Башен"}},
{x:20082,y:-24864,type:2, level:0, icon:"lm_continent.png", name:{en:"Barker Canyon Stronghold",fr:"Forteresse du Canyon de Barker",de:"Rindenschlucht-Festung",ru:"Форт Каньона Искусников "}},
{x:19607,y:-25221,type:2, level:0, icon:"lm_continent.png", name:{en:"Barker Canyon Farm",fr:"Ferme du Canyon de Barker",de:"Rindenschlucht-Hof",ru:"Ферма Каньона Искусников"}},
{x:19606,y:-25532,type:2, level:0, icon:"lm_continent.png", name:{en:"Highridges Diplomatic Outpost",fr:"Avant-Poste Diplomatique des Hauts Récifs",de:"Diplomatischer Außenposten des Hohen Riffs",ru:"Дипломатическая миссия Вышние Хребты"}},
{x:19273,y:-25851,type:2, level:0, icon:"lm_continent.png", name:{en:"Workshop of the Waste ",fr:"Atelier des Terres Désolées",de:"Werkstatt des Untergangs",ru:"Мастерская Пустоши"}},
{x:19748,y:-25997,type:2, level:0, icon:"lm_continent.png", name:{en:"Thesos Research Center",fr:"Centre de Recherche de Thesos",de:"Thesos-Forschungszentrum",ru:"Исследовательский центр Фессоса"}},
{x:16272,y:-24253,type:2, level:0, icon:"lm_continent.png", name:{en:"Dyron Hill Border Post",fr:"Poste Frontière de la Colline de Dyron",de:"Grenzposten der Dyron-Anhöhe",ru:"Пограничная Застава Холм Дайрона"}},
{x:16702,y:-24264,type:2, level:0, icon:"lm_continent.png", name:{en:"North Dyron Stronghold",fr:"Forteresse de Dyron Nord",de:"Norddyron-Festung",ru:"Форт Северный Дайрон"}},
{x:16092,y:-24713,type:2, level:0, icon:"lm_continent.png", name:{en:"Dragon&#039;s Tail Workshop",fr:"Atelier de la Queue du Dragon",de:"Drachenschwanz-Werkstatt",ru:"Мастерская Хвост Дракона"}},
{x:16374,y:-25334,type:2, level:0, icon:"lm_continent.png", name:{en:"Malmont Farm",fr:"Ferme de Malmontagne",de:"Malmont-Hof",ru:"Ферма Мальмонт"}},
{x:16382,y:-25812,type:2, level:0, icon:"lm_continent.png", name:{en:"Canyon Road Workshop",fr:"Atelier de la Route du Canyon",de:"Canyonweg-Werkstatt",ru:"Мастерская дороги Каньонов"}},
{x:18955,y:-26157,type:2, level:0, icon:"lm_continent.png", name:{en:"Thesos Springs Stronghold",fr:"Forteresse des Sources de Thesos",de:"Festung der Thesos-Quelle",ru:"Форт Родники Фессоса"}},
{x:20070,y:-26463,type:2, level:0, icon:"lm_continent.png", name:{en:"Running Ridge Workshop",fr:"Atelier de la Crête Eternelle",de:"Werkstatt am Langen Grat",ru:"Мастерская Бегущей Гряды"}},
{x:19104,y:-26637,type:2, level:0, icon:"lm_continent.png", name:{en:"South Range Diplomatic Outpost",fr:"Avant-Poste Diplomatique de la Chaîne Sud",de:"Diplomatischer Außenposten der Südlichen Hügelkette",ru:"Дипломатическая Миссия южного Предела"}},
{x:18312,y:-26805,type:2, level:0, icon:"lm_continent.png", name:{en:"Southend Dune Farm",fr:"Ferme des Dunes du Bas",de:"Hof der Entlegenen Dünen",ru:"Ферма Южные дюны"}},
{x:19933,y:-26823,type:2, level:0, icon:"lm_continent.png", name:{en:"Kipucka Plain Border Post",fr:"Poste Frontière de la Plaine des Kipuckas",de:"Grenzposten der Kipucka-Ebene",ru:"Пограничная Застава Равнин Кипакка"}},
{x:19134,y:-25195,type:2, level:0, icon:"lm_continent.png", name:{en:"Dragon&#039;s Spine Stronghold",fr:"Forteresse de la Colonne du Dragon",de:"Stachel-des-Drachen-Festung",ru:"Форт Драконий Хребет"}},
{x:18936,y:-25680,type:2, level:0, icon:"lm_continent.png", name:{en:"Dragon&#039;s Spine Workshop",fr:"Atelier de la Colonne du Dragon",de:"Stachel-des-Drachen-Werkstatt",ru:"Мастерская Драконий Хребет"}},
{x:18185,y:-25671,type:2, level:0, icon:"lm_continent.png", name:{en:"Woodburn Magic Pole",fr:"Pôle Magique des Bois Calcifiés",de:"Holzbrand - Magischer Ort",ru:"Очаг магии Пылающее древо"}},
{x:17848,y:-25828,type:2, level:0, icon:"lm_continent.png", name:{en:"Woodburn Stronghold",fr:"Forteresse des Bois Calcifiés",de:"Holzbrand-Festung",ru:"Форт У пылающего древа"}},
{x:4247,y:-715,type:2, level:0, icon:"lm_continent.png", name:{en:"Westgrove Stronghold",fr:"Forteresse du Bosquet Ouest",de:"Westwald-Festung",ru:"Форт Западной Рощи"}},
{x:4557,y:-556,type:2, level:0, icon:"lm_continent.png", name:{en:"Eastgrove Workshop",fr:"Atelier du Bosquet Est",de:"Ostwald-Werkstatt",ru:"Мастерская Восточной Рощи"}},
{x:5035,y:-884,type:2, level:0, icon:"lm_continent.png", name:{en:"Highgrove Research Center",fr:"Centre de Recherche du Bosquet Supérieur",de:"Hochwald-Forschungszentrum",ru:"Исследовательский центр Вышней Рощи"}},
{x:4880,y:-1200,type:2, level:0, icon:"lm_continent.png", name:{en:"Ginti Workshop",fr:"Atelier de Ginti",de:"Ginti-Werkstatt",ru:"Лаборатория Джинти"}},
{x:1690,y:-1832,type:2, level:0, icon:"lm_continent.png", name:{en:"Little Mountain Workshop",fr:"Atelier de la Petite Montagne",de:"Werkstatt des Kleinen Berges",ru:"Мастерская у Маленькой Горы"}},
{x:2151,y:-1525,type:2, level:0, icon:"lm_continent.png", name:{en:"Little Mountain Stronghold",fr:"Forteresse de la Petite Montagne",de:"Festung des Kleinen Berges",ru:"Форт у Маленькой Горы"}},
{x:1519,y:-1354,type:2, level:0, icon:"lm_continent.png", name:{en:"Berello Gorge Border Post",fr:"Poste Frontière de la Gorge de Berello",de:"Berello-Schlucht-Grenzposten",ru:"Пограничная застава Ущелье Берелло"}},
{x:1673,y:-892,type:2, level:0, icon:"lm_continent.png", name:{en:"Virginia Falls Diplomatic Outpost",fr:"Avant-Poste Diplomatique des Chutes de Virginia",de:"Diplomatischer Außenposten der Virginia-Fälle",ru:"Дипломатическая Миссия Водопад Виргиния"}},
{x:1364,y:-568,type:2, level:0, icon:"lm_continent.png", name:{en:"Virginia Falls Magic Pole",fr:"Pôle Magique des Chutes de Virginia",de:"Magischer Ort der Virginia-Fälle",ru:"Очаг Магии Водопад Виргиния"}},
{x:5357,y:-6481,type:2, level:0, icon:"lm_continent.png", name:{en:"Upper Hovel Diplomatic Outpost",fr:"Avant-Poste Diplomatique de la Masure Supérieure",de:"Diplomatischer Außenposten der Oberen Hütte",ru:"Дипломатическая Миссия верхнего Логова"}},
{x:5039,y:-6797,type:2, level:0, icon:"lm_continent.png", name:{en:"Folly Farm",fr:"Ferme de la Folie",de:"Hof der Ebene der Torheit",ru:"Ферма Недомыслия"}},
{x:5360,y:-7126,type:2, level:0, icon:"lm_continent.png", name:{en:"Greenpool Workshop",fr:"Atelier de la Mare Verte",de:"Werkstatt des Grünen Beckens",ru:"Мастерская Зеленый Пруд"}},
{x:5676,y:-7282,type:2, level:0, icon:"lm_continent.png", name:{en:"Chilling Swamp Research Center",fr:"Centre de Recherche du Marais Pétrifiant",de:"Forschungszentrum am Sumpf der Angst",ru:"Исследовательский Центр Знобкого Болота"}},
{x:5515,y:-7596,type:2, level:0, icon:"lm_continent.png", name:{en:"Maze of Sprite Border Post",fr:"Poste Frontière du Labyrinthe des Lutins",de:"Geisterlabyrinth-Grenzposten",ru:"Пограничная застава Лабиринта Спрайтов"}},
{x:3920,y:-6639,type:2, level:0, icon:"lm_continent.png", name:{en:"Panting Pass Research Center",fr:"Centre de Recherche du Col des Palpitations",de:"Forschungszentrum am Schwierigen Pfad",ru:"Исследовательский Центр Удушливого Перевала"}},
{x:3601,y:-6159,type:2, level:0, icon:"lm_continent.png", name:{en:"Stalia Stronghold",fr:"Forteresse de Stalia",de:"Stalia-Festung",ru:"Форт Сталия"}},
{x:4226,y:-5666,type:2, level:0, icon:"lm_continent.png", name:{en:"Fearing  Fen Farm",fr:"Ferme du Marécage de l&#039;Angoisse",de:"Hof am Gefürchteten Sumpf",ru:"Ферма Болото Страха"}},
{x:3929,y:-5504,type:2, level:0, icon:"lm_continent.png", name:{en:"Fencoomb Workshop",fr:"Atelier de Fencoomb",de:"Moorteiche-Werkstatt",ru:"Мастерская у Волглого Лога"}},
{x:2946,y:-2313,type:2, level:0, icon:"lm_continent.png", name:{en:"Runninghill Border Post",fr:"Poste Frontière du Col Vallonné",de:"Gebirgspass-Grenzposten",ru:"Застава Бегущего Холма"}},
{x:2339,y:-2498,type:2, level:0, icon:"lm_continent.png", name:{en:"Wooky Workshop",fr:"Atelier du Réveil",de:"Wooky-Werkstatt",ru:"Мастерская Вуки"}},
{x:2008,y:-2012,type:2, level:0, icon:"lm_continent.png", name:{en:"Dryland Gate Stronghold",fr:"Forteresse de la Porte des Terres Arides",de:"Festung der Ödlandpforte",ru:"Форт Врата Суши"}},
{x:2628,y:-2014,type:2, level:0, icon:"lm_continent.png", name:{en:"Runninghill Research Center",fr:"Centre de Recherche du Col Vallonné",de:"Gebirgspass-Forschungszentrum",ru:"Исследовательский центр Бегущего Холма"}},
{x:2488,y:-1506,type:2, level:0, icon:"lm_continent.png", name:{en:"Ryecliff Stronghold",fr:"Forteresse de la Falaise de Rye",de:"Festung der Klippe von Rye",ru:"Форт Ржаного Мыса"}},
{x:2976,y:-1514,type:2, level:0, icon:"lm_continent.png", name:{en:"Torbak Hill Farm",fr:"Ferme de la Colline des Torbaks",de:"Hof der Anhöhe der Torbaks",ru:"Фирма Холма Торбаков"}},
{x:3587,y:-1694,type:2, level:0, icon:"lm_continent.png", name:{en:"Smitten Mount Workshop",fr:"Atelier de la Montagne du Tourment",de:"Werkstatt am Berg der Pein",ru:"Мастерская Обрушенной горы"}},
{x:3611,y:-2011,type:2, level:0, icon:"lm_continent.png", name:{en:"Smitten Mount Farm",fr:"Ferme de la Montagne du Tourment",de:"Hof am Berg der Pein",ru:"Ферма Обрушенной Горы"}},
{x:3292,y:-2790,type:2, level:0, icon:"lm_continent.png", name:{en:"Psykopla Knoll Trading Post",fr:"Poste d&#039;Echange du Monticule des Psykoplas",de:"Handelsposten am Psykopla-Hügel",ru:"Фактория Холмы Псикоплы "}},
{x:4254,y:-5032,type:2, level:0, icon:"lm_continent.png", name:{en:"The Rising Border Post",fr:"Poste Frontière de la Poussée",de:"Grenzposten der Erhebung",ru:"Пограничная застава На Возвышенности"}},
{x:4710,y:-5189,type:2, level:0, icon:"lm_continent.png", name:{en:"Southeast Trade Post",fr:"Poste d&#039;Echange du Sud-Est",de:"Südöstlicher Handelsposten",ru:"Фактория Юго-Восточная"}},
{x:5029,y:-5533,type:2, level:0, icon:"lm_continent.png", name:{en:"Finder&#039;s Farm",fr:"Ferme de l&#039;Inventeur",de:"Hof des Entdeckers",ru:"Ферма Первооткрывателей"}},
{x:5223,y:-5850,type:2, level:0, icon:"lm_continent.png", name:{en:"Trove Workshop",fr:"Atelier du Trésor",de:"Werkstatt des Horts",ru:"Мастерская Сокровищницы"}},
{x:5831,y:-6146,type:2, level:0, icon:"lm_continent.png", name:{en:"Kami Circle Magic Pole",fr:"Pôle Magique du Cercle Kamic",de:"Magischer Ort des Kami-Kreises",ru:"Магический Очаг круга Ками"}},
{x:9213,y:-6464,type:2, level:0, icon:"lm_continent.png", name:{en:"Ichorgate Research Center",fr:"Centre de Recherche de la Porte d&#039;Ichor",de:"Tor von Ichor - Forschungszentrum",ru:"Исследовательский центр Врата Ихора"}},
{x:8550,y:-6815,type:2, level:0, icon:"lm_continent.png", name:{en:"Hammer Hills Trade Post",fr:"Poste d&#039;Echange des Collines Martelées",de:"Hammerhöhen-Handelsposten",ru:"Фактория Холмы-Молоты"}},
{x:8250,y:-7113,type:2, level:0, icon:"lm_continent.png", name:{en:"Umbragate Farm",fr:"Ferme de la Porte des Ombres",de:"Tor nach Umbra - Hof",ru:"Ферма Врата Тени"}},
{x:9529,y:-7128,type:2, level:0, icon:"lm_continent.png", name:{en:"Destiny&#039;s End Diplomatic Outpost",fr:"Avant-Poste Diplomatique de la Fin de la Destinée",de:"Schicksalsende - Diplomatischer Außenposten",ru:"Дипломатическая миссия Роковой предел"}},
{x:9034,y:-7293,type:2, level:0, icon:"lm_continent.png", name:{en:"Watchcomb Workshop",fr:"Atelier de Gardecombe",de:"Talwacht-Werkstatt",ru:"Мастерская Гребень Наблюдателей"}},
{x:8541,y:-7426,type:2, level:0, icon:"lm_continent.png", name:{en:"Highwatch Farm",fr:"Ferme de la Crête Surveillée",de:"Hohe Wacht - Hof",ru:"Ферма Высшее Наблюдение"}},
{x:8867,y:-7773,type:2, level:0, icon:"lm_continent.png", name:{en:"Wastegate Border Post",fr:"Poste Frontière de la Porte des Terres Désolées",de:"Tor ins Niemandsland – Grenzposten",ru:"Пограничная застава Врата Пустоши"}},
{x:5998,y:-9845,type:2, level:0, icon:"lm_continent.png", name:{en:"Ruins of Leron Workshop",fr:"Atelier des Ruines de Léron",de:"Werkstatt der Ruinen von Leron",ru:"Мастерская Руин Лернона"}},
{x:6006,y:-10799,type:2, level:0, icon:"lm_continent.png", name:{en:"Halls of Darkring Research Center",fr:"Centre de Recherche des Salles de la Bague Noire",de:"Forschungszentrum der Hallen des Schwarzen Rings",ru:"Исследовательский центр Залы темного кольца "}},
{x:6171,y:-11600,type:2, level:0, icon:"lm_continent.png", name:{en:"The Fallen Tree Workshop",fr:"Atelier de l&#039;Arbre Abattu",de:"Gefallener-Baum-Werkstatt",ru:"Мастерская Падшего Древа"}},
{x:5838,y:-12232,type:2, level:0, icon:"lm_continent.png", name:{en:"Land of Plenty Research Center",fr:"Centre de Recherche de la Terre d&#039;Abondance",de:"Land des Überflusses - Forschungszentrum",ru:"Исследовательский центр Земли изобилия"}},
{x:6804,y:-12562,type:2, level:0, icon:"lm_continent.png", name:{en:"Underwood Woe Stronghold",fr:"Forteresse des Sous-Bois du Malheur",de:"Festung am Unterholz des Unglücks",ru:"Форт Горе Подлесное"}},
{x:6021,y:-13063,type:2, level:0, icon:"lm_continent.png", name:{en:"Three Totems Pass Workshop",fr:"Atelier du Col des Trois Totems",de:"Dreitotempass-Werkstatt",ru:"Мастерская Проход трех Тотемов"}},
{x:6482,y:-13679,type:2, level:0, icon:"lm_continent.png", name:{en:"Alleyways Bluff Stronghold",fr:"Forteresse de la Ruelle",de:"Festung des Klippendurchgangs",ru:"Форт Утеса трех переулков"}},
{x:6326,y:-13999,type:2, level:0, icon:"lm_continent.png", name:{en:"High Henge Research Center",fr:"Centre de Recherche du Cromlech Supérieur",de:"Forschungszentrum des Oberen Steinkreises",ru:"Исследовательский центр Высокие Мезолиты "}},
{x:6635,y:-14654,type:2, level:0, icon:"lm_continent.png", name:{en:"Lowhills Stronghold",fr:"Forteresse des Basses Collines",de:"Festung der Kleinen Hügel",ru:"Форт Малохолмья"}},
{x:6318,y:-15289,type:2, level:0, icon:"lm_continent.png", name:{en:"Lowhills Research Center",fr:"Centre de Recherche des Basses Collines",de:"Forschungszentrum der Kleinen Hügel",ru:"Исследовательский центр Малохолмья"}},
{x:6138,y:-16217,type:2, level:0, icon:"lm_continent.png", name:{en:"Eerie Valley Magic Pole",fr:"Pôle Magique de la Vallée Sinistre",de:"Schauriges Tal - Magischer Ort",ru:"Очаг Магии Долина Ужаса"}},
{x:6799,y:-16398,type:2, level:0, icon:"lm_continent.png", name:{en:"Aedengate Workshop",fr:"Atelier de la Porte d&#039;Aeden",de:"Aedenpforte-Werkstatt",ru:"Мастерская Врата Аэдан"}},
{x:5678,y:-16719,type:2, level:0, icon:"lm_continent.png", name:{en:"Yelkgrove Border Post",fr:"Poste Frontière du Bosquet des Yelks",de:"Yelkwäldchen-Grenzposten",ru:"Пограничная застава Роща Йелков"}},
{x:3122,y:-10160,type:2, level:0, icon:"lm_continent.png", name:{en:"Deadtree Stronghold",fr:"Forteresse de l&#039;Arbre Mort",de:"Festung des Toten Baumes",ru:"Форт Мертводрева"}},
{x:3459,y:-10477,type:2, level:0, icon:"lm_continent.png", name:{en:"Sap Marsh Trade Post",fr:"Poste d&#039;Echange du Marécage de la Sève",de:"Sapsumpf-Handelsposten",ru:"Фактория Болота Сэп"}},
{x:2958,y:-10799,type:2, level:0, icon:"lm_continent.png", name:{en:"Shooting Meadow Border Post",fr:"Poste Frontière du Pré Lancinant",de:"Grenzposten der Stechenden Wiese",ru:"Пограничная застава Луг Застрельщиков"}},
{x:551,y:-13516,type:2, level:0, icon:"lm_continent.png", name:{en:"Grotto Walk Research Center",fr:"Centre de Recherche de la Promenade des Grottes",de:"Grottenweg-Forschungszentrum",ru:"Исследовательский центр Тропа Грота"}},
{x:1345,y:-13836,type:2, level:0, icon:"lm_continent.png", name:{en:"Cursed Cavern Stronghold",fr:"Forteresse de la Caverne Maudite",de:"Festung der Verfluchten Höhle",ru:"Форт Проклятой Пещеры"}},
{x:876,y:-13999,type:2, level:0, icon:"lm_continent.png", name:{en:"Great Mushroom Wood Workshop",fr:"Atelier des Bois du Champignon Géant",de:"Werkstatt im Großen Pilzwald",ru:"Мастерская Великого Грибного Леса"}},
{x:1039,y:-14473,type:2, level:0, icon:"lm_continent.png", name:{en:"Wings of Spite Farm",fr:"Ferme des Ailes du Dépit",de:"Hof des Flügels der Feindschaft",ru:"Ферма Крылья Досады"}},
{x:1041,y:-14801,type:2, level:0, icon:"lm_continent.png", name:{en:"Continuity Corner Diplomatic Outpost",fr:"Avant-Poste Diplomatique du Tournant de la Continuité",de:"Diplomatischer Außenposten in der Wegkehre der Kontinuität",ru:"Дипломатическая миссия Точка непрерывности"}},
{x:1840,y:-13520,type:2, level:0, icon:"lm_continent.png", name:{en:"Saplake Major Stronghold",fr:"Forteresse du Lac Majeure de Sève",de:"Festung des Großen Sapsees",ru:"Форт Большое Сэпозеро"}},
{x:2316,y:-13680,type:2, level:0, icon:"lm_continent.png", name:{en:"Stockdell  Workshop",fr:"Atelier du Valon des Souches",de:"Werkstatt im Freundlichen Tal",ru:"Мастерская Стокделл"}},
{x:2656,y:-14323,type:2, level:0, icon:"lm_continent.png", name:{en:"Haunted Gorge Farm",fr:"Ferme de la Gorge Hantée",de:"Hof an der Schlucht des Spuks",ru:"Мастерская ущелья Призраков"}},
{x:2326,y:-14801,type:2, level:0, icon:"lm_continent.png", name:{en:"Dragon Horns Workshop",fr:"Atelier des Cornes du Dragon",de:"Drachenhorn-Werkstatt",ru:"Мастерская драконова Рога"}},
{x:1545,y:-15099,type:2, level:0, icon:"lm_continent.png", name:{en:"Ruins of Cryton Farm",fr:"Ferme des Ruines de Cryton",de:"Hof der Ruinen von Cryton",ru:"Ферма Руины Крайтона"}},
{x:2633,y:-15285,type:2, level:0, icon:"lm_continent.png", name:{en:"Hall of the Mountain Bane Stronghold",fr:"Forteresse de la Salle de la Montagne Empoisonnée",de:"Festung des vergifteten Berges",ru:"Форт залов горного Бедствия"}},
{x:18967,y:-29845,type:2, level:0, icon:"lm_continent.png", name:{en:"Watergate Border Post",fr:"Poste Frontière de la Porte des Eaux",de:"Wasserpforte-Grenzposten",ru:"Пограничная застава Врата Вод"}},
{x:17519,y:-29999,type:2, level:0, icon:"lm_continent.png", name:{en:"Loria Stronghold",fr:"Forteresse de Loria",de:"Lorias Festung",ru:"Форт Лории"}},
{x:19601,y:-30320,type:2, level:0, icon:"lm_continent.png", name:{en:"Loria Ponds Research Center",fr:"Centre de Recherche des Etangs de Loria",de:"Lorias Teiche - Forschungszentrum",ru:"Исследовательский центр Пруды Лории "}},
{x:19112,y:-30641,type:2, level:0, icon:"lm_continent.png", name:{en:"Whirling Stronghold",fr:"Forteresse du Tourbillon",de:"Wirbelwind-Festung",ru:"Форт Ветряки"}},
{x:19281,y:-31438,type:2, level:0, icon:"lm_continent.png", name:{en:"Gusty Gorge Workshop",fr:"Atelier de la Gorge des Vents",de:"Werkstatt der Stürmischen Schlucht",ru:"Мастерская Теснины Ветров"}},
{x:16572,y:-30004,type:2, level:0, icon:"lm_continent.png", name:{en:"Loriagate Border Post",fr:"Poste d&#039;Echange de Port-Loria",de:"Grenzposten des Tors von Loria",ru:"Пограничная застава Врата Лории"}},
{x:15764,y:-30321,type:2, level:0, icon:"lm_continent.png", name:{en:"Windyway Workshop",fr:"Atelier de la Route des Vents",de:"Weg der Winde - Werkstatt",ru:"Мастерская Дороги Ветров"}},
{x:15104,y:-30642,type:2, level:0, icon:"lm_continent.png", name:{en:"Muse Watch Lake Farm",fr:"Ferme du Lac des Veilleurs de Songe",de:"Hof am See der Traumwächter",ru:"Ферма Стражи озера Муз"}},
{x:16410,y:-30643,type:2, level:0, icon:"lm_continent.png", name:{en:"Threepillars Farm",fr:"Ferme de la Tricolonne",de:"Drei-Säulen-Hof",ru:"Ферма Три Опоры"}},
{x:15601,y:-31121,type:2, level:0, icon:"lm_continent.png", name:{en:"Musemere Workshop",fr:"Atelier de l&#039;Etang de la Contemplation",de:"Werkstatt der Verträumten Seen",ru:"Мастерская Озеро Муз"}},
{x:14483,y:-31760,type:2, level:0, icon:"lm_continent.png", name:{en:"Cute Springs Research Center",fr:"Centre de Recherche des Sources des Cutes",de:"Cute-Quellsee-Forschungszentrum",ru:"Исследовательский Центр Ключи Кьют"}},
{x:14961,y:-31762,type:2, level:0, icon:"lm_continent.png", name:{en:"Liberty Gateway Border Post",fr:"Poste Frontière de la Porte de la Liberté",de:"Grenzposten des Tors zur Freiheit",ru:"Пограничная застава Врата Вольности"}},
{x:14322,y:-32240,type:2, level:0, icon:"lm_continent.png", name:{en:"Cute Springs Twining Stronghold",fr:"Forteresse des Sources Agitées des Cutes",de:"Festung der Sprudelnden Cute-Quellen",ru:"Форт плетения Кьют"}},
{x:14148,y:-32720,type:2, level:0, icon:"lm_continent.png", name:{en:"Tupile Trade Post",fr:"Poste d&#039;Echange de Tupile",de:"Tupile-Handelsposten",ru:"Фактория Тьюпайл"}},
{x:14961,y:-32719,type:2, level:0, icon:"lm_continent.png", name:{en:"Twintops Workshop",fr:"Atelier des Cimes Jumelles",de:"Zwillingsgipfel-Werkstatt",ru:"Мастерская Пики Близнецы"}},
{x:14799,y:-33680,type:2, level:0, icon:"lm_continent.png", name:{en:"Fount Lake Workshop",fr:"Atelier du Lac des Sources",de:"Quellsee-Werkstatt",ru:"Мастерская Озеро Родника"}},
{x:14626,y:-34159,type:2, level:0, icon:"lm_continent.png", name:{en:"Bountygate Border Post",fr:"Poste Frontière de la Porte de la Générosité",de:"Grenzposten am Tor des Überflusses",ru:"Пограничная застава Врата Изобилия"}},
{x:15280,y:-34157,type:2, level:0, icon:"lm_continent.png", name:{en:"Fount Porch Trade Post",fr:"Poste d&#039;Echange du Porche des Sources",de:"Quellhalle-Handelsposten",ru:"Фактория Террасы Родника"}},
{x:14648,y:-34644,type:2, level:0, icon:"lm_continent.png", name:{en:"Corrie Finley Farm",fr:"Ferme de Corrie Finley",de:"Corrie-Finley-Hof",ru:"Ферма Корри Финли"}},
{x:15770,y:-34641,type:2, level:0, icon:"lm_continent.png", name:{en:"Ponders End Workshop",fr:"Atelier des Colonnes de la Méditation",de:"Werkstatt der Säulen der Besinnung",ru:"Мастерская Предел раздумий"}},
{x:18322,y:-33679,type:2, level:0, icon:"lm_continent.png", name:{en:"Tippy Peaks Diplomatic Outpost",fr:"Avant-Poste Diplomatique des Pics Instables",de:"Diplomatischer Außenposten der Brüchigen Gipfel",ru:"Дипломатическая Миссия Острые Пики"}},
{x:17506,y:-34002,type:2, level:0, icon:"lm_continent.png", name:{en:"Sinking Sands Workshop",fr:"Atelier des Sables de la Perdition",de:"Werkstatt des Sinkenden Sandes",ru:"Мастерская Зыбучие пески"}},
{x:18172,y:-34000,type:2, level:0, icon:"lm_continent.png", name:{en:"Eastshore Magic Pole",fr:"Pôle Magique de la Rivest",de:"Magischer Ort der Ostküste",ru:"Очаг Магии Истхорн"}},
{x:17840,y:-34319,type:2, level:0, icon:"lm_continent.png", name:{en:"Graveyard Gate Research Center",fr:"Centre de Recherche de la Promenade Caverneuse",de:"Friedhofspforte-Forschungszentrum",ru:"Исследовательский центр У кладбищенских врат "}},
{x:17361,y:-34476,type:2, level:0, icon:"lm_continent.png", name:{en:"Mire of the Dire Stronghold",fr:"Forteresse du Bourbier de la Misère",de:"Festung des Grässlichen Morasts",ru:"Форт Зловещая Трясина"}},
{x:15911,y:-32245,type:2, level:0, icon:"lm_continent.png", name:{en:"Darkmoor Border Post",fr:"Poste Frontière des Landes Obscures",de:"Schwarzmoor-Grenzposten",ru:"Пограничная застава Мрачные Топи"}},
{x:17993,y:-32407,type:2, level:0, icon:"lm_continent.png", name:{en:"Dainty Isle Workshop",fr:"Atelier de l&#039;Ile de la Fragilité",de:"Werkstatt der Anmutigen Insel",ru:"Мастерская Остров Изящества"}},
{x:16415,y:-32574,type:2, level:0, icon:"lm_continent.png", name:{en:"Sandy Workshop",fr:"Atelier des Sables",de:"Sandy-Werkstatt",ru:"Песчаная Мастерская"}},
{x:17678,y:-32712,type:2, level:0, icon:"lm_continent.png", name:{en:"Farely Farm",fr:"Ferme de Farely",de:"Farely-Hof",ru:"Ферма Фэйрлей"}},
{x:16728,y:-32860,type:2, level:0, icon:"lm_continent.png", name:{en:"Greenvale Trade Post",fr:"Poste d&#039;Echange de Vertval",de:"Grünes Tal - Handelsposten",ru:"Фактория Зеленая Долина"}},
{x:15916,y:-33192,type:2, level:0, icon:"lm_continent.png", name:{en:"Windermeer Farm",fr:"Ferme de Windermeer",de:"Windermeer-Hof",ru:"Ферма Виндермеера"}},
{x:11933,y:-5191,type:2, level:0, icon:"lm_continent.png", name:{en:"Withered Stock Magic Pole",fr:"Pôle Magique de la Souche Flétrie",de:"Magischer Ort des Verdorrten Stammes",ru:"Очаг Магии Заброшенный Кладезь"}},
{x:11267,y:-4868,type:2, level:0, icon:"lm_continent.png", name:{en:"Gu-Qin Workshop",fr:"Atelier de Gu-Qin",de:"Gu-Qin-Werkstatt",ru:"Мастерская Гу-Кина ."}},
{x:9996,y:-4878,type:2, level:0, icon:"lm_continent.png", name:{en:"Doomed Maze Farm",fr:"Ferme du Labyrinthe Condamné",de:"Hof des Verdammten Labyrinths",ru:"Ферма Обреченного Лабиринта"}},
{x:10627,y:-4714,type:2, level:0, icon:"lm_continent.png", name:{en:"Underway Arch Stronghold",fr:"Forteresse de l&#039;Arc Caché",de:"Wölbung-Festung",ru:"Форт свода Подземелий"}},
{x:11776,y:-4544,type:2, level:0, icon:"lm_continent.png", name:{en:"Gu-Qin Research Center",fr:"Centre de Recherche de Gu-Qin",de:"Gu-Qin-Forschungszentrum",ru:"Исследовательский Центр Гу-Кина"}},
{x:11112,y:-4230,type:2, level:0, icon:"lm_continent.png", name:{en:"Umbra Highback  Workshop",fr:"Atelier du Dôme des Ombres",de:"Schattenkuppel-Werkstatt",ru:"Мастерская Теневой гряды"}},
{x:10639,y:-4243,type:2, level:0, icon:"lm_continent.png", name:{en:"Sly Lake Crossroads Border Post",fr:"Poste Frontière du Lac Dissimulé",de:"Grenzposten am Verborgenen See",ru:"Пограничная застава перекрестка у Озера Коварства"}},
{x:10317,y:-3759,type:2, level:0, icon:"lm_continent.png", name:{en:"Qai-Du Workshop",fr:"Atelier de Qai-Du",de:"Qai-Du-Werkstatt",ru:"Мастерская Кай-Ду"}},
{x:11761,y:-3921,type:2, level:0, icon:"lm_continent.png", name:{en:"Land&#039;s End Farm",fr:"Ferme du Bout des Terres",de:"Land-Enden-Hof",ru:"Ферма на краю Света"}},
{x:11282,y:-3595,type:2, level:0, icon:"lm_continent.png", name:{en:"Sai-Shun Stronghold",fr:"Forteresse de Sai-Shun",de:"Sai-Shun-Festung",ru:"Форт Саи-Шуна"}},
{x:10789,y:-3452,type:2, level:0, icon:"lm_continent.png", name:{en:"Maiden Pond Workshop",fr:"Atelier de l&#039;Etang Vierge",de:"Jungfernteich-Werkstatt",ru:"Мастерская у Девственного Пруда"}},
{x:10156,y:-3280,type:2, level:0, icon:"lm_continent.png", name:{en:"Bighand Border Post",fr:"Poste Frontière de la Main Géante",de:"Grenzposten der Großen Hand",ru:"Пограничная Застава Тяжелая Рука"}},
{x:11922,y:-3117,type:2, level:0, icon:"lm_continent.png", name:{en:"Great Boulder Workshop",fr:"Atelier du Grand Tunnel Rocailleux",de:"Werkstatt am Großen Brocken",ru:"Мастерская Великой Глыбы"}},
{x:11429,y:-3109,type:2, level:0, icon:"lm_continent.png", name:{en:"Great Boulder Trade Post",fr:"Poste d&#039;Echange du Grand Tunnel Rocailleux",de:"Handelsposten am Großen Brocken",ru:"Фактория Великой Глыбы"}},
{x:11261,y:-2633,type:2, level:0, icon:"lm_continent.png", name:{en:"Zo-Kian Ruins Workshop",fr:"Atelier des Ruines de Zo-Kian",de:"Zo-Kian Ruinen - Werkstatt",ru:"Мастерская Руины Зо-Киан"}},
{x:10632,y:-2650,type:2, level:0, icon:"lm_continent.png", name:{en:"Lost Valley Stronghold",fr:"Forteresse de la Vallée Perdue",de:"Vergessenes Tal - Festung",ru:"Форт Затерянной Долины"}},
{x:11296,y:-2175,type:2, level:0, icon:"lm_continent.png", name:{en:"Paramount Stock Workshop",fr:"Atelier de la Souche Capitale",de:"Aussicht-Werkstatt",ru:"Мастерская Верховный Кладезь"}},
{x:10789,y:-2174,type:2, level:0, icon:"lm_continent.png", name:{en:"Staring Stronghold",fr:"Forteresse de la Contemplation",de:"Festung der Kontemplation",ru:"Форт Бдительность"}},
{x:11767,y:-1847,type:2, level:0, icon:"lm_continent.png", name:{en:"Witherstock Farm",fr:"Ferme de la Souche Flétrie",de:"Hof des Verdorrten Stamms",ru:"Ферма у Заброшенного Кладезя"}},
{x:9688,y:-2948,type:2, level:0, icon:"lm_continent.png", name:{en:"Kincher Arch Border Post",fr:"Poste Frontière de l&#039;Arc des Kinchers",de:"Kincherbogen-Grenzposten",ru:"Пограничная застава арки Кинчеров"}},
{x:9205,y:-2635,type:2, level:0, icon:"lm_continent.png", name:{en:"Fustylake Workshop",fr:"Atelier du Lac Poussiéreux",de:"Werkstatt am Stickenden See",ru:"Мастерская Прогорклого Озера"}},
{x:9838,y:-2479,type:2, level:0, icon:"lm_continent.png", name:{en:"Demon&#039;s Crossroads Diplomatic Outpost",fr:"Avant-Poste Diplomatique du Croisement du Démon",de:"Diplomatischer Außenposten der Dämonenkreuzung",ru:"Дипломатическая миссия Перекресток Демона "}},
{x:9346,y:-2146,type:2, level:0, icon:"lm_continent.png", name:{en:"Bighorn Mountain Farm",fr:"Ferme de la Montagne aux Cornes",de:"Hornberg-Hof",ru:"Ферма у горы Бигхорн"}},
{x:8242,y:-3597,type:2, level:0, icon:"lm_continent.png", name:{en:"Jen-Laï Research Center",fr:"Centre de Recherche de Jen-Laï",de:"Jen-Laï-Forschungszentrum",ru:"Исследовательский центр Джен-Лаи"}},
{x:7907,y:-2953,type:2, level:0, icon:"lm_continent.png", name:{en:"Lonview Stronghold",fr:"Forteresse de l&#039;Horizon",de:"Horizont-Festung",ru:"Форт Одинокого Наблюдателя"}},
{x:7585,y:-2491,type:2, level:0, icon:"lm_continent.png", name:{en:"Stainmoor Stronghold",fr:"Forteresse de la Lande Tachetée",de:"Fleckenmoor-Festung",ru:"Форт Стэйнмура"}},
{x:8232,y:-2309,type:2, level:0, icon:"lm_continent.png", name:{en:"Stainmoor Border Post",fr:"Poste Frontière de la Lande Tachetée",de:"Fleckenmoor-Grenzposten",ru:"Пограничная застава Стэйнмура"}},
{x:7593,y:-1988,type:2, level:0, icon:"lm_continent.png", name:{en:"Desertstock Farm",fr:"Ferme de Désertstock",de:"Hof des Trockenen Stamms",ru:"Ферма Пустынный Кладезь"}},
{x:7109,y:-1994,type:2, level:0, icon:"lm_continent.png", name:{en:"Great Outback Workshop",fr:"Atelier de l&#039;Arrière-Pays",de:"Werkstatt des Großen Buschlandes",ru:"Мастерская Великая Целина "}},
{x:8575,y:-1670,type:2, level:0, icon:"lm_continent.png", name:{en:"Ni-Jing Farm",fr:"Ferme de Ni-Jing",de:"Ni-Jing-Hof",ru:"Ферма Ни-Джинг"}},
{x:16623,y:-24625,type:1, level:0, icon:"", name:{en:"Dyron",fr:"Dyron",de:"Dyron",ru:"Дайрон"}},
{x:19658,y:-26252,type:1, level:0, icon:"", name:{en:"Thesos",fr:"Thesos",de:"Thesos",ru:"Фессос"}},
{x:21207,y:-25527,type:1, level:0, icon:"", name:{en:"Aegus",fr:"Aegus",de:"Aegus",ru:"Аэгус"}},
{x:21983,y:-26169,type:1, level:0, icon:"", name:{en:"Kaemon",fr:"Kaemon",de:"Kaemon",ru:"Каэмон"}},
{x:22478,y:-25525,type:1, level:0, icon:"", name:{en:"Sekovix",fr:"Sekovix",de:"Sekovix",ru:"Сековикс"}},
{x:21515,y:-26643,type:1, level:0, icon:"", name:{en:"Phyxon",fr:"Phyxon",de:"Phyxon",ru:"Фиксон"}},
{x:22797,y:-26643,type:1, level:0, icon:"", name:{en:"Galemus",fr:"Galemus",de:"Galemus",ru:"Галэмус"}},
{x:3729,y:-3758,type:1, level:0, icon:"", name:{en:"Natae",fr:"Natae",de:"Natae",ru:"Нэтей"}},
{x:4231,y:-4110,type:1, level:0, icon:"", name:{en:"Davae",fr:"Davae",de:"Davae",ru:"Дэвей"}},
{x:4887,y:-4413,type:1, level:0, icon:"", name:{en:"Avalae",fr:"Avalae",de:"Avalae",ru:"Эвелей"}},
{x:862,y:-6310,type:1, level:0, icon:"", name:{en:"Stalli",fr:"Stalli",de:"Stalli",ru:"Сталли"}},
{x:1526,y:-6640,type:1, level:0, icon:"", name:{en:"Borea",fr:"Borea",de:"Borea",ru:"Борея"}},
{x:2169,y:-6628,type:1, level:0, icon:"", name:{en:"Nistia",fr:"Nistia",de:"Nistia",ru:"Нистия"}},
{x:1025,y:-6943,type:1, level:0, icon:"", name:{en:"Rosilio",fr:"Rosilio",de:"Rosilio",ru:"Росилио"}},
{x:1818,y:-7115,type:1, level:0, icon:"", name:{en:"Miani",fr:"Miani",de:"Miani",ru:"Миани"}},
{x:18095,y:-31032,type:1, level:0, icon:"", name:{en:"Avendale",fr:"Avendale",de:"Avendale",ru:"Эвендейл"}},
{x:17886,y:-31885,type:1, level:0, icon:"", name:{en:"Crystabell",fr:"Crystabell",de:"Crystabell",ru:"Кристабелл"}},
{x:15566,y:-32967,type:1, level:0, icon:"", name:{en:"Windermeer",fr:"Windermeer",de:"Windermeer",ru:"Виндермеер"}},
{x:21197,y:-33845,type:1, level:0, icon:"", name:{en:"Aubermouth",fr:"Aubermouth",de:"Aubermouth",ru:"Обермаут"}},
{x:22147,y:-33837,type:1, level:0, icon:"", name:{en:"Barkdell",fr:"Barkdell",de:"Barkdell",ru:"Баркделл"}},
{x:22805,y:-33511,type:1, level:0, icon:"", name:{en:"Hobwelly",fr:"Hobwelly",de:"Hobwelly",ru:"Хобвэлли"}},
{x:21354,y:-34632,type:1, level:0, icon:"", name:{en:"Waverton",fr:"Waverton",de:"Waverton",ru:"Уэвертон"}},
{x:22632,y:-34638,type:1, level:0, icon:"", name:{en:"Dingleton",fr:"Dingleton",de:"Dingleton",ru:"Динглтон"}},
{x:9622,y:-3509,type:1, level:0, icon:"", name:{en:"Hoï-Cho",fr:"Hoï-Cho",de:"Hoï-Cho",ru:"Хоа-Шо"}},
{x:8775,y:-3688,type:1, level:0, icon:"", name:{en:"Jen-Laï",fr:"Jen-Laï",de:"Jen-Laï",ru:"Джен-Лаи"}},
{x:9941,y:-4156,type:1, level:0, icon:"", name:{en:"Min-Cho",fr:"Min-Cho",de:"Min-Cho",ru:"Мин-Хо"}},
{x:7284,y:-4395,type:1, level:0, icon:"", name:{en:"Qaï Lo",fr:"Qaï Lo",de:"Qaï Lo",ru:"Квай-Ло"}},
{x:7760,y:-4878,type:1, level:0, icon:"", name:{en:"Sheng Wo",fr:"Sheng Wo",de:"Sheng Wo",ru:"Шенг-Уо"}},
{x:8398,y:-4586,type:1, level:0, icon:"", name:{en:"Nen Xing",fr:"Nen Xing",de:"Nen Xing",ru:"Нен-Ксинг"}},
{x:7442,y:-5359,type:1, level:0, icon:"", name:{en:"Koï Zun",fr:"Koï Zun",de:"Koï Zun",ru:"Кои-Зан"}},
{x:8400,y:-5200,type:1, level:0, icon:"", name:{en:"Yin Piang",fr:"Yin Piang",de:"Yin Piang",ru:"Йин-Панг"}},
{x:1042,y:-10531,type:-1, level:0, icon:"", name:{en:"Abyss of Ichor",fr:"Gouffre d&#039;Ichor",de:"Abgrund von Ichor",ru:"Кровоточащая бездна"}},
{x:18080,y:-25441,type:-1, level:0, icon:"", name:{en:"Burning Desert",fr:"Désert Ardent",de:"Brennende Wüste",ru:"Пылающая Пустыня"}},
{x:18693,y:-24467,type:0, level:0, icon:"", name:{en:"Pyr",fr:"Pyr",de:"Pyr",ru:"Пайр"}},
{x:23523,y:-24385,type:-1, level:0, icon:"", name:{en:"Old Lands",fr:"Terres Anciennes",de:"Alte Lande",ru:"Старые земли"}},
{x:22090,y:-26159,type:-1, level:0, icon:"", name:{en:"Burning Desert",fr:"Désert Ardent",de:"Brennende Wüste",ru:"Пылающая Пустыня"}},
{x:20639,y:-480,type:-1, level:0, icon:"", name:{en:"a building",fr:"bâtiment",de:"ein Gebäude",ru:"здание"}},
{x:2350,y:-16871,type:-1, level:0, icon:"", name:{en:"Kitins&#039; Lair",fr:"Kitinière",de:"Kitin-Nest",ru:"Логово китинов"}},
{x:3242,y:-3880,type:-1, level:0, icon:"", name:{en:"Verdant Heights",fr:"Sommet Verdoyant",de:"Grüne Anhöhen",ru:"Цветущие выси"}},
{x:4712,y:-3443,type:0, level:0, icon:"", name:{en:"Yrkanis",fr:"Yrkanis",de:"Yrkanis",ru:"Ирканис"}},
{x:16378,y:-917,type:-1, level:0, icon:"", name:{en:"Old Lands",fr:"Terres Anciennes",de:"Alte Lande",ru:"Старые земли"}},
{x:1442,y:-6640,type:-1, level:0, icon:"", name:{en:"Matis Island",fr:"Ile Matis",de:"Matis-Insel",ru:"Остров Матис"}},
{x:9428,y:-11083,type:-1, level:0, icon:"", name:{en:"Controlled Lands",fr:"Terres contrôlées",de:"Kontrollierte Lande",ru:"Освоенные Земли"}},
{x:10350,y:-11758,type:0, level:0, icon:"", name:{en:"Ranger camp",fr:"Campement ranger",de:"Ranger-Lager",ru:"Лагерь Рэйнджеров"}},
{x:8788,y:-7196,type:-1, level:0, icon:"", name:{en:"Nexus Minor",fr:"Nexus Mineur",de:"Kleiner Nexus",ru:"Плато Нексус"}},
{x:6323,y:-13203,type:-1, level:0, icon:"", name:{en:"Lands of Umbra",fr:"Route des Ombres",de:"Länder von Umbra",ru:"Земля Теней"}},
{x:3173,y:-10532,type:-1, level:0, icon:"", name:{en:"Under Spring",fr:"Sources Interdites",de:"Verbotene Quellen",ru:"Подземный источник"}},
{x:1592,y:-14475,type:-1, level:0, icon:"", name:{en:"Wastelands",fr:"Terres Abandonnées",de:"Niemandsland",ru:"Пустоземье"}},
{x:16917,y:-32115,type:-1, level:0, icon:"", name:{en:"Aeden Aqueous",fr:"Aeden Aqueous",de:"Aeden Aqueous",ru:"Эден Аквеос"}},
{x:17192,y:-32989,type:0, level:0, icon:"", name:{en:"Fairhaven",fr:"Fairhaven",de:"Fairhaven",ru:"Фэрхевен"}},
{x:24152,y:-30090,type:-1, level:0, icon:"", name:{en:"Old Lands",fr:"Terres Anciennes",de:"Alte Lande",ru:"Стародавние земли"}},
{x:22015,y:-33943,type:-1, level:0, icon:"", name:{en:"Aeden Aqueous",fr:"Aeden Aqueous",de:"Aeden Aqueous",ru:"Эден Аквеос"}},
{x:9711,y:-3127,type:-1, level:0, icon:"", name:{en:"Witherings",fr:"Pays Malade",de:"Verdorrende Lande",ru:"Земли Увядания"}},
{x:8627,y:-2871,type:0, level:0, icon:"", name:{en:"Zora",fr:"Zora",de:"Zora",ru:"Зора"}},
{x:8055,y:-4922,type:-1, level:0, icon:"", name:{en:"Zoraï Island",fr:"Ile Zoraï",de:"Zoraï-Insel",ru:"Остров Зораи"}}
];
//
// Ryzom Maps
// Copyright (c) 2009 Meelis Mägi <nimetu@gmail.com>
//
// This file is part of Ryzom Maps at http://maps.bmsite.net
//

/**
 * Translates current mouse coordinates to ingame x/y/zone and displays them
 */
function RyzomMapLabelControl(rl){
	this.langs=['en', 'fr', 'de', 'ru'];
	this.rl_=rl;
	this.doLabelChange=function(self, target){ return true;};
}
RyzomMapLabelControl.prototype=new GControl();

/**
 * Create element where info is displayed and add it to map
 * Automatically called
 */
RyzomMapLabelControl.prototype.initialize=function(map){
	var container = document.createElement('div');
	this.setStyle(container);
	
	for(var i=0;i<this.langs.length;i++){
		var d = document.createElement('img');
		d.alt=this.langs[i];
		d.style.margin='0 2px';
		d.src='http://maps.bmsite.net/images/lang/flag-'+this.langs[i]+'.png';
		container.appendChild(d);
	}
	
	map.getContainer().appendChild(container);
	
	var me=this;
	GEvent.addDomListener(container, 'click', function(e){
		if(e.target.alt){
			me.setLanguage(e.target.alt, e.target);
		}
	});
	return container;
};

RyzomMapLabelControl.prototype.setLanguage=function(lang, target){
	this.rl_.setLanguage(lang);
	this.doLabelChange(this, lang, target);
};

/**
 * Set the default location
 */
RyzomMapLabelControl.prototype.getDefaultPosition = function(){
	return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(2, 2));
};

/**
 * Define textbox style
 */
RyzomMapLabelControl.prototype.setStyle=function(div){
	div.style.color = "#0000cc";
	div.style.backgroundColor="transparent";//white";
	div.style.font="small Arial";
	div.style.border="none";//1px solid black";
	div.style.padding="2px";
	div.style.width="70px";
	div.style.height="1.2em";
	div.style.textAlign="center";
	div.style.cursor="pointer";
};

//
// Ryzom Maps
// Copyright (c) 2009 Meelis Mägi <nimetu@gmail.com>
//
// This file is part of Ryzom Maps at http://maps.bmsite.net
//


/**
 * Labels for map - comes with it's own marker manager
 *
 * @param string lang label language - en, fr, de
 * @param bool show_region_level if set to TRUE, then show colored region names - default is FALSE
 */
function RyzomMapLabelOverlay(lang, show_region_level){
	// default language is 'en'
	this.lang=lang;
	this.show_region_level = false; 
	this.dragged=false;

	//
	this.labels = [];

	//
	this.hidden = false;
};
RyzomMapLabelOverlay.prototype=new GOverlay();

/**
 * Set label language and calls redraw
 */
RyzomMapLabelOverlay.prototype.setLanguage=function(lang){
	if(this.lang==lang) return;
	
	var me=this;
	// change language
	this.lang=lang;

	setTimeout(function(){
		for(var i=0,len=me.labels.length;i<len;i++){
			var name='<nobr>'+ryzomLabels[me.labels[i].idx].name[lang]+'</nobr>';
			if(me.labels[i].marker.tooltip != undefined){
				me.labels[i].marker.tooltip=name;
			}else{
				try{
					me.labels[i].marker.setContents(name);
				}catch(ex){
					me.labels[i].marker.html=name;
				}
			}
		}
	}, 0);
};

/**
 * Attach or remove region color CSS rules
 *
 * @param bool state
 */
RyzomMapLabelOverlay.prototype.showRegionLevel=function(state){
	if(this.show_region_level==state){
		return;
	}


	// FIXME: must look thru all the labels and change their class name
	//        there is no one single DIV unfortunately

	this.show_region_level=state;
};

/**
 * @param GMap2 map
 */
RyzomMapLabelOverlay.prototype.initialize=function(map){
// called by map after the overlay is added to the map using GMap2.addOverlay()
	this.map_ = map;

	// disable redraw when map is being dragged
	var me=this;
	GEvent.addListener(this.map_, 'dragstart', function(){
		me.dragged=true;
	});
	GEvent.addListener(this.map_, 'dragend', function(){
		me.dragged=false;
		me.redraw();
	});

	// prep labels
	this.doLabels();

	// set css to show colorized region labels
	this.showRegionLevel(this.show_region_level);
};

RyzomMapLabelOverlay.prototype.remove=function(){
	// remove labels from map
	for(var i=0,len=this.labels.length;i<len;i++){
		this.labels[i].remove();
	}
};

/**
 * @return GOverlay
 */
RyzomMapLabelOverlay.prototype.copy=function(){
	// make clone 
	return new RyzomMapLabelOverlay(this.lang, this.show_region_level);
};

/**
 * @param bool force
 */
RyzomMapLabelOverlay.prototype.redraw=function(force){

	if(this.hidden || this.dragged) return;

	// FIXME: will be called when user is moving map too
	var bounds=this.map_.getBounds();
	var zoom = this.map_.getZoom();

	for(var i=0,len=this.labels.length; i<len; i++){
		var m=this.labels[i];
		var latlng=m.latlng;
		var opts = this.zoomRange_(m.type, zoom);

		if(bounds.containsLatLng(latlng) && zoom >=opts.min && zoom<=opts.max
		&& ((m.is_label && opts.label) || (!m.is_label && opts.icon))) {
			if(!m.marker.attached){
				this.map_.addOverlay(m.marker);
				m.marker.attached=true;
			}
			m.marker.show();
		}else{
			m.marker.hide();
		}
	}
};

RyzomMapLabelOverlay.prototype.getKml=function(callback){
	return false;
};

RyzomMapLabelOverlay.prototype.hide=function(){
	this.hidden=true;
	for(var i=0,len=this.labels.length;i<len;i++){
		if(this.labels[i].attached) this.labels[i].marker.hide();
	}
};

RyzomMapLabelOverlay.prototype.show=function(){
	this.hidden=false;
	this.redraw(true);
};
RyzomMapLabelOverlay.prototype.supportsHide=function(){
	return true;
};
RyzomMapLabelOverlay.prototype.isHidden=function(){
	return this.hidden;
};

RyzomMapLabelOverlay.prototype.zoomRange_ = function(type, zoom){
	// defaults
	var minZoom=5;
	var maxZoom=99;
	var showIcon=true;
	var showLabel=true;
	// by type
	switch(type){
		case -1: // continent. biggest zone
			minZoom=5; maxZoom=7;
			break;
		case 0: // capital city
			minZoom=6; maxZoom=10;
			break;
		case 1: // towns
			minZoom=6;
			break;
		case 2: // outposts
			showLabel=zoom>9;
			minZoom=7;
			break;
		case 3: // stable
			minZoom=7;
			showLabel=false;
			break;
		case 4: // region
			minZoom=7;
			break;
		case 5: // area
			minZoom=8;
			break;
		case 6: // street
			minZoom=10;
			break;
	}
	return { min:minZoom, max:maxZoom, icon:showIcon, label:showLabel};
};

/**
 * Creates label elements and adds them to map
 */
RyzomMapLabelOverlay.prototype.doLabels=function(){
	//
	this.disabledRegions=[
	    //
		'corrupted_moor',
		// Ring region text
		'r2_desert', 'r2_lakes', 'r2_jungle', 'r2_forest', 'r2_roots',
		// these mask 'Old Land' text
		//'tryker_island', 'matis_island', 'zorai_island', 'fyros_island'
	];
	
	var markers=[];
	for(var i=0,len=ryzomLabels.length;i<len;i++){
		// disable some region labels
		var dst = RyzomXY.belongsToIngame(ryzomLabels[i].x, ryzomLabels[i].y);
		if(dst.length>0 && this.disabledRegions.indexOf(dst[0])>-1){
			continue;
		}

		var latlng=this.map_.fromIngame(ryzomLabels[i].x, ryzomLabels[i].y);
		
		if(ryzomLabels[i].icon!=''){
			// icon might have a tooltip
			var icon=new GMarker(latlng, {
				icon: RyzomIcons.OUTPOST
			});
			icon.tooltip = '<nobr>'+ryzomLabels[i].name[this.lang]+'</nobr>';

			this.labels.push({
				idx    : i,
				type   : ryzomLabels[i].type,
				latlng : latlng,
				marker : icon,
				is_label: false,
				attached: false // is marker added to map ?
			});
		}

		var level=ryzomLabels[i].level || '0';
		var label = new ELabel(latlng, ryzomLabels[i].name[this.lang], "mapLabel mapLabel"+ryzomLabels[i].type+" region-q"+level, new GSize(-100, 0));
		
		// FIXME: dup code
		this.labels.push({
			idx    : i,
			type   : ryzomLabels[i].type,
			latlng : latlng,
			marker : label,
			is_label: true,
			attached: false // is marker added to map ?
		});
	}
};

// ELabel.js 
//
//   This Javascript is provided by Mike Williams
//   Community Church Javascript Team
//   http://www.bisphamchurch.org.uk/   
//   http://econym.org.uk/gmap/
//
//   This work is licenced under a Creative Commons Licence
//   http://creativecommons.org/licenses/by/2.0/uk/
//
// Version 0.2      the .copy() parameters were wrong
// version 1.0      added .show() .hide() .setContents() .setPoint() .setOpacity() .overlap
// version 1.1      Works with GMarkerManager in v2.67, v2.68, v2.69, v2.70 and v2.71
// version 1.2      Works with GMarkerManager in v2.72, v2.73, v2.74 and v2.75
// version 1.3      add .isHidden()
// version 1.4      permit .hide and .show to be used before addOverlay()
// version 1.5      fix positioning bug while label is hidden
// version 1.6      added .supportsHide()
// version 1.7      fix .supportsHide()
// version 1.8      remove the old GMarkerManager support due to clashes with v2.143

      function ELabel(point, html, classname, pixelOffset, percentOpacity, overlap) {
        // Mandatory parameters
        this.point = point;
        this.html = html;
        
        // Optional parameters
        this.classname = classname||"";
        this.pixelOffset = pixelOffset||new GSize(0,0);
        if (percentOpacity) {
          if(percentOpacity<0){percentOpacity=0;}
          if(percentOpacity>100){percentOpacity=100;}
        }        
        this.percentOpacity = percentOpacity;
        this.overlap=overlap||false;
        this.hidden = false;
      } 
      
      ELabel.prototype = new GOverlay();

      ELabel.prototype.initialize = function(map) {
        var div = document.createElement("div");
        div.style.position = "absolute";
        div.innerHTML = '<div class="' + this.classname + '">' + this.html + '</div>' ;
        //map.getPane(G_MAP_FLOAT_SHADOW_PANE).appendChild(div);
		map.getPane(G_MAP_MARKER_SHADOW_PANE).appendChild(div);
        this.map_ = map;
        this.div_ = div;
        if (this.percentOpacity) {        
          if(typeof(div.style.filter)=='string'){div.style.filter='alpha(opacity:'+this.percentOpacity+')';}
          if(typeof(div.style.KHTMLOpacity)=='string'){div.style.KHTMLOpacity=this.percentOpacity/100;}
          if(typeof(div.style.MozOpacity)=='string'){div.style.MozOpacity=this.percentOpacity/100;}
          if(typeof(div.style.opacity)=='string'){div.style.opacity=this.percentOpacity/100;}
        }
        if (this.overlap) {
          var z = GOverlay.getZIndex(this.point.lat());
          this.div_.style.zIndex = z;
        }
        if (this.hidden) {
          this.hide();
        }
      };

      ELabel.prototype.remove = function() {
        this.div_.parentNode.removeChild(this.div_);
      };

      ELabel.prototype.copy = function() {
        return new ELabel(this.point, this.html, this.classname, this.pixelOffset, this.percentOpacity, this.overlap);
      };

      ELabel.prototype.redraw = function(force) {
		  if(force){
	        var p = this.map_.fromLatLngToDivPixel(this.point);
    	    var h = parseInt(this.div_.clientHeight);
        	this.div_.style.left = (p.x + this.pixelOffset.width) + "px";
	        this.div_.style.top = (p.y +this.pixelOffset.height - h) + "px";
		  }
      };

      ELabel.prototype.show = function() {
        if (this.div_) {
          this.div_.style.display="";
          this.redraw();
        }
        this.hidden = false;
      };
      
      ELabel.prototype.hide = function() {
        if (this.div_) {
          this.div_.style.display="none";
        }
        this.hidden = true;
      };
      
      ELabel.prototype.isHidden = function() {
        return this.hidden;
      };
      
      ELabel.prototype.supportsHide = function() {
        return true;
      };

      ELabel.prototype.setContents = function(html) {
        this.html = html;
        this.div_.innerHTML = '<div class="' + this.classname + '">' + this.html + '</div>' ;
        this.redraw(true);
      };
      
      ELabel.prototype.setPoint = function(point) {
        this.point = point;
        if (this.overlap) {
          var z = GOverlay.getZIndex(this.point.lat());
          this.div_.style.zIndex = z;
        }
        this.redraw(true);
      };
      
      ELabel.prototype.setOpacity = function(percentOpacity) {
        if (percentOpacity) {
          if(percentOpacity<0){percentOpacity=0;}
          if(percentOpacity>100){percentOpacity=100;}
        }        
        this.percentOpacity = percentOpacity;
        if (this.percentOpacity) {        
          if(typeof(this.div_.style.filter)=='string'){this.div_.style.filter='alpha(opacity:'+this.percentOpacity+')';}
          if(typeof(this.div_.style.KHTMLOpacity)=='string'){this.div_.style.KHTMLOpacity=this.percentOpacity/100;}
          if(typeof(this.div_.style.MozOpacity)=='string'){this.div_.style.MozOpacity=this.percentOpacity/100;}
          if(typeof(this.div_.style.opacity)=='string'){this.div_.style.opacity=this.percentOpacity/100;}
        }
      };

      ELabel.prototype.getPoint = function() {
        return this.point;
      };

      ELabel.prototype.getLatLng = function() {
          return this.point;
      };
/**
 * @name MarkerClusterer
 * @version 1.0
 * @author Xiaoxi Wu
 * @copyright (c) 2009 Xiaoxi Wu
 * @fileoverview
 * This javascript library creates and manages per-zoom-level
 * clusters for large amounts of markers (hundreds or thousands).
 * This library was inspired by the <a href="http://www.maptimize.com">
 * Maptimize</a> hosted clustering solution.
 * <br /><br/>
 * <b>How it works</b>:<br/>
 * The <code>MarkerClusterer</code> will group markers into clusters according to
 * their distance from a cluster's center. When a marker is added,
 * the marker cluster will find a position in all the clusters, and
 * if it fails to find one, it will create a new cluster with the marker.
 * The number of markers in a cluster will be displayed
 * on the cluster marker. When the map viewport changes,
 * <code>MarkerClusterer</code> will destroy the clusters in the viewport
 * and regroup them into new clusters.
 *
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/**
 * @name MarkerClustererOptions
 * @class This class represents optional arguments to the {@link MarkerClusterer}
 * constructor.
 * @property {Number} [maxZoom] The max zoom level monitored by a
 * marker cluster. If not given, the marker cluster assumes the maximum map
 * zoom level. When maxZoom is reached or exceeded all markers will be shown
 * without cluster.
 * @property {Number} [gridSize=60] The grid size of a cluster in pixel. Each
 * cluster will be a square. If you want the algorithm to run faster, you can set
 * this value larger.
 * @property {Array of MarkerStyleOptions} [styles]
 * Custom styles for the cluster markers.
 * The array should be ordered according to increasing cluster size,
 * with the style for the smallest clusters first, and the style for the
 * largest clusters last.
 * @property {Function} [calculator] A function to calculator what will be showed
 * on cluster marker and what kind of style will cluster marker be.
 * This function auto called by Cluster. The default calculator will show number
 * of markers in a cluster. This function take one parm: markers.
 * You can add some property to marker of this markers array to calculat values.
 * This function returns an object:
 * {
 *   'text': 'The text to be showed on cluster marker',
 *   'index': 'Style index in array of MarginStylesOptions user passed.'
 * }
 * @property {Boolean} [zoomOnClick=true] Whether default behaviour of zooming on a cluster
 * upon clicking should be used.
 */

/**
 * @name MarkerStyleOptions
 * @class An array of these is passed into the {@link MarkerClustererOptions}
 * styles option.
 * @property {String} [url] Image url.
 * @property {Number} [height] Image height.
 * @property {Number} [height] Image width.
 * @property {Array of Number} [opt_anchor] Anchor for label text, like [24, 12].
 *    If not set, the text will align center and middle.
 * @property {String} [opt_textColor="black"] Text color.
 * @property {Number} [opt_textSize=11] Text size in px.
 */

/**
 * Creates a new MarkerClusterer to cluster markers on the map.
 *
 * @constructor
 * @param {GMap2} map The map that the markers should be added to.
 * @param {Array of GMarker} opt_markers Initial set of markers to be clustered.
 * @param {MarkerClustererOptions} opt_opts A container for optional arguments.
 */
function MarkerClusterer(map, opt_markers, opt_opts) {
  // private members
  var clusters_ = [];
  var map_ = map;
  var maxZoom_ = null;
  var me_ = this;
  var gridSize_ = 60;
  var sizes = [53, 56, 66, 78, 90];
  var styles_ = [];
  var leftMarkers_ = [];
  var mcfn_ = null;
  var zoomOnClick_ = true;

  // default calculator function
  var calculator_ = function (markers) {
    var index = 0;
    var count = markers.length;
    var dv = count;
    while (dv !== 0) {
      dv = parseInt(dv / 10, 10);
      index ++;
    }
    var stylesCount = this.getStyles().length;
    if (stylesCount < index) {
      index = stylesCount;
    }
    return {
      'text': count,
      'index': index
    };
  };

  var i = 0;
  for (i = 1; i <= 5; ++i) {
    styles_.push({
      'url': 'http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/images/m' + i + '.png',
      'height': sizes[i - 1],
      'width': sizes[i - 1]
    });
  }

  if (typeof opt_opts === 'object' && opt_opts !== null) {
    if (typeof opt_opts.gridSize === 'number' && opt_opts.gridSize > 0) {
      gridSize_ = opt_opts.gridSize;
    }
    if (typeof opt_opts.maxZoom === 'number') {
      maxZoom_ = opt_opts.maxZoom;
    }
    if (typeof opt_opts.styles === 'object' && opt_opts.styles !== null && opt_opts.styles.length !== 0) {
      styles_ = opt_opts.styles;
    }
    if (typeof opt_opts.calculator === 'function') {
      calculator_ = opt_opts.calculator;
    }
    if (typeof opt_opts.zoomOnClick === 'boolean') {
      zoomOnClick_ = opt_opts.zoomOnClick;
    }
  }

  /**
   * Set calculator function
   * @param {Function} calculator calculator function.
   */
  this.setCalculator = function (calculator) {
    calculator_ = calculator;
  };

  /**
   * Get calculator function.
   * @return {Object}
   */
  this.getCalculator = function () {
    return GEvent.callback(this, calculator_);
  };
  
  /**
   * Get boolean value representing whether default behaviour of zooming on cluster upon clicking should be used.
   * @return {Boolean}
   */
  this.isZoomOnClick = function() {
    return zoomOnClick_;
  };

  /**
   * When we add a marker, the marker may not in the viewport of map, then we don't deal with it, instead
   * we add the marker into a array called leftMarkers_. When we reset MarkerClusterer we should add the
   * leftMarkers_ into MarkerClusterer.
   */
  function addLeftMarkers_() {
    if (leftMarkers_.length === 0) {
      return;
    }
    var leftMarkers = [];

    for (i = 0; i < leftMarkers_.length; ++i) {
      if (isMarkerInViewport_(leftMarkers_[i])) {
        me_.addMarker(leftMarkers_[i], true, null, null, true);
      } else {
        leftMarkers.push(leftMarkers_[i]);
      }
    }
    leftMarkers_ = leftMarkers;
  }

  /**
   * Get cluster marker images of this marker cluster. Mostly used by {@link Cluster}
   * @return {Array of String}
   */
  this.getStyles = function () {
    return styles_;
  };

  /**
   * Remove all markers from MarkerClusterer.
   */
  this.clearMarkers = function () {
    for (var i = 0; i < clusters_.length; ++i) {
      if (typeof clusters_[i] !== "undefined" && clusters_[i] !== null) {
        clusters_[i].clearMarkers();
      }
    }
    clusters_ = [];
    leftMarkers_ = [];
//    GEvent.removeListener(mcfn_);
  };

  /**
   * Check a marker, whether it is in current map viewport.
   * @private
   * @return {Boolean} if it is in current map viewport
   */
  function isMarkerInViewport_(marker) {
    return map_.getBounds().containsLatLng(marker.getLatLng());
  }

  /**
   * When reset MarkerClusterer, there will be some markers get out of its cluster.
   * These markers should be add to new clusters.
   * @param {Array of GMarker} markers Markers to add.
   */
  function reAddMarkers_(markers) {
    var len = markers.length;
    var clusters = [];
    for (var i = len - 1; i >= 0; --i) {
      me_.addMarker(markers[i].marker, true, markers[i].isAdded, clusters, true);
    }
    addLeftMarkers_();
  }

  /**
   * Add a marker.
   * @private
   * @param {GMarker} marker Marker you want to add
   * @param {Boolean} opt_isNodraw Whether redraw the cluster contained the marker
   * @param {Boolean} opt_isAdded Whether the marker is added to map. Never use it.
   * @param {Array of Cluster} opt_clusters Provide a list of clusters, the marker
   *     cluster will only check these cluster where the marker should join.
   */
  this.addMarker = function (marker, opt_isNodraw, opt_isAdded, opt_clusters, opt_isNoCheck) {
    if (opt_isNoCheck !== true) {
      if (!isMarkerInViewport_(marker)) {
        leftMarkers_.push(marker);
        return;
      }
    }

    var isAdded = opt_isAdded;
    var clusters = opt_clusters;
    var pos = map_.fromLatLngToDivPixel(marker.getLatLng());

    if (typeof isAdded !== "boolean") {
      isAdded = false;
    }
    if (typeof clusters !== "object" || clusters === null) {
      clusters = clusters_;
    }

    var length = clusters.length;
    var cluster = null;
    for (var i = length - 1; i >= 0; i--) {
      cluster = clusters[i];
      var center = cluster.getCenter();
      if (center === null) {
        continue;
      }
      center = map_.fromLatLngToDivPixel(center);

      // Found a cluster which contains the marker.
      if (pos.x >= center.x - gridSize_ && pos.x <= center.x + gridSize_ &&
          pos.y >= center.y - gridSize_ && pos.y <= center.y + gridSize_) {
        cluster.addMarker({
          'isAdded': isAdded,
          'marker': marker
        });
        if (!opt_isNodraw) {
          cluster.redraw_();
        }
        return;
      }
    }

    // No cluster contain the marker, create a new cluster.
    cluster = new Cluster(this, map);
    cluster.addMarker({
      'isAdded': isAdded,
      'marker': marker
    });
    if (!opt_isNodraw) {
      cluster.redraw_();
    }

    // Add this cluster both in clusters provided and clusters_
    clusters.push(cluster);
    if (clusters !== clusters_) {
      clusters_.push(cluster);
    }
  };

  /**
   * Remove a marker.
   *
   * @param {GMarker} marker The marker you want to remove.
   */

  this.removeMarker = function (marker) {
    for (var i = 0; i < clusters_.length; ++i) {
      if (clusters_[i] && clusters_[i].removeMarker(marker)) {
        clusters_[i].redraw_();
        return;
      }
    }
  };

  /**
   * Redraw all clusters in viewport.
   */
  this.redraw_ = function () {
    var clusters = this.getClustersInViewport_();
    for (var i = 0; i < clusters.length; ++i) {
      clusters[i].redraw_(true);
    }
  };

  /**
   * Get all clusters in viewport.
   * @return {Array of Cluster}
   */
  this.getClustersInViewport_ = function () {
    var clusters = [];
    var curBounds = map_.getBounds();
    for (var i = 0; i < clusters_.length; i ++) {
      if (clusters_[i].isInBounds(curBounds)) {
        clusters.push(clusters_[i]);
      }
    }
    return clusters;
  };

  /**
   * Get max zoom level.
   * @private
   * @return {Number}
   */
  this.getMaxZoom_ = function () {
    return maxZoom_;
  };

  /**
   * Get map object.
   * @private
   * @return {GMap2}
   */
  this.getMap_ = function () {
    return map_;
  };

  /**
   * Get grid size
   * @private
   * @return {Number}
   */
  this.getGridSize_ = function () {
    return gridSize_;
  };

  /**
   * Get total number of markers.
   * @return {Number}
   */
  this.getTotalMarkers = function () {
    var result = 0;
    for (var i = 0; i < clusters_.length; ++i) {
      result += clusters_[i].getTotalMarkers();
    }
    return result;
  };

  /**
   * Get total number of clusters.
   * @return {int}
   */
  this.getTotalClusters = function () {
    return clusters_.length;
  };

  /**
   * Collect all markers of clusters in viewport and regroup them.
   */
  this.resetViewport = function () {
    var clusters = this.getClustersInViewport_();
    var tmpMarkers = [];
    var removed = 0;

    for (var i = 0; i < clusters.length; ++i) {
      var cluster = clusters[i];
      var oldZoom = cluster.getCurrentZoom();
      if (oldZoom === null) {
        continue;
      }
      var curZoom = map_.getZoom();
      if (curZoom !== oldZoom) {

        // If the cluster zoom level changed then destroy the cluster
        // and collect its markers.
        var mks = cluster.getMarkers();
        for (var j = 0; j < mks.length; ++j) {
          var newMarker = {
            'isAdded': false,
            'marker': mks[j].marker
          };
          tmpMarkers.push(newMarker);
        }
        cluster.clearMarkers();
        removed++;
        for (j = 0; j < clusters_.length; ++j) {
          if (cluster === clusters_[j]) {
            clusters_.splice(j, 1);
          }
        }
      }
    }

    // Add the markers collected into marker cluster to reset
    reAddMarkers_(tmpMarkers);
    this.redraw_();
  };


  /**
   * Add a set of markers.
   *
   * @param {Array of GMarker} markers The markers you want to add.
   */
  this.addMarkers = function (markers) {
    for (var i = 0; i < markers.length; ++i) {
      this.addMarker(markers[i], true);
    }
    this.redraw_();
  };

  
  /**   
    * Returns the cluster containing this marker, if any.   
    *   
    * @param {GMarker} marker The marker whose containing cluster is to be returned.   
    * @return {Cluster}   
    */  
  this.getParentCluster = function(marker) {
    return marker.parentCluster_;
  };

  
  // initialize
  if (typeof opt_markers === "object" && opt_markers !== null) {
    this.addMarkers(opt_markers);
  }

  // when map move end, regroup.
  mcfn_ = GEvent.addListener(map_, "moveend", function () {
    me_.resetViewport();
  });
}
/**
 * Create a cluster to collect markers.
 * A cluster includes some markers which are in a block of area.
 * If there are more than one markers in cluster, the cluster
 * will create a {@link ClusterMarker_} and show the total number
 * of markers in cluster.
 *
 * @constructor
 * @private
 * @param {MarkerClusterer} markerClusterer The marker cluster object
 */
function Cluster(markerClusterer) {
  var center_ = null;
  var markers_ = [];
  var markerClusterer_ = markerClusterer;
  var map_ = markerClusterer.getMap_();
  var clusterMarker_ = null;
  var zoom_ = map_.getZoom();
  var this_ = this;
  
  /**
   * Get markers of this cluster.
   *
   * @return {Array of GMarker}
   */
  this.getMarkers = function () {
    return markers_;
  };

  /**
   * Get the marker clusterer handling this cluster
   *
   * @return {MarkerClusterer} The marker clusterer object
   */
  this.getMarkerClusterer = function() {
    return markerClusterer_;
  };
  
  /**
   * If this cluster intersects certain bounds.
   *
   * @param {GLatLngBounds} bounds A bounds to test
   * @return {Boolean} Is this cluster intersects the bounds
   */
  this.isInBounds = function (bounds) {
    if (center_ === null) {
      return false;
    }

    if (!bounds) {
      bounds = map_.getBounds();
    }
    var sw = map_.fromLatLngToDivPixel(bounds.getSouthWest());
    var ne = map_.fromLatLngToDivPixel(bounds.getNorthEast());

    var centerxy = map_.fromLatLngToDivPixel(center_);
    var inViewport = true;
    var gridSize = markerClusterer.getGridSize_();
    if (zoom_ !== map_.getZoom()) {
      var dl = map_.getZoom() - zoom_;
      gridSize = Math.pow(2, dl) * gridSize;
    }
    if (ne.x !== sw.x && (centerxy.x + gridSize < sw.x || centerxy.x - gridSize > ne.x)) {
      inViewport = false;
    }
    if (inViewport && (centerxy.y + gridSize < ne.y || centerxy.y - gridSize > sw.y)) {
      inViewport = false;
    }
    return inViewport;
  };

  /**
   * Get cluster center.
   *
   * @return {GLatLng}
   */
  this.getCenter = function () {
    return center_;
  };

  /**
   * Add a marker.
   *
   * @param {Object} marker An object of marker you want to add:
   *   {Boolean} isAdded If the marker is added on map.
   *   {GMarker} marker The marker you want to add.
   */
  this.addMarker = function (marker) {
    if (center_ === null) {
      /*var pos = marker['marker'].getLatLng();
       pos = map.fromLatLngToContainerPixel(pos);
       pos.x = parseInt(pos.x - pos.x % (GRIDWIDTH * 2) + GRIDWIDTH);
       pos.y = parseInt(pos.y - pos.y % (GRIDWIDTH * 2) + GRIDWIDTH);
       center = map.fromContainerPixelToLatLng(pos);*/
      center_ = marker.marker.getLatLng();
    }
    marker.marker.parentCluster_ = this_;     
    markers_.push(marker);
  };

  /**
   * Remove a marker from cluster.
   *
   * @param {GMarker} marker The marker you want to remove.
   * @return {Boolean} Whether find the marker to be removed.
   */
  this.removeMarker = function (marker) {
    for (var i = 0; i < markers_.length; ++i) {
      if (marker === markers_[i].marker) {
        if (markers_[i].isAdded) {
          map_.removeOverlay(markers_[i].marker);
        }
        delete markers_[i].marker.parentCluster_;
        markers_.splice(i, 1);
        return true;
      }
    }
    return false;
  };

  /**
   * Get current zoom level of this cluster.
   * Note: the cluster zoom level and map zoom level not always the same.
   *
   * @return {Number}
   */
  this.getCurrentZoom = function () {
    return zoom_;
  };

  /**
   * Redraw a cluster.
   * @private
   * @param {Boolean} isForce If redraw by force, no matter if the cluster is
   *     in viewport.
   */
  this.redraw_ = function (isForce) {
    if (!isForce && !this.isInBounds()) {
      return;
    }

    // Set cluster zoom level.
    zoom_ = map_.getZoom();
    var i = 0;
    var mz = markerClusterer.getMaxZoom_();
    if (mz === null) {
      mz = map_.getCurrentMapType().getMaximumResolution();
    }
    if (zoom_ > mz || this.getTotalMarkers() === 1) {

      // If current zoom level is beyond the max zoom level or the cluster
      // have only one marker, the marker(s) in cluster will be showed on map.
      for (i = 0; i < markers_.length; ++i) {
        if (markers_[i].isAdded) {
          if (markers_[i].marker.isHidden()) {
            markers_[i].marker.show();
          }
        } else {
          map_.addOverlay(markers_[i].marker);
          markers_[i].isAdded = true;
        }
      }
      if (clusterMarker_ !== null) {
        clusterMarker_.hide();
      }
    } else if (this.getTotalMarkers() > 1) {
      // Else add a cluster marker on map to show the number of markers in
      // this cluster.
      for (i = 0; i < markers_.length; ++i) {
        if (markers_[i].isAdded && (!markers_[i].marker.isHidden())) {
          markers_[i].marker.hide();
        }
      }
      var sums = markerClusterer_.getCalculator()(this.getRealMarkers());
      if (clusterMarker_ === null) {
        clusterMarker_ = new ClusterMarker_(center_, sums, markerClusterer_.getStyles(), markerClusterer_.getGridSize_(), this_);
        map_.addOverlay(clusterMarker_);
      } else {
        if (clusterMarker_.isHidden()) {
          clusterMarker_.show();
        }
        clusterMarker_.setSums(sums);
        clusterMarker_.redraw(true);
      }
    }
  };

  /**
   * Remove all the markers from this cluster.
   */
  this.clearMarkers = function () {
    if (clusterMarker_ !== null) {
      map_.removeOverlay(clusterMarker_);
    }
    for (var i = 0; i < markers_.length; ++i) {
      if (markers_[i].isAdded) {
        map_.removeOverlay(markers_[i].marker);
      }
      delete markers_[i].marker.parentCluster_;    
    }
    markers_ = [];
  };

  /**
   * Get number of markers.
   * @return {Number}
   */
  this.getTotalMarkers = function () {
    return markers_.length;
  };

  /**
   * Get all real markers by array.
   * @return {GMarker}
   */
  this.getRealMarkers = function () {
    var result = [];
    for (var i = 0; i < markers_.length; ++i) {
      result.push(markers_[i].marker);
    }
    return result;
  };
}

/**
 * ClusterMarker_ creates a marker that shows the number of markers that
 * a cluster contains.
 *
 * @constructor
 * @private
 * @param {GLatLng} latlng Marker's lat and lng.
 * @param {Object} sums text and image to show:
 *   {String} text Text to show.
 *   {Number} index Image index by styles.
 * @param {Array of Object} styles The image list to be showed:
 *   {String} url Image url.
 *   {Number} height Image height.
 *   {Number} width Image width.
 *   {Array of Number} anchor Text anchor of image left and top.
 *   {String} textColor text color.
 * @param {Number} padding Padding of marker center.
 * @param {Cluster} cluster Cluster object that corresponds to this marker.
 */
function ClusterMarker_(latlng, sums, styles, padding, cluster) {
/*  var index = 0;
  var dv = count;
  while (dv !== 0) {
    dv = parseInt(dv / 10, 10);
    index ++;
  }

  if (styles.length < index) {
    index = styles.length;
  }*/
  var index = sums.index;
  this.useStyle(styles[index-1]);
  this.styleDirty_ = false;
  this.latlng_ = latlng;
  this.index_ = index;
  this.styles_ = styles;
  this.text_ = sums.text;
  this.padding_ = padding;
  this.sums_ = sums;
  this.cluster_ = cluster;
}

ClusterMarker_.prototype = new GOverlay();

/**
 * Populates style dependant fields given a particular style.
 * @private
 */
ClusterMarker_.prototype.useStyle = function(style) {
  this.url_ = style.url;
  this.height_ = style.height;
  this.width_ = style.width;
  this.textColor_ = style.opt_textColor;
  this.anchor_ = style.opt_anchor;
  this.textSize_ = style.opt_textSize;
};

/**
 * Initialize cluster marker.
 * @private
 */
ClusterMarker_.prototype.initialize = function (map) {
  this.map_ = map;
  var div = document.createElement("div");
  var latlng = this.latlng_;  
  var pos = this.getPosFromLatLng(latlng);  
  div.style.cssText = this.createCss(pos);
  div.innerHTML = this.text_;
  map.getPane(G_MAP_MAP_PANE).appendChild(div);
  var padding = this.padding_;
  var cluster = this.cluster_;
  GEvent.addDomListener(div, "click", function () {
    GEvent.trigger(cluster.getMarkerClusterer(), "clusterclick", cluster);
    if (cluster.getMarkerClusterer().isZoomOnClick()) {
      var pos = map.fromLatLngToDivPixel(latlng);
      var sw = new GPoint(pos.x - padding, pos.y + padding);
      sw = map.fromDivPixelToLatLng(sw);
      var ne = new GPoint(pos.x + padding, pos.y - padding);
      ne = map.fromDivPixelToLatLng(ne);
      var zoom = map.getBoundsZoomLevel(new GLatLngBounds(sw, ne), map.getSize());
      map.setCenter(latlng, zoom);
    }
  });
  this.div_ = div;
};

/**
 * Returns position to place the div depending on the latlong.
 * @private
 */
ClusterMarker_.prototype.getPosFromLatLng = function(latlng) {
  var pos = this.map_.fromLatLngToDivPixel(latlng);
  pos.x -= parseInt(this.width_ / 2, 10);
  pos.y -= parseInt(this.height_ / 2, 10);
  return pos;
};

/**
 * Returns the css style string to be applied to the div given its position.
 * @private
 */
ClusterMarker_.prototype.createCss = function(pos) {
  var mstyle = "";
  if (document.all) {
    mstyle = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="' + this.url_ + '");';
  } else {
    mstyle = "background:url(" + this.url_ + ");";
  }
  if (typeof this.anchor_ === "object") {
    if (typeof this.anchor_[0] === "number" && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) {
      mstyle += 'height:' + (this.height_ - this.anchor_[0]) + 'px;padding-top:' + this.anchor_[0] + 'px;';
    } else {
      mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
    }
    if (typeof this.anchor_[1] === "number" && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) {
      mstyle += 'width:' + (this.width_ - this.anchor_[1]) + 'px;padding-left:' + this.anchor_[1] + 'px;';
    } else {
      mstyle += 'width:' + this.width_ + 'px;text-align:center;';
    }
  } else {
    mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
    mstyle += 'width:' + this.width_ + 'px;text-align:center;';
  }
  var txtColor = this.textColor_ ? this.textColor_ : 'black';
  var txtSize = this.textSize_ ? this.textSize_ : 11;
  
  return mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
      pos.x + "px;color:" + txtColor +  ";position:absolute;font-size:" + txtSize + "px;" +
      'font-family:Arial,sans-serif;font-weight:bold';
};

/**
 * Remove this overlay.
 * @private
 */
ClusterMarker_.prototype.remove = function () {
  this.div_.parentNode.removeChild(this.div_);
};

/**
 * Copy this overlay.
 * @private
 */
ClusterMarker_.prototype.copy = function () {
  return new ClusterMarker_(this.latlng_, this.sums_, this.text_, this.styles_, this.padding_, this.cluster_);
};

/**
 * Redraw this overlay.
 * @private
 */
ClusterMarker_.prototype.redraw = function (force) {
  if (!force) {
    return;
  }
  var pos = this.getPosFromLatLng(this.latlng_);
  if (this.styleDirty_) {
    this.styleDirty_ = false;
    this.useStyle(this.styles_[this.index_-1]);
    this.div_.style.cssText = this.createCss(pos);
  } else {
    this.div_.style.top =  pos.y + "px";
    this.div_.style.left = pos.x + "px";
  }
};

/**
 * Hide this cluster marker.
 */
ClusterMarker_.prototype.hide = function () {
  this.div_.style.display = "none";
};

/**
 * Show this cluster marker.
 */
ClusterMarker_.prototype.show = function () {
  this.div_.style.display = "";
};

/**
 * Get whether the cluster marker is hidden.
 * @return {Boolean}
 */
ClusterMarker_.prototype.isHidden = function () {
  return this.div_.style.display === "none";
};

/**
 * Sets this cluster marker sums value, updates its text and marks the style for updating on next redraw if necessary.
 * @param {Object} sums text and image to show:
 *   {String} text Text to show.
 *   {Number} index Image index by styles.
 */
ClusterMarker_.prototype.setSums = function (sums) {
  if (sums.index !== this.index_) {
    this.styleDirty_ = true;
  }
  this.sums_ = sums;
  this.text_ = sums.text;
  this.index_ = sums.index;
  this.div_.innerHTML = sums.text;
};
/**
 * @name MarkerManager
 * @version 1.0
 * @copyright (c) 2007 Google Inc.
 * @author Doug Ricket, others
 *
 * @fileoverview Marker manager is an interface between the map and the user,
 * designed to manage adding and removing many points when the viewport changes.
 * <br /><br />
 * <b>How it Works</b>:<br/> 
 * The MarkerManager places its markers onto a grid, similar to the map tiles.
 * When the user moves the viewport, it computes which grid cells have
 * entered or left the viewport, and shows or hides all the markers in those
 * cells.
 * (If the users scrolls the viewport beyond the markers that are loaded,
 * no markers will be visible until the <code>EVENT_moveend</code> 
 * triggers an update.)
 * In practical consequences, this allows 10,000 markers to be distributed over
 * a large area, and as long as only 100-200 are visible in any given viewport,
 * the user will see good performance corresponding to the 100 visible markers,
 * rather than poor performance corresponding to the total 10,000 markers.
 * Note that some code is optimized for speed over space,
 * with the goal of accommodating thousands of markers.
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

/**
 * @name MarkerManagerOptions
 * @class This class represents optional arguments to the {@link MarkerManager}
 *     constructor.
 * @property {Number} [maxZoom] Sets the maximum zoom level monitored by a
 *     marker manager. If not given, the manager assumes the maximum map zoom
 *     level. This value is also used when markers are added to the manager
 *     without the optional {@link maxZoom} parameter.
 * @property {Number} [borderPadding] Specifies, in pixels, the extra padding
 *     outside the map's current viewport monitored by a manager. Markers that
 *     fall within this padding are added to the map, even if they are not fully
 *     visible.
 * @property {Boolean} [trackMarkers=false] Indicates whether or not a marker
 *     manager should track markers' movements. If you wish to move managed
 *     markers using the {@link setPoint}/{@link setLatLng} methods, 
 *     this option should be set to {@link true}.
 */

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

  opt_opts = opt_opts || {};
  me.tileSize_ = MarkerManager.DEFAULT_TILE_SIZE_;

  var mapTypes = map.getMapTypes();
  var mapMaxZoom = mapTypes[0].getMaximumResolution();
  for (var i = 0; i < mapTypes.length; i++) {
    var mapTypeMaxZoom = mapTypes[i].getMaximumResolution();
    if (mapTypeMaxZoom > mapMaxZoom) {
      mapMaxZoom = mapTypeMaxZoom;
    }
  }
  me.maxZoom_  = opt_opts.maxZoom || mapMaxZoom;

  me.trackMarkers_ = opt_opts.trackMarkers;
  me.show_ = opt_opts.show || true;

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

  me.gridWidth_ = [];

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

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

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

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

  me.shownBounds_ = me.getMapGridBounds_();
}

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


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

/**
 * Removes all markers in the manager, and
 * removes any visible markers from the map.
 */
MarkerManager.prototype.clearMarkers = function () {
  var me = this;
  me.processAll_(me.shownBounds_, me.removeOverlay_);
  me.resetManager_();
};


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


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

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

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

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


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


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


/**
 * Removes marker from the manager and from the map
 * (if it's currently visible).
 * @param {GMarker} marker The marker to delete.
 */
MarkerManager.prototype.removeMarker = function (marker) {
  var me = this;
  var zoom = me.maxZoom_;
  var changed = false;
  var point = marker.getPoint();
  var grid = me.getTilePoint_(point, zoom, GSize.ZERO);
  while (zoom >= 0) {
    var cell = me.getGridCellNoCreate_(grid.x, grid.y, zoom);

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


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

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


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


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

/** 
 * Returns a marker given latitude, longitude and zoom. If the marker does not 
 * exist, the method will return a new marker. If a new marker is created, 
 * it will NOT be added to the manager. 
 * 
 * @param {Number} lat - the latitude of a marker. 
 * @param {Number} lng - the longitude of a marker. 
 * @param {Number} zoom - the zoom level 
 * @return {GMarker} marker - the marker found at lat and lng 
 */ 
MarkerManager.prototype.getMarker = function(lat, lng, zoom) { 
  var me = this; 
  var mPoint = new GLatLng(lat, lng); 
  var gridPoint = me.getTilePoint_(mPoint, zoom, GSize.ZERO); 

  var marker = new GMarker(mPoint); 
  var cellArray = me.getGridCellNoCreate_(gridPoint.x, gridPoint.y, zoom); 
  if(cellArray != undefined){ 
    for (var i = 0; i < cellArray.length; i++) 
    { 
      if(lat == cellArray[i].getLatLng().lat() && 
         lng == cellArray[i].getLatLng().lng()) 
      { 
        marker = cellArray[i]; 
      } 
    } 
  } 
  return marker; 
}; 

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

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

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


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


/**
 * Turns at geographical bounds into a grid-space bounds.
 *
 * @param {LatLngBounds} bounds The geographical bounds.
 * @param {Number} zoom The zoom level of the bounds.
 * @param {GSize} swPadding The padding in pixels to extend beyond the
 * given bounds.
 * @param {GSize} nePadding The padding in pixels to extend beyond the
 * given bounds.
 * @return {GBounds} The bounds in grid space.
 */
MarkerManager.prototype.getGridBounds_ = function (bounds, zoom, swPadding, nePadding) {
  zoom = Math.min(zoom, this.maxZoom_);

  var bl = bounds.getSouthWest();
  var tr = bounds.getNorthEast();
  var sw = this.getTilePoint_(bl, zoom, swPadding);
  var ne = this.getTilePoint_(tr, zoom, nePadding);
  var gw = this.gridWidth_[zoom];

  // Crossing the prime meridian requires correction of bounds.
  if (tr.lng() < bl.lng() || ne.x < sw.x) {
    sw.x -= gw;
  }
  if (ne.x - sw.x  + 1 >= gw) {
    // Computed grid bounds are larger than the world; truncate.
    sw.x = 0;
    ne.x = gw - 1;
  }
  var gridBounds = new GBounds([sw, ne]);
  gridBounds.z = zoom;
  return gridBounds;
};


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


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


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


/**
 * Is this layer visible?
 *
 * Returns visibility setting
 *
 * @return {Boolean} Visible
 */
MarkerManager.prototype.visible = function () {
  return this.show_ ? true : false;
};


/**
 * Returns true if the manager is hidden.
 * Otherwise returns false.
 * @return {Boolean} Hidden
 */
MarkerManager.prototype.isHidden = function () {
  return !this.show_;
};


/**
 * Shows the manager if it's currently hidden.
 */
MarkerManager.prototype.show = function () {
  this.show_ = true;
  this.refresh();
};


/**
 * Hides the manager if it's currently visible
 */
MarkerManager.prototype.hide = function () {
  this.show_ = false;
  this.refresh();
};


/**
 * Toggles the visibility of the manager.
 */
MarkerManager.prototype.toggle = function () {
  this.show_ = !this.show_;
  this.refresh();
};


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


/**
 * After the viewport may have changed, add or remove markers as needed.
 */
MarkerManager.prototype.updateMarkers_ = function () {
  var me = this;
  me.mapZoom_ = this.map_.getZoom();
  var newBounds = me.getMapGridBounds_();

  // If the move does not include new grid sections,
  // we have no work to do:
  if (newBounds.equals(me.shownBounds_) && newBounds.z === me.shownBounds_.z) {
    return;
  }

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

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

  me.notifyListeners_();
};


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


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


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


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


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


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


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

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

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


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

// cache created 2010-09-03 16:40:02 UTC
