/*
============================================================================================


	JS-GIS API: JavaScript Geographic Information System browser library
 	Copyright (C) 2001, 2002 Luca Sigfrido Percich

	The JS-GIS API is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation: please refer to

	http://www.gnu.org/licenses/gpl.html

	The JS-GIS API 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 General Public License for more details.


============================================================================================

 	Author:			Luca Sigfrido Percich (luca.percich@reacoop.it)
 	Version:		2.1
 	Last updated:		dec 26, 2002

				added:	TAnchor class; TFrame.anchor & TImage.anchor
					minor bug fixes
	

============================================================================================
*/




/*
============================================================================================
============================================================================================

 	Geometric classes

	Class definitions:	TPoint
				TRect

============================================================================================
============================================================================================
*/


	function TPoint(aX, aY)
	{
		this.className	= 'TPoint'
		this.isPoint	= 1

		this.distance	= TPoint_distance
		this.expand	= TPoint_expand
		this.moveTo	= TPoint_moveTo
		this.translate	= TPoint_translate
		this.toRelative	= TPoint_toRelative
		this.toAbsolute	= TPoint_toAbsolute
		this.within	= TPoint_within
		this.contains	= TPoint_contains
		this.doRound	= TPoint_doRound
		this.clone	= TPoint_clone
		this.assign	= TPoint_assign

		this.toString	= TPoint_toString

		this.moveTo(aX, aY)	
	}


	
	function TPoint_toString()
	{
		return 'TPoint(' + this.x + ',' + this.y + ')'
	}

	function TPoint_clone()
	{
		return new TPoint(this.x, this.y)
	}


	function TPoint_assign(p)
	{
		if (p && p.isPoint)
		{
			this.x = p.x
			this.y = p.y
		}
	}


	function TPoint_distance(p)
	{
		if (p.isPoint)
			return Math.sqrt(Math.pow(this.x - p.x, 2) + Math.pow(this.y - p.y, 2))
	}


	function TPoint_expand(leftX, upY, rightX, downY)
	{
		if (arguments.length < 2) var upY  = leftX
		if (arguments.length < 3) var rightX = leftX
		if (arguments.length < 4) var downY = upY
		return new TRect(this.x - leftX, this.y - upY, this.x + rightX, this.y + downY)
	}


	function TPoint_moveTo(newX, newY)
	{
		this.x = newX
		this.y = newY
	}


	function TPoint_translate(src, dest)
	{
		if (src && src.isRect && dest && dest.isRect)
		  return new TPoint
			(
			dest.min.x + 
			((src.xAxis == dest.xAxis) ? (this.x - src.min.x) : (src.max.x - this.x)) * dest.width / src.width
			,
			dest.min.y +
			((src.yAxis == dest.yAxis) ? (this.y - src.min.y) : (src.max.y - this.y)) * dest.height / src.height
			)
	}



	function TPoint_within(r)
	{
		if (!r) return

		if (r.isRect)
			return	(this.x >= r.min.x) &&
				(this.x <= r.max.x) &&
				(this.y >= r.min.y) &&
				(this.y <= r.max.y)
		else if (r.contains)
			return r.contains(this)
	}



	function TPoint_contains(obj)
	{
		return 0
	}



	function TPoint_toRelative(origin)
	{
		if (origin.isPoint) return new TPoint(this.x - origin.x, this.y - origin.y)
	}



	function TPoint_toAbsolute(origin)
	{
		if (origin.isPoint) return new TPoint(origin.x + this.x, origin.y + this.y)
	}



	function TPoint_doRound()
	{
		this.x = Math.round(this.x)
		this.y = Math.round(this.y)
	}






/*
	TRect Class
============================================================================================
*/


	function TRect(aMinX, aMinY, aMaxX, aMaxY, bottomUpY, rightLeftX)
	{
		this.className		= 'TRect'
		this.isRect		= 1

		this.max		= new TPoint(Math.max(aMinX, aMaxX), Math.max(aMinY, aMaxY))
		this.min		= new TPoint(Math.min(aMinX, aMaxX), Math.min(aMinY, aMaxY))
		this.width		= this.max.x - this.min.x
		this.height		= this.max.y - this.min.y

		this.yAxis		= (bottomUpY)  ?  -1 : 1
		this.xAxis		= (rightLeftX) ?  -1 : 1

		this.centroid		= TRect_centroid
		this.moveTo		= TRect_moveTo
		this.moveBy		= TRect_moveBy
		this.resize		= TRect_resize
		this.within		= TRect_within
		this.intersects		= TRect_intersects
		this.intersection	= TRect_intersection
		this.translate		= TRect_translate
		this.toRelative		= TRect_toRelative
		this.toAbsolute		= TRect_toAbsolute
		this.doRound		= TRect_doRound
		this.clone		= TRect_clone
		this.assign		= TRect_assign

		this.toString		= TRect_toString

	}



	function TRect_toString()
	{
		return 'TRect(' + this.min + ',' + this.max + ')'
	}
	
	function TRect_clone()
	{
		return new TRect(this.min.x, this.min.y, this.max.x, this.max.y, (this.yAxis != 1), (this.xAxis != 1))
	}


	function TRect_assign(r)
	{
		if (r && r.isRect)
		{
			this.moveTo(r.min.x, r.min.y, r.max.x, r.max.y)
			this.xAxis = r.xAxis
			this.yAxis = r.yAxis
		}
	}		


	function TRect_within(r)
	{
		if (!r) return 0

	     	if (r.isRect)
	
			return  (
				(r.min.x <= this.min.x) &&
				(r.max.x >= this.max.x) &&
				(r.min.y <= this.min.y) &&
				(r.max.y >= this.max.y)
				)

			else if (r.contains)

				return r.contains(this)

	     		else 

				return 0
	}



	function TRect_intersects(r)
	{
		if (r && r.isRect)

		  return !(
			(this.min.x > r.max.x) ||
			(this.max.x < r.min.x) ||
			(this.min.y > r.max.y) ||
			(this.max.y < r.min.y)
			)

		else if (r.intersects)

		  return r.intersects(this)

		else

		  return 0
	}



	function TRect_intersection(r)
	{
		if (this.intersects(r))
			return new TRect(
				Math.max(this.min.x, r.min.x), Math.max(this.min.y, r.min.y),
				Math.min(this.max.x, r.max.x), Math.min(this.max.y, r.max.y),
				(this.yAxis != 1), (this.xAxis != 1))
	}




	function TRect_translate(src, dest)
	{
		if ((min = this.min.translate(src, dest)) && 
		    (max = this.max.translate(src, dest)))
			return new TRect(min.x, min.y, max.x, max.y, (this.yAxis != 1), (this.xAxis != 1))
	}




	function TRect_centroid()
	{
		return new TPoint(
			((this.max.x - this.min.x) / 2) + this.min.x,
			((this.max.y - this.min.y) / 2) + this.min.y)
	}
		

	
	function TRect_moveTo(newMinX, newMinY, newMaxX, newMaxY)
	{
		this.min.moveTo(newMinX, newMinY)

		if (arguments.length > 2)
		{
			this.max.moveTo(newMaxX, newMaxY)
			this.width = this.max.x - this.min.x
			this.height = this.max.y - this.min.y
		}
		 else
			this.max.moveTo(this.min.x + this.width, this.min.y + this.height)

	}
	



	function TRect_moveBy(deltaX, deltaY)
	{
		this.moveTo(this.min.x + deltaX, this.min.y + deltaY)
	}



	function TRect_resize(newWidth, newHeight)
	{
		this.moveTo(this.min.x, this.min.y, this.min.x + newWidth, this.min.y + newHeight)
	}
	
	

	function TRect_toRelative(origin)
	{
		if ((nMin = this.min.toRelative(origin)) && 
		    (nMax = this.max.toRelative(origin)))
			return new TRect(nMin.x, nMin.y, nMax.x, nMax.y, (this.yAxis != 1), (this.xAxis != 1))
	}



	function TRect_toAbsolute(origin)
	{
		if ((nMin = this.min.toAbsolute(origin)) &&
		    (nMax = this.max.toAbsolute(origin)))
			return new TRect(nMin.x, nMin.y, nMax.x, nMax.y, (this.yAxis != 1), (this.xAxis != 1))
	}


	function TRect_doRound()
	{
		this.min.doRound()
		this.max.doRound()
	}








/*
============================================================================================
============================================================================================

 	HTML classes

	Class definitions:	TStyle
				TFrame
				TImage
				TAnchor


============================================================================================
============================================================================================*/


/*
	TStyle Class
============================================================================================
*/

	function TStyle()
	{
		this.className		= 'TStyle'
		this.isStyle		= 1

		this.units		= 'px'
		this.autoClip		= 1
		this.overflow		= ''

		this.toString		= TStyle_toString
		this.setBox		= TStyle_setBox
		this.setClip		= TStyle_setClip
		this.setBorder		= TStyle_setBorder
		this.setBackground	= TStyle_setBackground

		this.getDef		= TStyle_getDef
	}



	function TStyle_toString()
	{
		var szBuf = ''

		if (this.border)
		{
			if ((this.border.width) && (this.border.width > 0))
				szBuf += 'border: ' + this.border.width + this.units + ' ' + this.border.style + ' ' + this.border.color + ';'
		}

		if (this.box)
		{
			szBuf += 'position:absolute;top:' + this.box.y + this.units + ';left:' + this.box.x + this.units + ';'
			if (this.box.width) szBuf  += 'width:' + this.box.width + this.units + ';'
			if (this.box.height) szBuf += 'height:' + this.box.height + this.units + ';'
		}

		if (this.clip)
		{
			szBuf += 'clip:rect(' + this.clip.top + this.units + ' ' + this.clip.right + this.units + ' ' + this.clip.bottom + this.units + ' ' + this.clip.left + this.units + ');'
		}

		if (this.overflow) szBuf += 'overflow:' + this.overflow + ';'

		if (this.background)
		{
			if (this.background.image != '')
				szBuf += 'background-image:URL(' + this.background.image + ');layer-background-image:URL(' + this.background.image + ');'

			if (this.background.color != '')
				szBuf += 'background-color:' + this.background.color + ';layer-background-color:' + this.background.color + ';'
		}

		return szBuf
	}



	function TStyle_getDef()
	{
		var szBuf = this.toString()
		return (szBuf) ? ' style="' + szBuf + '"' : ''
	}


	function TStyle_setBox(x, y, width, height)
	{
		this.box		= new Object()
		this.box.x		= x
		this.box.y		= y
		this.box.width		= width
		this.box.height		= height
	}


	function TStyle_setClip(top, right, bottom, left)
	{
		this.clip		= new Object()

		var k = (this.border) ? this.border.width * 2 : 0

		this.clip.top		= (top)	   ? top    : -k
		this.clip.right		= (right)  ? right  : this.box.width + k
		this.clip.bottom	= (bottom) ? bottom : this.box.height + k
		this.clip.left		= (left)   ? left   : -k
	}


	function TStyle_setBorder(width, style, color)
	{
		this.border		= new Object()

		this.border.width	= (width) ? width : 0
		this.border.style	= (style) ? style : 'solid'
		this.border.color	= (color) ? color : 'black'
	}



	function TStyle_setBackground(color, image)
	{
		this.background 	= new Object()

		this.background.color	= (color) ? color : ''
		this.background.image	= (image) ? image : ''
	}



/*
	TFrame Class
============================================================================================
*/

	function TFrame(aX, aY, aWidth, aHeight, varName)
	{
		this.className		= 'TFrame'
		this.isFrame		= 1

		this.rect		= new TRect(aX, aY, aX + aWidth, aY + aHeight)
		this.varName 		= (varName) ? varName : ''
		this.visible		= 1

		this.style		= new TStyle()


		this.contents		= ''

		this.anchor		= ''
		this.beforeDraw		= ''

		this.toString		= TFrame_toString
		this.resize		= TFrame_resize
		this.updateBox		= TFrame_updateBox
		this.setClickHandler	= TFrame_setClickHandler
		this.updateBox()

	}



	function TFrame_toString()
	{

		if ((this.beforeDraw) && !(this.beforeDraw())) return ''

		if (!this.visible) return ''

		var szBuf = '<div '
		if (this.varName != '') szBuf += 'id="' + this.varName + '" '

		if (this.style)
		{
			if (this.autoClip) this.style.setClip()
			szBuf += this.style.getDef()
		}

		szBuf += '>' + ((this.contents) ? this.contents : '') + '</div>'

		if (this.anchor)
			szBuf = this.anchor.toString(szBuf)

		return szBuf
		
	}
	


	function TFrame_updateBox()
	{
		this.style.setBox(this.rect.min.x, this.rect.min.y, this.rect.width, this.rect.height)
	}


	function TFrame_resize(newW, newH, newX, newY)
	{
		if (arguments.length > 2)
			this.rect.moveTo(newX, newY, newX + newW, newY + newH)
		else
			this.rect.resize(newW, newH)

		this.updateBox()
	}


	function TFrame_setClickHandler(handlerObjName)
	{
		this.anchor = new TAnchor('parent.objectClick(event, ' + handlerObjName + ')','',1,1)
	}



/*
	TImage Class
--------------------------------------------------------------------------------------------*/

	function TImage(src, w, h, x, y)
	{
		this.className	= 'TImage'
		this.isImage	= 1

		this.src	= src
		this.width	= (w) ? w : 0
		this.height	= (h) ? h : 0
		this.borderW	= 0
		this.alt	= ''
		
		this.imgmap	= ''

		this.anchor	= ''

		if (arguments.length > 3)
		{
			this.style = new TStyle()
			this.style.setBox(x, y, w, h)
		}

		this.toString	= TImage_toString
	}


	function TImage_toString()
	{
		var szBuf = '<img src="' + this.src + '"'

		if (this.width > 0) szBuf += ' width="' + this.width + '"'
		if (this.height > 0) szBuf += ' height="' + this.height + '"'

		szBuf += ' border="' + this.borderW + '"'

		if (this.imgmap) szBuf += ' USEMAP="#' + this.imgmap + '"'
		if (this.alt) szBuf += ' ALT="' + this.alt + '"'

		if (this.style) szBuf += this.style.getDef()
			
		szBuf += '>'

		if (this.anchor) szBuf = this.anchor.toString(szBuf)

		return szBuf
	}





/*
	TAnchor Class
--------------------------------------------------------------------------------------------*/

	function TAnchor(href, target, useOnClick, disabled)
	{
		this.className	= 'TAnchor'
		this.isAnchor	= 1

		this.href	= href
		this.target	= (target) ? target : ''
		this.useOnClick	= (useOnClick) ? 1 : 0
		this.enabled	= (disabled) ? 0 : 1
		
		this.toString	= TAnchor_toString
	}


	function TAnchor_toString(contents)
	{
		var szBuf = ''

		if (this.enabled) {

			szBuf = '<a'
			if (this.useOnClick) 
				szBuf += ' href="#" onclick="' + this.href + '"'
			else 
				szBuf += ' href="' + this.href + '"' + ((this.target) ? (' target="' + this.target + '"') : '')

			szBuf += '>'

			if (contents) szBuf += contents + '</a>'

		} else if (contents) 
			
			szBuf = contents

		return szBuf
	}





/*
	TFrame event handler
============================================================================================
*/


	function objectClick(e, obj)
	{

		if (e.clientX)	// opera, msie, ns
		{
			var x = e.clientX
			var y = e.clientY
		}

		else if (e.offsetX) // msie
		{
			var x = e.offsetX + obj.rect.min.x
			var y = e.offsetY + obj.rect.min.y
		}

		else if (e.layerX)	// ns
		{
			var x = e.layerX + obj.rect.min.x
			var y = e.layerY + obj.rect.min.y
		}


		if (!isNaN(x) && !isNaN(y) && (obj.onClickHandler))
			obj.onClickHandler(new TPoint(x, y))
	}











/*
============================================================================================
============================================================================================

 	Map classes

	Class definitions:	TMap
				TProjectMap	(inherits from TMap)
				TLocatorMap	(inherits from TMap)
				TMapObject	(inherits from TFrame)

============================================================================================
============================================================================================
*/



/*
	TMap Class
============================================================================================
*/

	function TMap(varName, aWidth, aHeight, numTilesX, numTilesY, aX, aY)
	{
		this.className	  = 'TMap'
		this.isMap	  = 1

		this.varName	  = (varName) ? varName : ''
		this.extent	  = new TRect(0,0,0,0,1)

		var oX 		  = (aX) ? aX : 0
		var oY 		  = (aY) ? aY : 0

		this.frame	  = new TFrame(oX, oY, aWidth, aHeight, (varName) ? varName + '.frame' : '')
		this.frame.setClickHandler(this.varName)
		this.onClickHandler	= ''
		
		with(this.frame.style)
		{
			setBackground('#FFFFFF')
			setClip()
			overflow = 'hidden'
		}

		this.tilesX = (numTilesX) ? numTilesX : 1
		this.tilesY = (numTilesY) ? numTilesY : this.tilesX

		this.tiles	= new Array()
		var tW = Math.round(aWidth / this.tilesX)
		var tH = Math.round(aHeight / this.tilesY)
		
		// img coordinates are RELATIVE to frame's aX, aY
		for (var r = 0; r < this.tilesY; r++)
		{
			this.tiles[r] = new Array()
			for (var c = 0; c < this.tilesX; c++)
				this.tiles[r][c] = new TImage('', tW, tH, (c * tW), ((this.tilesY - r - 1) * tH))
		}

		this.drawList		= new Array()
	
		this.toString		= TMap_toString
		this.pix2geo		= TMap_pix2geo
		this.geo2pix		= TMap_geo2pix
	}


	function TMap_toString()
	{
		var szBuf = ''
		for (var r = this.tilesY - 1; r >= 0; r--)
			for (var c = 0; c < this.tilesX; c++)
				szBuf += this.tiles[r][c].toString()

		var szInnerObj = ''
		var szOuterObj = ''

		for (var ct = 0; ct < this.drawList.length; ct++)
		{
			if (this.drawList[ct].isMapObject)
				this.drawList[ct].update(this)
			if (this.drawList[ct].drawInner)
				szInnerObj += this.drawList[ct] + '\n'
			  else
				szOuterObj += this.drawList[ct] + '\n'
		}


		szBuf += szInnerObj

		this.frame.contents = szBuf
		szBuf = this.frame.toString()

		szBuf += szOuterObj
		return szBuf
	
	}

	// graphic to geographic coordinate conversion
	function TMap_pix2geo(obj)
	{
		return obj.translate(this.frame.rect, this.extent)
	}



	// geographic to graphic coordinate conversion
	function TMap_geo2pix(obj)
	{
		return obj.translate(this.extent, this.frame.rect)
	}
	



/*
	TProjectMap Class
============================================================================================
*/

	function TProjectMap(aProject, varName, aWidth, aHeight, numTilesX, numTilesY, aX, aY)
	{
		this.inherited			= TMap
		this.inherited(varName, aWidth, aHeight, numTilesX, numTilesY, aX, aY)

		this.className			= 'TProjectMap'
		this.isProjectMap		= 1

		this.project			= aProject

		this.zoomLevel			= 0
		this.theme			= 0
		this.tool			= 100
		this.imagemap			= -1
		this.availImagemaps		= new Array()
		
		var p = Project.extent.centroid()
		this.center			= p.clone()

		this.llTile			= new TPoint(0, 0)
		this.includes			= ''

		this.update			= TProjectMap_update
		this.gotoXY			= TProjectMap_gotoXY
		this.moveMap			= TProjectMap_moveMap
		this.panMap			= TProjectMap_panMap
		this.setZoomLevel		= TProjectMap_setZoomLevel
		this.setTool			= TProjectMap_setTool
		this.setTheme			= TProjectMap_setTheme
		this.toString			= TProjectMap_toString

		this.frame.map			= this
		this.onClickHandler		= TProjectMap_mapClick

		this.statusChanged		= ''
		this.defaultTool		= ''
		this.defaultTheme		= ''

		this.update()

	}



	function TProjectMap_moveMap(deltay, deltax)
	{
  		this.gotoXY(this.center.x + deltax, this.center.y + deltay)
	}



	function TProjectMap_panMap(deltarows, deltacols)
	{
		var z = this.project.zoomLevels.get(this.zoomLevel)
		if (z) this.moveMap(deltarows * z.geoHeight, deltacols * z.geoWidth)
	}


	function TProjectMap_gotoXY(cX, cY, newlev)
	{
 		var oldTool	= this.tool
 		var oldTheme	= this.theme
		var oldZoom 	= this.zoomLevel

		this.center.moveTo(cX, cY)

		if (arguments.length > 2) this.setZoomLevel(newlev, 1)

		this.update(this.zoomLevel != oldZoom, this.theme != oldTheme, this.tool != oldTool, 1)

	}


	function TProjectMap_setZoomLevel(newlev, disableEvents)
	{
		var oldZoom 	= this.zoomLevel
 		var oldTool	= this.tool
 		var oldTheme	= this.theme

  		this.zoomLevel = normalizeNumber(Math.round(newlev), 0, this.project.zoomLevels.items.length - 1)

		this.setTheme(this.theme, 1)
		this.setTool(this.tool, 1)

		if (!disableEvents)
			this.update(this.zoomLevel != oldZoom, this.theme != oldTheme, this.tool != oldTool, 0)
	}



	function TProjectMap_setTheme(newTheme, disableEvents)
	{
		var oldTheme = this.theme
		this.theme = normalizeNumber(newTheme, 0, this.project.themes.items.length - 1)

		var t = this.project.themes.get(this.theme)

	 	if (!t || !t.isAvailable(this.zoomLevel))
		{
			if (this.defaultTheme) 
				this.theme = this.defaultTheme()
			  else
				this.theme = this.project.themes.firstAvail(this.zoomLevel)
		}

		if (!disableEvents) this.update(0,(oldTheme != this.theme),0,0)
	}



	function TProjectMap_setTool(newTool, disableEvents)
	{
 		this.tool = newTool
		var im

		if (	((this.tool < 100) && (!(im = this.project.imagemaps.get(this.tool)) || !im.isAvailable(this.zoomLevel)))  ||
			((this.tool == 100) && (this.zoomLevel == this.project.zoomLevels.items.length - 1)) ||
			((this.tool == 101) && (this.zoomLevel == 0))
		   )
			
			if (this.defaultTool)
		 		this.tool = this.defaultTool()
			  else
			  {	if (this.zoomLevel > 0)
					this.tool = 101
				else if (this.zoomLevel < this.project.zoomLevels.items.length - 1)
					this.tool = 100
				else
					this.tool = 999
			  }

		if (this.tool < 100) this.imagemap = this.tool
		if (!(im = this.project.imagemaps.get(this.imagemap)) || !im.isAvailable(this.zoomLevel))
		{
			this.imagemap = -1
			if (this.availImagemaps.length > 0) this.imagemap = this.availImagemaps[0]
		}

 		if (!disableEvents) this.update(0,0,1,0)
	}



	function TProjectMap_mapClick(p)
	{
		p = this.pix2geo(p)

		if (this.tool == 100)	// zoom IN
			this.gotoXY(p.x, p.y, this.zoomLevel + 1)
		
		else if (this.tool == 101) 	// zoom OUT
			this.gotoXY(p.x, p.y, this.zoomLevel - 1)
	}



	function TProjectMap_update(cZoom, cTheme, cTool, cPosition)
	{

		var z = this.project.zoomLevels.get(this.zoomLevel)
		if (!z)	return 0

		this.availImagemaps.length = 0
		for(var ct = 0; ct < this.project.imagemaps.items.length; ct++)
			if (this.project.imagemaps.items[ct].isAvailable(this.zoomLevel))
				this.availImagemaps[this.availImagemaps.length] = ct

		if (arguments.length < 4) cPosition	= 1
		if (arguments.length < 3) cTool		= 1
		if (arguments.length < 2) cTheme	= 1
		if (arguments.length < 1) cZoom		= 1

		var dw = this.tilesX * z.geoWidth / 2
		var dh = this.tilesY * z.geoHeight / 2


		this.llTile.assign(z.getTiles(this.center))

		if (this.tilesX % 2)
			this.llTile.x -= (this.tilesX - 1) / 2
		else
		{
			this.llTile.x -= (this.tilesX - 2) / 2
			if (this.center.x < z.extent.min.x + (this.llTile.x - 0.5) * z.geoWidth) this.llTile.x -= 1
		}

		if (this.tilesY % 2) 
			this.llTile.y -= (this.tilesY - 1) / 2
		else
		{
			this.llTile.y -= (this.tilesY - 2) / 2
			if (this.center.y < z.extent.min.y + (this.llTile.y - 0.5) * z.geoHeight) this.llTile.y -= 1
		}
		

		this.center.moveTo(	z.extent.min.x + (this.llTile.x - 1) * z.geoWidth  + dw, 
					z.extent.min.y + (this.llTile.y - 1) * z.geoHeight + dh)

		this.extent.moveTo(	this.center.x - dw,
					this.center.y - dh,
					this.center.x + dw,
					this.center.y + dh)


		if (arguments.length == 4)
		{
			if (this.statusChanged)
				this.statusChanged(cZoom, cTheme, cTool, cPosition)
			else
				for (var ct = 0; ct < frames.length; ct++) 
					frames[ct].document.location.reload()
		}
		
		return true
	}


	function TProjectMap_toString()
	{
		this.includes = ''

		var t = this.project.themes.get(this.theme)
		if (this.tool < 100)
		{
			var im = this.project.imagemaps.get(this.tool)
			this.frame.anchor.enabled = 0
		}
		else if (this.tool != 999)
			this.frame.anchor.enabled = 1
	
		for (var r = 0; r < this.tilesY; r++)
			for (var c = 0; c < this.tilesX; c++)
			{
				this.tiles[r][c].src = t.getTileURL(this.llTile.y + r, this.llTile.x + c, this.zoomLevel)
				if ((this.tool < 100) && (im))
				{
					if (this.tiles[r][c].imgmap = im.getTileName(this.llTile.y + r, this.llTile.x + c, this.zoomLevel))
						this.includes +='<script language="JavaScript" src="' + 
							im.getTileURL(this.llTile.y + r, this.llTile.x + c, this.zoomLevel) +
							'"></script>' + '\n'
				}
				else this.tiles[r][c].imgmap = ''
			}


		this.inherited = TMap_toString
		return this.inherited() + this.includes
	}






/*
	TLocatorMap Class
============================================================================================
*/

	function TLocatorMap(aProjectMap, varName, aWidth, aHeight, aImgSrc, aX, aY)
	{
		this.inherited			= TMap
		this.inherited(varName, aWidth, aHeight, 1, 1, aX, aY)

		this.className			= 'TLocatorMap'
		this.isLocatorMap		= 1

		this.lrect			= new TMapObject(new TRect(0,0,0,0))

		this.lrect.style.setBorder(1,'solid','red')
		this.lrect.drawInner		= 0

		this.map			= aProjectMap
		this.tiles[0][0].src 		= aImgSrc
		this.drawList[0]		= this.lrect

		this.extent.assign(this.map.project.extent)


		this.toString			= TLocatorMap_toString
		this.onClickHandler		= TLocatorMap_mapClick

		this.frame.map			= this.map
		this.frame.locator		= this
		this.frame.anchor.enabled	= 1
	}



	function TLocatorMap_mapClick(p)
	{
		p = this.pix2geo(p)
		this.map.gotoXY(p.x, p.y)
	}


	function TLocatorMap_toString()
	{
		this.lrect.obj.assign(this.map.extent)

		this.inherited = TMap_toString
		return this.inherited()
	}




/*
	TMapObject Class
============================================================================================
*/


	function TMapObject(obj, table, id, imgPath, imgW, imgH, varname, options)
	{
		this.inherited	= TFrame
		this.inherited(0,0,0,0, varname)

		this.className	= 'TMapObject'
		this.isMapObject = 1

		this.obj	= obj
		this.table	= table
		this.id		= id

		this.drawInner	= 0

		this.update	= TMapObject_update

		if (obj.isRect)
		{
			if (imgPath) this.style.setBackground('', imgPath)
		}
		
		else if (obj.isPoint)
		{
			this.contents   = new TImage(imgPath, imgW, imgH)
			this.ptOffset	= (options) ? options : 'cc'
		}
	}



	function TMapObject_update(map)
	{
		var newObj = map.geo2pix(this.obj)

		if (this.obj.isRect)
		{
			this.rect.assign(newObj)
			if (this.contents && this.contents.isImage)
			{
				this.contents.width	= this.rect.width
				this.contents.height	= this.rect.height
			}
		}

		else if (this.obj.isPoint)
		{
			var hx = this.contents.width	// 
			var hy = this.contents.height

			switch(this.ptOffset.substr(0,1)){
				case 'u': hy = 0; break;
				case 'c': hy = Math.floor(hy / 2); break;
			}

			switch(this.ptOffset.substr(1,1)){
				case 'l': hx = 0; break;
				case 'c': hx = Math.floor(hx / 2); break;
			}
			
			this.rect.moveTo(newObj.x - hx, newObj.y - hy)
		}

		this.updateBox()
	}





/*
============================================================================================
============================================================================================

 	JS-GIS project classes

	Class definitions:	TProject
				TZoomLevel
				TTileSet

				TObjArray
				TTileSetArray	(inherits from TObjArray)			

============================================================================================
============================================================================================
*/



/*
	TProject class
============================================================================================
*/

	function TProject(aName, minX, minY, apixWidth, apixHeight, aImgExt, aImgMapExt)
	{
      		this.name		= aName
		this.pixWidth 		= apixWidth
		this.pixHeight 		= apixHeight
		this.imgExt		= (aImgExt) ? aImgExt : '.png'
		this.imgmapExt		= (aImgMapExt) ? aImgMapExt : '.js'

		this.extent		= new TRect(minX, minY, minX + 9999999, minY + 9999999, 1)

		this.zoomLevels		= new TObjArray()
		this.themes		= new TTileSetArray()
		this.imagemaps		= new TTileSetArray()

		this.rootDir		= document.location.href.substring(0, document.location.href.lastIndexOf('/') + 1)
		this.mapDir  		= this.rootDir + 'maps/'
		this.nomap		= this.mapDir + 'nomap' + this.imgExt
		this.numWidth		= 3
		this.className		= 'TProject'
		this.version		= '2'

		this.addZoomLevel	= TProject_addZoomLevel
		this.addTheme		= TProject_addTheme
		this.addImagemap	= TProject_addImagemap
	}

	function TProject_addZoomLevel(aName, aFolder, ageoWidth, ageoHeight, anumTilesX, anumTilesY, aScale)
	{
		this.zoomLevels.items[this.zoomLevels.items.length] = new TZoomLevel(this, aName, aFolder, ageoWidth, ageoHeight, anumTilesX, anumTilesY, aScale)
		this.extent.resize(	Math.min(this.extent.width, ageoWidth * anumTilesX), 
					Math.min(this.extent.height, ageoHeight * anumTilesY))
	}



	function TProject_addTheme(aName, aFolder, aLevelList, aDescription)
	{
		this.themes.items[this.themes.items.length] = new TTileSet(this, aName, aFolder, aLevelList, aDescription, this.imgExt)
	}



	function TProject_addImagemap(aName, aFolder, aLevelList, aDescription)
	{
		this.imagemaps.items[this.imagemaps.items.length] = new TTileSet(this, aName, aFolder, aLevelList, aDescription, this.imgmapExt)
	}





/*
	TZoomLevel class
============================================================================================
*/

	function TZoomLevel(aProject, aName, aFolder, ageoWidth, ageoHeight, anumTilesX, anumTilesY, aScale)
	{
		this.project		= aProject

      		this.name		= aName
		this.folder 		= aFolder
		this.geoWidth 		= ageoWidth
		this.geoHeight 		= ageoHeight
		this.zoom 		= ageoWidth
		this.numTilesX 		= anumTilesX
		this.numTilesY 		= anumTilesY
		this.scale		= aScale

		this.tiles		= new TRect(1, 1, anumTilesX + 1, anumTilesY + 1, 1)
		this.extent		= new TRect(aProject.extent.min.x, aProject.extent.min.y, 
					aProject.extent.min.x + anumTilesX * ageoWidth, 
					aProject.extent.min.y + anumTilesY * ageoHeight, 1)

		this.getTiles		= TZoomLevel_getTiles
		this.getPath		= TZoomLevel_getPath
	}


	function TZoomLevel_getTiles(obj)
	{
		if (obj.isPoint) 
		{
			obj = obj.translate(this.extent, this.tiles)
			obj.x = Math.floor(obj.x)
			obj.y = Math.floor(obj.y)
			return obj
		}

		else if (obj.isRect) 
		{
			min = this.getTiles(obj.min)
			max = this.getTiles(obj.max)
			return new TRect(min.x, min.y, max.x, max.y, 1)
		}
	}



	function TZoomLevel_getPath()
	{
 		return this.project.mapDir + this.folder + "/"
	}





/*
	TTileSet class
============================================================================================
*/

	function TTileSet(aProject, aName, aFolder, aLevelList, aDescription, aExtension)
	{
		this.project		= aProject

      		this.name		= aName
		this.folder 		= aFolder
		this.levelList 		= aLevelList
		this.description	= aDescription

		this.extension		= aExtension
		this.prefix		= 'tile'

		this.getPath		= TTileSet_getPath
		this.isAvailable	= TTileSet_isAvailable
		this.getTileURL		= TTileSet_getTileURL
		this.getTileName	= TTileSet_getTileName
		this.noTile		= TTileSet_noTile

	}


	function TTileSet_isAvailable(zoom)
	{
		return (this.levelList.indexOf(zoom) >= 0)
	}


	function TTileSet_getPath(zoom)
	{
 		var z = this.project.zoomLevels.get(zoom)
		if (z) 
			return z.getPath() + this.folder + '/'
		  else
			return ''
	}


	function TTileSet_getTileURL(row, col, zoom)
	{
		var n = this.getTileName(row, col, zoom)

		if (!n) return this.noTile()

		return this.getPath(zoom) + n + this.extension
	}


	function TTileSet_getTileName(row, col, zoom)
	{
		var z = this.project.zoomLevels.get(zoom)

		if (!z || !this.isAvailable(zoom) || (col < 1) || (col > z.numTilesX) || (row < 1) || (row > z.numTilesY))
			return ''
		  else
			return this.prefix + leadingZero(row, this.project.numWidth) + leadingZero(col, this.project.numWidth)
	}


	function TTileSet_noTile()
	{
		return this.project.nomap
	}





/*
	TObjArray
============================================================================================
*/

	function TObjArray()
	{
		this.items	= new Array()
		this.get	= TObjArray_get
	}


	function TObjArray_get(id)
	{
		if ((!isNaN(id)) && (id >= 0) && (id < this.items.length))
			return this.items[id]
		  else
		{
			id = (id + '').toLowerCase()
			for (ct = 0; ct < this.items.length; ct++)
				if ((this.items[ct].name) && (this.items[ct].name.toLowerCase() == id))
					return this.items[id]

			return ''
		}
	}



/*
	TTileSetArray
============================================================================================
*/

	function TTileSetArray()
	{
		this.inherited	= TObjArray
		this.inherited()

		this.firstAvail = TTileSetArray_firstAvail
	}



	function TTileSetArray_firstAvail(zoom)
	{
		for(i = 0; i <= this.items.length; i++) 
			if (this.items[i].isAvailable(zoom)) 
				return i
		return -1
	}







/*
============================================================================================
============================================================================================

	Globals

============================================================================================
============================================================================================
*/


	function normalizeNumber(n, aMin, aMax)
	{
		return (n < aMin) ? aMin : ((n > aMax) ? aMax : n)
	}



	function leadingZero(n, width)
	{
		var s = n.toString()
		while(s.length < width) s = '0' + s

		return s
		
	}