if (window.PPlaceType) {
	PPlaceType.SCHOOL.id = 42;

	var P_PLACETYPE_NJ_RENTAL_MARKETS_ID = 55;
	PPlaceType.NJ_RENTAL_MARKETS = new PPlaceType(P_PLACETYPE_NJ_RENTAL_MARKETS_ID,"NJ Rental Markets", "boundary_type_njrentalmarkets");

	PPlaceTypeConfig.containmentOrder = [ PPlaceType.COUNTRY, PPlaceType.STATE, PPlaceType.CDBG, PPlaceType.CONGRESSIONAL, PPlaceType.CBSA_07, PPlaceType.COUNTY,
	                                      PPlaceType.MD, PPlaceType.COUNTY_SUBDIVISION, PPlaceType.SLD_UPPER, PPlaceType.SLD_LOWER, PPlaceType.CITY, PPlaceType.SCHOOL, 
					      PPlaceType.ZIP, PPlaceType.CENSUSTRACT, PPlaceType.BLOCKGROUP ];

	function comparePlaceType(placeTypeLeft, placeTypeRight){
		var containmentOrder = PPlaceTypeConfig.containmentOrder;
		var leftIndex;
		var rightIndex;
		for(var i=0;i<containmentOrder.length;i++){
			if(containmentOrder[i].id == placeTypeLeft.id)
				leftIndex = i;
			if(containmentOrder[i].id == placeTypeRight.id)
				rightIndex = i;
		}
		if(leftIndex>rightIndex)
			return 1;
		else if(rightIndex>leftIndex)
			return -1;
		else if(rightIndex != null && rightIndex == leftIndex)
			return 0;
		else
			return null;

	}
	
	function orderPlaceTypes(placeTypes){
		var containmentOrder = PPlaceTypeConfig.containmentOrder;
		var sortedPlaceTypes = new Array();
		
		for(var i=0; i<containmentOrder.length; i++){
			for(var j=0; j<placeTypes.length; j++){
				if(containmentOrder[i] == placeTypes[j] && sortedPlaceTypes[sortedPlaceTypes.length-1] != placeTypes[j])
					sortedPlaceTypes.push(placeTypes[j]);
			}
		}
		return sortedPlaceTypes;
	}
	
	PPlaceType.COUNTRY.pluralName = "Countries";
	PPlaceType.COUNTY.pluralName = "Counties";
	PPlaceType.CITY.pluralName = "Cities";
	PPlaceType.ZIP.pluralName = "Zip Codes";

	PPlaceType.prototype.getPluralName = function(){
		if(this.pluralName)
			return this.pluralName;
		else
			return this.getName() + "s"
	}

// ***************************
// *** Class: PPlaceLoader ***
// ***************************
	PPlaceLoader = function() {}
	PPlaceLoader.serviceUrl = PEnvironment.placeUrl;

	PPlaceLoader.load = function(ids,callback) {
		var idstring = ids.join(",");
		PAsync2.call(this.serviceUrl + '/pid/'+ idstring + '.ppjs?key=FCB88D4AA99A82D014260C9D34AC82F6', callback);
	}

	PPlaceLoader.loadByPath = function(path, callback, parms ) {
		var _parms = "";
		if( parms ) {
			for( var i in parms ) {
				_parms += '&' + i + '=' + parms[i];
			}
		}
		var url = this.serviceUrl + '/' + path + '.ppjs?key=FCB88D4AA99A82D014260C9D34AC82F6' + _parms;
		PAsync2.call(url, callback);
	}

	/** This should go in the Pushpin API * */
	PWebUtil.parseQuery = function(q) {
		var ret = {};

		if (q==null) q = window.location.search;

		if (q.length>1) q = q.substring(1, q.length);
		else return ret;

		var pairs = q.split('&');
		var pair;
		for (var i=0; i<pairs.length; i++) {
			pair = pairs[i].split('=');
			ret[pair[0]] = decodeURI(pair[1]);
		}
		return ret;
	}

	POverlaySetUtil = {};
	POverlaySetUtil.toFilterMap = function(sets) {
		var retVal = {}
		var l = sets.length;
		var s;
		for (var i=0; i<l; i++) {
			s = sets[i];
			retVal[s.id] = s.filtergroups;
		}
	}
	POverlaySetUtil.toIdCsv = function(sets) {
		var retVal = [];
		var l = sets.length;
		for (var i=0; i<l; i++) {
			retVal.push(sets[i].id);
		}
		return retVal.join(',');
	}

	PFilterUtil = {};
	PFilterUtil.getUrlComponent = function(jFilters) {
		var filterString = "";
		for (var i in jFilters) {
			if (jFilters[i]) {
				for (var j in jFilters[i]) {
					var jFilters2 = jFilters[i][j];
					var l = jFilters2.length;
					for (var k=0; k<l; k++) {
						var jFilter = jFilters2[k];
						filterString += i + ";" + jFilter.name + ";" + jFilter.values.join(",") + ";" + jFilter.type + ";" + jFilter.valuesDisplay + ";" + jFilter.columnName + ";" + jFilter.columnType;
						filterString += ":";
					}
				}
			}
		}
		filterString = filterString.substring(0,filterString.length-1);
		return encodeURIComponent(filterString);
	}

	PFilterUtil.getFilterMap = function(urlcomp) {
		var decode = decodeURIComponent(urlcomp);
		var filtermap = {};
		var filters = decode.split(":");
		var l = filters.length;
		for (var i=0; i<l; i++) {
			var attribs = filters[i].split(";");
			var id = attribs[0];
			var name = attribs[1];
			if (!filtermap[id])
				filtermap[id] = {};
			if (!filtermap[id][name])
				filtermap[id][name] = [];
			var values = attribs[2].split(",");
			var filter = new Object();
			filter.name = name;
			filter.values = values;
			filter.type = attribs[3];
			filter.valuesDisplay = attribs[4];
			filter.columnName = attribs[5];
			filter.columnType = attribs[6];
			filtermap[id][name].push(filter);
		}
		return filtermap;
	}

// Overwrite PAsync with checks for IE url length problem
	PAsync.postUrl = 'rest/posturl.jsp';
	PAsync.call = function (url, o, f) {
		// validate
		if (o == null) throw 'cannot pass in null object';
		if (f == null) throw 'cannot pass in null object';

		// store state
		var retVal = this.createId();
		this.contexts[retVal] = [o, f];

		// call asynchronously via script
		var s = document.createElement('script');
		if (url.indexOf('?') >= 0) {
			url += '&callback=' + retVal;
		} else {
			url += '?callback=' + retVal;
		}

		if(!this.activeRequests)
			this.activeRequests = 1;
		else
			this.activeRequests++;

		if (window.ActiveXObject && url.length > 2000) {
			var urlsplit = url.split('?');
			url = urlsplit[0];
			var params = urlsplit[1];
			this.createPostRequest(url, params, s, retVal);
		} else {
			s.src = url;
			this.scripts[retVal] = s;
			// this.headobject[0].appendChild(s);
			document.body.appendChild(s);
		}
		return retVal;
	}
	PAsync.callPost = function(url, o, f) {
		// validate
		if (o == null) throw 'cannot pass in null object';
		if (f == null) throw 'cannot pass in null object';

		// store state
		var retVal = this.createId();
		this.contexts[retVal] = [o, f];

		// call asynchronously via script
		var s = document.createElement('script');
		if (url.indexOf('?') >= 0) {
			url += '&callback=' + retVal;
		} else {
			url += '?callback=' + retVal;
		}
		var urlsplit = url.split('?');
		url = urlsplit[0];
		var params = urlsplit[1];

		this.createPostRequest(url, params, s, retVal);

		return retVal;
	}
	PAsync.createPostRequest = function(url, params, s, id) {
		// set up http post request
		var http = PXmlHttp.create();
		if (url.match("http://")) {
			params += '&url=' + url;
			posturl = this.postUrl;
		} else
			posturl = url;

		if (http) {
			http.open("POST", posturl, true);
			http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			http.setRequestHeader("Content-length", params.length);
			http.setRequestHeader("Connection", "close");
			http.send(params);
			var async = this;
			http.onreadystatechange = function() {
				if (http.readyState == 4 && http.status == 200) {
					s.text = http.responseText;
					async.scripts[id] = s;
					// async.headobject[0].appendChild(s);
					document.body.appendChild(s);
				}
			}
		}
	}
	PAsync.callback = function(id, args) {
		var c = this.contexts[id];
		c[1].apply(c[0], args);
		this.contexts[id] = null;
		if(this.activeRequests)
			this.activeRequests--;

		// have to do this to avoid mem leak in IE
		PWebUtil.purge(this.scripts[id]);
		if (!this.scripts[id].text)
			document.body.removeChild(this.scripts[id]);
		// this.headobject[0].removeChild(this.scripts[id]);
		this.scripts[id] = null;
	}

// Same for PAsync2
	PAsync2.postUrl = 'rest/posturl.jsp';
	PAsync2.call = function (url, f) {
		// validate
		if (f == null) throw 'cannot pass in null object';
		// store state
		var retVal = this.createId();
		this.contexts[retVal] =  f;
		// call asynchronously via script
		var s = document.createElement('script');
		if (url.indexOf('?') >= 0) {
			url += '&';
		} else {
			url += '?';
		}
		url = url + 'callback=PAsync2.callback(' + retVal + ')';
		if (url.length > 2000) {
			var urlsplit = url.split('?');
			url = urlsplit[0];
			var params = urlsplit[1];
			this.createPostRequest(url, params, s, retVal);
		} else {
			s.src = url;
			this.scripts[retVal] = s;
			// this.headobject[0].appendChild(s);
			document.body.appendChild(s);
		}
		return retVal;
	}
	PAsync2.callPost = function (url, f) {
		// validate
		if (f == null) throw 'cannot pass in null object';
		// store state
		var retVal = this.createId();
		this.contexts[retVal] =  f;
		// call asynchronously via script
		var s = document.createElement('script');
		if (url.indexOf('?') >= 0) {
			url += '&';
		} else {
			url += '?';
		}
		url = url + 'callback=PAsync2.callback(' + retVal + ')';

		var urlsplit = url.split('?');
		url = urlsplit[0];
		var params = urlsplit[1];
		this.createPostRequest(url, params, s, retVal);

		return retVal;
	}
	PAsync2.createPostRequest = function(url, params, s, id) {
		// set up http post request
		var http = PXmlHttp.create();
		var posturl = "";
		if (url.match("http://")) {
			params += '&url=' + url;
			posturl = this.postUrl;
		} else {
			posturl = url;
		}

		if (http) {
			http.open("POST", posturl, true);
			http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			http.setRequestHeader("Content-length", params.length);
			http.setRequestHeader("Connection", "close");
			http.send(params);
			var async = this;
			http.onreadystatechange = function() {
				if (http.readyState == 4 && http.status == 200) {
					s.text = http.responseText;
					async.scripts[id] = s;
					// async.headobject[0].appendChild(s);
					document.body.appendChild(s);
				}
			}
		}
	}
	PAsync2.callback = function(id) {
		var c = this.contexts[id];
		PAsync2.contexts[id] = null;

		// have to do this to avoid mem leak in IE
		PWebUtil.purge(this.scripts[id]);
		if (!this.scripts[id].text)
			document.body.removeChild(this.scripts[id]);
		// this.headobject[0].removeChild(this.scripts[id]);
		this.scripts[id] = null;

		return c;
	}
	
	// Define the polygon icons for for map pages that don't include sites data.
	if(polyicon === undefined || lineicon === undefined || specicon === undefined) {
		var polyicon;
		var lineicon;
		var specicon;
		var poly_red;
	}

	// Object with point dataset id and polygon set id that goes with it, and
	// boundary definition id
	// If polygon set has no point set to go with it then polygon id is used where point set id would be
	// NEW: A specific color can now be applied to a polygon set to override the default blue color.
	// To do this, just add a 'color' attribute to a polygon set below. (ex: color:'FF0000')
	POverlaySet.PointAndPolygonSets = {};
	POverlaySet.PointAndPolygonSets[58750] = {polygonID:61100, bdid:76, icon:polyicon};
	POverlaySet.PointAndPolygonSets[75950] = {polygonID:76050, bdid:84, icon:polyicon};
	POverlaySet.PointAndPolygonSets[83400] = {polygonID:83450, bdid:118, icon:polyicon};
	POverlaySet.PointAndPolygonSets[85050] = {polygonID:85050, bdid:121, icon:polyicon};
	POverlaySet.PointAndPolygonSets[86900] = {polygonID:86950, bdid:124, icon:polyicon};
	POverlaySet.PointAndPolygonSets[87700] = {polygonID:87750, bdid:125, icon:lineicon};
	POverlaySet.PointAndPolygonSets[87800] = {polygonID:87850, bdid:126, icon:lineicon};
	POverlaySet.PointAndPolygonSets[87900] = {polygonID:87950, bdid:127, icon:lineicon};
	POverlaySet.PointAndPolygonSets[88000] = {polygonID:88050, bdid:128, icon:lineicon};
	POverlaySet.PointAndPolygonSets[88350] = {polygonID:88400, bdid:129, icon:lineicon};
	POverlaySet.PointAndPolygonSets[88450] = {polygonID:88500, bdid:130, icon:lineicon};
	POverlaySet.PointAndPolygonSets[88550] = {polygonID:88600, bdid:131, icon:lineicon};
	POverlaySet.PointAndPolygonSets[88650] = {polygonID:88700, bdid:132, icon:lineicon};
	POverlaySet.PointAndPolygonSets[88900] = {polygonID:88950, bdid:133, icon:lineicon};
	POverlaySet.PointAndPolygonSets[89000] = {polygonID:89050, bdid:134, icon:lineicon};
	POverlaySet.PointAndPolygonSets[89100] = {polygonID:89150, bdid:135, icon:lineicon};
	POverlaySet.PointAndPolygonSets[89300] = {polygonID:89300, bdid:136, icon:specicon};
	POverlaySet.PointAndPolygonSets[90000] = {polygonID:90000, bdid:137, icon:specicon};
	POverlaySet.PointAndPolygonSets[90100] = {polygonID:90100, bdid:138, icon:lineicon};
	POverlaySet.PointAndPolygonSets[90200] = {polygonID:90200, bdid:139, icon:specicon};
	POverlaySet.PointAndPolygonSets[90250] = {polygonID:90250, bdid:140, icon:lineicon};
	POverlaySet.PointAndPolygonSets[90300] = {polygonID:90300, bdid:141, icon:specicon};
	POverlaySet.PointAndPolygonSets[90450] = {polygonID:90450, bdid:142, icon:polyicon};
	POverlaySet.PointAndPolygonSets[90500] = {polygonID:90500, bdid:143, icon:polyicon};
	POverlaySet.PointAndPolygonSets[90550] = {polygonID:90550, bdid:144, icon:polyicon};
	POverlaySet.PointAndPolygonSets[91450] = {polygonID:91450, bdid:152, icon:polyicon};
	POverlaySet.PointAndPolygonSets[92050] = {polygonID:92050, bdid:153, icon:poly_red};
	POverlaySet.PointAndPolygonSets[92200] = {polygonID:92200, bdid:154, icon:polyicon};
	POverlaySet.PointAndPolygonSets[92900] = {polygonID:92900, bdid:155, icon:polyicon};
	POverlaySet.PointAndPolygonSets[93400] = {polygonID:93400, bdid:156, icon:polyicon};

// overwrite to check for polygon layer
	POverlaySet.prototype.setMarkersById = function() {
		if (this.map) {
			var bounds = this.map.getBounds();
			var sw = bounds.getSouthWest();
			var ne = bounds.getNorthEast();
			this.filtered = [];
			var set = this;
			var iconString = "";
			// prepare icon params if set
			if (this.icon != PIcon.DEFAULT)
				iconString = '&img=' + this.icon.image + '&shd=' + this.icon.shadow + '&isz=' + this.icon.iconSize + '&ssz=' + this.icon.shadowSize + '&ian=' + this.icon.iconAnchor + '&san=' + this.icon.shadowAnchor + '&iwa=' + this.icon.infoWindowAnchor;

			if (this.map && this.display) {
				var f = function(markers) {
					var pointpolysets = POverlaySet.PointAndPolygonSets;
					if (pointpolysets[this.id]) {
						set.renderServer();
					} else if (markers.length > 0 && !(markers[0] instanceof PMarker)) {
						set.serverPinCount = markers[0];
						set.renderServer();
					} else if (markers.length > 0 && set.map.getZoom() <= set.zoom) {
						set.serverPinCount = markers.length;
						set.renderServer();
					} else {
						set.removeServer();
						set.filtered = markers;
						set.refresh();
						set.serverOn = false;
					}
				};
				PAsync.call(PEnvironment.pinUrl + '/pin/js?cid=284&did=' + this.id + '&mnx=' + sw.lng() + '&mny=' + sw.lat() + '&mxx=' + ne.lng() + '&mxy=' + ne.lat() + '&mxp=' + this.pinmax + '&ftr=' + encodeURIComponent(this.createQuery()) + iconString, this, f);
			}
		}
	}

// overwrite to check for layer name length
	POverlaySet.prototype.renderServer = function(force) {
		var pointpolysets = POverlaySet.PointAndPolygonSets;
		if (this.id) {
			if (this.map) {
				var newQuery = this.createQuery();
				if (force || !(this.serverOn && this.oldQuery == newQuery) || (this.filtersbyicons.length > 0 && this.reloadfiltericons)) {
					this.removeServer();
					this.pinLayer = new PMapLayer();
					this.oldQuery = newQuery;

					var layername = "";
					
					// BEGIN OLD FILTER BY ICON CODE
					// This will be removed once the new color-code polygon functionality is ready.
					// check if there's filtering by icon
					if (this.filtersbyicons.length > 0) {
						// add polygons under
						if (pointpolysets[this.id])
							layername += "gpoly:bdid_" + pointpolysets[this.id].bdid + ":cid_284:ftr_,";
						
						var l = this.filtersbyicons.length;
						var l2 = this.filtericons.length;
						if (l2 < l)
							l = l2;
						
						// generate layername
						if (!this.onlyPolygonSet(this.id)) {
							for (var i=0; i<l; i++) {
								var iconString = this.encodeIconString(this.filtericons[i]);
								var dataString = 'cid=' + encodeURIComponent(284) + '&did=' + encodeURIComponent(this.id) + '&ftr=' + encodeURIComponent(this.createFilterQuery(this.filtersbyicons[i]));
								layername += 'pins_' + encodeURIComponent(iconString) + ':' + 'data_' + encodeURIComponent(dataString);
								if (i < l-1)
									layername += ":";
							}
						}
						
						// hack to turn on polygons and their points
						if (pointpolysets[this.id]) {
							layername += ",";
							for (var i=0; i<l; i++) {
								var iconString = this.encodeIconString(this.colorcodes[i]);
								var dataString = 'cid=' + encodeURIComponent(284) + '&did=' + encodeURIComponent(pointpolysets[this.id].polygonID) + '&ftr=' + encodeURIComponent(this.createFilterQuery(this.filtersbyicons[i]));
								layername += 'pins_' + encodeURIComponent(iconString) + ':' + 'data_' + encodeURIComponent(dataString);
								if (i < l-1)
									layername += ":";
							}
						}
						
						this.reloadfiltericons = false;
					} else {
						var encFilter = encodeURIComponent(this.oldQuery);
						if (pointpolysets[this.id])
							layername += 'gpoly:bdid_' + pointpolysets[this.id].bdid + ':cid_284:ftr_' + encodeURIComponent(this.createQuery(true)) + ',';
						
						if (!this.onlyPolygonSet(this.id)) {
							// generate layer name
							var iconString = this.encodeIconString(this.icon);
							var dataString = 'cid=' + encodeURIComponent(284) + '&did=' + encodeURIComponent(this.id) + '&ftr=' + encFilter;
							
							layername += 'pins_' + encodeURIComponent(iconString) + ':' + 'data_' + encodeURIComponent(dataString);
						}
						
						// hack to turn on polygons and their points
						if (pointpolysets[this.id]) {
							iconString = this.encodeIconString(pointpolysets[this.id].icon);
							dataString = 'cid=' + encodeURIComponent(284) + '&did=' + encodeURIComponent(pointpolysets[this.id].polygonID) + '&ftr=' + encFilter;
							layername += ',pins_' + encodeURIComponent(iconString) + ':' + 'data_' + encodeURIComponent(dataString);
						}
					}
					// END OLD FILTER BY ICON CODE
					
					
					/* New color-code polygon code. This functionality not yet active.
					if(this.filtersbyicons.length > 0) {
						// We're filtering by icon. (Color Code All was selected.)
						
						var l = this.filtersbyicons.length;
						var l2 = this.filtericons.length;
						if (l2 < l)
							l = l2;

						// Generate the color-coded polygon layers (if polygons exist).
						if(pointpolysets[this.id]) {
							// The actual lines of polygons are now color coded, instead of just
							// the icons. We can pass the the color we want to draw the polygon
							// as, but colors are handled on a layer by layer basis, so each group
							// of polygons of the same color are drawn together on their own layer.
							for(var i=0; i<l; i++) {
								// Check if a custom icon order (for colors) was set. If not,
								// use the default order stored in the polyicon set. The colors
								// and order stored in the polyicon set must remain in sync
								// with all other icon sets. If for some reason the polyicon
								// colors don't exist, the default polygon color will be used.
								var colorString = "";
								if(this.polycolorcodes !== undefined && this.polycolorcodes != null) {
									colorString = ":clr_" + this.polycolorcodes[i].color;
								} else if(polyicon.colors[i].color !== undefined && polyicon.colors[i].color != null) {
									colorString = ":clr_" + polyicon.colors[i].color;
								}
								// Filters for color-coded polygons are being applied by
								// Mapnik now, so build a Mapnik-specific filter query here.
								var filterString = encodeURIComponent(this.createFilterQueryMapnik(this.filtersbyicons[i]));
								layername += "gpoly:cid_284:bdid_" + pointpolysets[this.id].bdid + colorString + ":ftr_" + filterString + ",";
							}
						}

						// Generate the layer that contains all of the points.
						// Cycle through each pointset.
						for(var i=0; i<l; i++) {
							var filterString = this.createFilterQuery(this.filtersbyicons[i]);
							
							// No need to draw point icons if this set only contains polygons.
							if(!this.onlyPolygonSet(this.id)) {
								var iconString = this.encodeIconStringById(this.colorcodes[i]);
								layername += encodeURIComponent("pins_" + iconString + ":data_cid=284&did=" + this.id + "&ftr=" + filterString);
								if(pointpolysets[this.id]) {
									layername += ":";
								}
							}
							
							// If this pointset has a corresponding polygon
							// set, add the icons for the polygons as well.
							if(pointpolysets[this.id]) {
								// Color-coded polygons now use the polygon icon instead of
								// the point icon. Check if a custom icon order (for colors)
								// was set. If not, use the order stored in the applicable icon
								// set. If a custom icon order was specified for points in the
								// set, it also must be set for polygons or they won't match up.
								var picon;
								if(this.polycolorcodes !== undefined && this.polycolorcodes != null) {
									picon = this.polycolorcodes[i];
								} else {
									picon = pointpolysets[this.id].icon.colors[i];
								}
								iconString = this.encodeIconStringById(picon);
								layername += encodeURIComponent("pins_" + iconString + ":data_cid=284&did=" + pointpolysets[this.id].polygonID + "&ftr=" + filterString);
							}
							if(i < l-1) {
								layername += ":";
							}
						}

						this.reloadfiltericons = false;
					} else {
						// We're not color-coding the polygons, so just display
						// them on a single layer as we always have.
						
						var encFilter = encodeURIComponent(this.oldQuery);
						if (pointpolysets[this.id]) {
							// Only specify a polygon color if one has been set for this
							// polygon set. If no color is specified, the default is used.
							var colorString = "";
							if(pointpolysets[this.id].color !== undefined && pointpolysets[this.id].color != null) {
								colorString = ":clr_" + pointpolysets[this.id].color;
							}
							layername += 'gpoly:bdid_' + pointpolysets[this.id].bdid + colorString + ':cid_284:ftr_' + encodeURIComponent(this.createQuery(true)) + ',';
						}

						if (!this.onlyPolygonSet(this.id)) {
							// generate layer name
							var iconString = this.encodeIconStringById(this.icon);
							var dataString = 'cid=' + encodeURIComponent(284) + '&did=' + encodeURIComponent(this.id) + '&ftr=' + encFilter;

							layername += 'pins_' + encodeURIComponent(iconString) + ':' + 'data_' + encodeURIComponent(dataString);
						}

						// hack to turn on polygons and their points
						if (pointpolysets[this.id]) {
							iconString = this.encodeIconStringById(pointpolysets[this.id].icon);
							dataString = 'cid=' + encodeURIComponent(284) + '&did=' + encodeURIComponent(pointpolysets[this.id].polygonID) + '&ftr=' + encFilter;
							layername += ',pins_' + encodeURIComponent(iconString) + ':' + 'data_' + encodeURIComponent(dataString);
						}
					} */

					var set = this;
					function f(name) {

						// don't add dupes
						var checkDupe = true;
						var layers = set.map.mapType.getMapLayers();
						var l = layers.length;
						for (var i=0; i<l; i++) {
							if (layers[i].name == name) {
								checkDupe = false;
								break;
							}
						}

						if (checkDupe) {
							if (typeof loadDelayCheck != 'undefined')
								loadDelayCheck();
							set.pinLayer.name = name;
							set.pinLayer.title = 'donotshowinwidget';
							set.serverOn = true;
							set.map.mapType.addMapLayer(set.pinLayer);
							set.filtered = [];
							set.refresh();
						}
					}

					// check that browser is IE
					if (window.ActiveXObject && version < 8) {
						var maplayers = this.map.getCurrentMapType().getMapLayers();
						var l = maplayers.length;
						var layernames = layername;

						for (var i=0; i<l; i++)
							layernames += maplayers[i].name;

						// do special workaround if tile urls longer than IE can
						// handle
						if (layernames.length > 1800) {
							// check if special layer that actually has 3 layers
							var pointpolysets = POverlaySet.PointAndPolygonSets;
							if (pointpolysets[this.id]) {
								var morenames = layername.split(",");
								var lcids = "";
								PAsync2.call(PEnvironment.pinUrl + '/servlet/stringstore?str=' + morenames[0], function(id) {
									lcids += 'lcid_' + id;
									PAsync2.call(PEnvironment.pinUrl + '/servlet/stringstore?str=' + morenames[1], function(id) {
										lcids += ',lcid_' + id;
										PAsync2.call(PEnvironment.pinUrl + '/servlet/stringstore?str=' + morenames[2], function(id) {
											lcids += ',lcid_' + id;
											f(lcids);
										});
									});
								});
							} else {
								PAsync2.call(PEnvironment.pinUrl + '/servlet/stringstore?str=' + layername, function(id) {
									f('lcid_' + id);					
								});
							}
						} else
							f(layername);
					} else
						f(layername);

				} else
					this.map.refreshWidget(this.map.widgetOverlay);
			}
		}
	}

//	checks point and poly object to see if polygon set is connected to point set
	POverlaySet.prototype.onlyPolygonSet = function(id) {
		var ppset = POverlaySet.PointAndPolygonSets;
		if (ppset[id] && id == ppset[id].polygonID)
			return true;
		else
			return false;
	}

//	overwrite to check for colorcode
	POverlaySet.prototype.createQuery = function(poly) {
		var query = "";
		for (var i in this.filtergroups) {
			var filters = this.filtergroups[i];
			if (filters.length > 0) {
				query += "(";
				for (var j=0; j<filters.length; j++) {
					if (filters[j].values.length > 0) {
						if (poly)
							query += this.createFilterQueryMapnik(filters[j]);
						else
							query += this.createFilterQuery(filters[j]);
					}

					// OR across same filter
					if (j < filters.length-1)
						query += " or ";
				}

				// AND across different filters
				query += ") and ";
			}
		}
		var lastindex = query.lastIndexOf(' and');
		if (lastindex != -1)
			query = query.substring(0, lastindex);
		if (query.match("COLORCODE"))
			return "";
		else
			return query;
	}

// create special query for mapnik
	POverlaySet.prototype.createFilterQueryMapnik = function(filter) {
		var query = "";
		if (filter.columnType == PAttribute.NUMBER || filter.columnType == PAttribute.DATE) {
			if (filter.type == PAttributeFilter.RANGE) {
				var start = filter.values[0];
				var end = filter.values[1];

				if (filter.columnType == PAttribute.DATE) {
					start = "timestamp'" + filter.values[0].getFullYear()+'-'+filter.values[0].getMonth()+'-'+filter.values[0].getDate() + "'";
					end = "timestamp'" + filter.values[1].getFullYear()+'-'+filter.values[1].getMonth()+'-'+filter.values[1].getDate() + "'";
				}

				query += "(" + filter.columnName + " >= " + start + " and " + filter.columnName + " <= " + end + ")";
			} else if (filter.type == PAttributeFilter.EQUAL) {
				query += "(";
				for (var k=0; k < filter.values.length; k++) {
					var value = filter.values[k];
					if (filter.columnType == PAttribute.DATE)
						value = "timestamp'" + filter.values[k].getFullYear()+'-'+filter.values[k].getMonth()+'-'+filter.values[k].getDate() + "'"
						query += filter.columnName + " = " + value;
					if (k < filter.values.length-1)
						query += " or ";
				}
				query += ")";
			}
		} else if (filter.columnType == PAttribute.STRING) {
			for (var k=0; k < filter.values.length; k++) {
				query += filter.columnName + ".match('.*" + filter.values[k] + ".*')";
				if (k < filter.values.length-1)
					query += " or ";
			}
		} else if (filter.columnType == PAttribute.BOOLEAN) {
			query += filter.columnName + " = " + filter.values[0];
		}
		return query;
	}

	POverlaySet.prototype.encodeIconString = function(icon) {
		return 'img=' + encodeURIComponent(icon.image) + '&shd=' + encodeURIComponent(icon.shadow) + '&isz=' + encodeURIComponent(icon.iconSize) + '&ssz=' + encodeURIComponent(icon.shadowSize) + '&ian=' + encodeURIComponent(icon.iconAnchor) + '&san=' + encodeURIComponent(icon.shadowAnchor) + '&group=1';
	}
	
	POverlaySet.prototype.encodeIconStringById = function(icon) {
		return 'pinid=' + encodeURIComponent(icon.pinid) + '&shd=' + encodeURIComponent(icon.shadow) + '&isz=' + encodeURIComponent(icon.iconSize) + '&ssz=' + encodeURIComponent(icon.shadowSize) + '&ian=' + encodeURIComponent(icon.iconAnchor) + '&san=' + encodeURIComponent(icon.shadowAnchor) + '&group=1';
	}

// mva colorramps
	PColorRamp.MVA = [];
	PColorRamp.MVA[198887] = [new PColor('B89EE6'),new PColor('769EDE'),new PColor('8EB3E6'),new PColor('A5D3F2'),new PColor('FAFAB6'),new PColor('FAEC84'),new PColor('FFC87A')];
	PColorRamp.MVA[198870] = [new PColor('B89EE6'),new PColor('86ABE3'),new PColor('A2C0EB'),new PColor('FAFAB6'),new PColor('FFCE7A'),new PColor('F5BA6E')];
	PColorRamp.MVA[198853] = [new PColor('9D83C7'),new PColor('AF9BD1'),new PColor('86ABE3'),new PColor('A2C0EB'),new PColor('FAFAB6'),new PColor('FAEC84'),new PColor('FFCE7A'),new PColor('F5BA6E')];
	PColorRamp.MVA[196544] = [new PColor('B89EE6'),new PColor('A3C3F0'),new PColor('FAFAB6'),new PColor('FAEC84'),new PColor('FFC87A')];
	PColorRamp.MVA[196510] = [new PColor('B89EE6'),new PColor('86ABE3'),new PColor('A2C0EB'),new PColor('FAFAB6'),new PColor('FAEC84')];
	PColorRamp.MVA[196528] = [new PColor('9D83C7'),new PColor('AB8FDB'),new PColor('C0AAE6'),new PColor('FAFAB6'),new PColor('FAEC84'),new PColor('FFDA99'),new PColor('FCCA74'),new PColor('F7BB54')];
	PColorRamp.MVA[196126] = [new PColor('A3ABDF'),new PColor('B19AC8'),new PColor('DD91BF'),new PColor('F69B91'),new PColor('FEB984'),new PColor('FED990'),new PColor('FEF6A0'),new PColor('D6C19D'),new PColor('D6D69D'),new PColor('CCC5BE')];
	PColorRamp.MVA[196211] = [new PColor('7F40A8'),new PColor('C485FF'),new PColor('004DA8'),new PColor('5089DE'),new PColor('9ED0FF'),new PColor('E69800'),new PColor('FFD37F'),new PColor('FFFF73')];
	PColorRamp.MVA[196193] = [new PColor('AA66CD'),new PColor('005CE6'),new PColor('73B2FF'),new PColor('FFFF00'),new PColor('FFAA00'),new PColor('FF0000'),new PColor('CCC5BE')];
	PColorRamp.MVA[196570] = [new PColor('B89EE6'),new PColor('A3C3F0'),new PColor('FAFAB6'),new PColor('FAEC84'),new PColor('FFCE7A'),new PColor('F5BA6E'),new PColor('CCC5BE')];
	PColorRamp.MVA[196300] = [new PColor('4E4E4E'),new PColor('A900E6'),new PColor('7A8EF5'),new PColor('FFFFC9'),new PColor('F7F288'),new PColor('F0E478'),new PColor('FEAC00'),new PColor('E86B6B'),new PColor('CCC5BE')];
	PColorRamp.MVA[9609712] = [new PColor('9D83C7'),new PColor('AF9BD1'),new PColor('86ABE3'),new PColor('A2C0EB'),new PColor('FAFAB6'),new PColor('FAEC84'),new PColor('FFCE7A'),new PColor('F5BA6E')];
	PColorRamp.MVA[9627156] = [new PColor('5D4770'),new PColor('FFE6F4'),new PColor('FAFAB6')];
	PColorRamp.MVA[9627551] = [new PColor('a186be'),new PColor('ac92c4'),new PColor('c2add3'),new PColor('7eaddc'),new PColor('9dc1e6'),new PColor('fbf6b6'),new PColor('fde97f'),new PColor('fdc578'),new PColor('f9af91')];
	PColorRamp.MVA[9627506] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9627505] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9627501] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9629002] = [new PColor('800000'),new PColor('ff4000'),new PColor('ffa000'),new PColor('ffff00'),new PColor('40c080'),new PColor('2060ff'),new PColor('a040ff'),new PColor('ff4080'),new PColor('ffffff')];
	PColorRamp.MVA[9628952] = [new PColor('ffffff'),new PColor('ff4080'),new PColor('a040ff'),new PColor('2060ff'),new PColor('40c080'),new PColor('ffff00'),new PColor('ffa000'),new PColor('ff4000'),new PColor('800000')];
	PColorRamp.MVA[9629150] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9629155] = [new PColor('5D4770'),new PColor('9B839B'),new PColor('E6CCE0'),new PColor('FAFAB6')];
	PColorRamp.MVA[9629156] = [new PColor('5D4770'),new PColor('E6CCE0'),new PColor('FAFAB6')];
	PColorRamp.MVA[9629158] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9639604] = [new PColor('FFEBB0'),new PColor('FFC354'),new PColor('F5A300'),new PColor('C4DFF2'),new PColor('96C4F2'),new PColor('6F95C9'),new PColor('DFCCF0'),new PColor('C2A6E0'),new PColor('9278B3')];
	PColorRamp.MVA[9639751] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9639756] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9639757] = [new PColor('5D4770'),new PColor('FAFAB6')];
	PColorRamp.MVA[9844405] = [new PColor('9F87C7'),new PColor('C0AAE6'),new PColor('7DA3DE'),new PColor('8EB3E6'),new PColor('AFCAF0'),new PColor('FFFFC9'),new PColor('F5E380'),new PColor('F7B94D'),new PColor('F5A88C'),new PColor('D7C29E'),new PColor('C7D79E'),new PColor('CCC5BE'),new PColor('828282')];
	PColorRamp.MVA[9845403] = [new PColor('5D4770'),new PColor('FAFAB6')];

// rewrite to check for negative values in breaks and mvas
	PLegend.prototype.getColors = function() {
		var colorramp = [];
		var colors = [];

		if (this.indicator.nodata)
			colorramp.push(this.nodatacolor.getColor());

		// check for mvas
		if (this.indicator && (this.indicator.breakid == P_BREAKTYPE_EXACT_VALUE_ID || this.indicator.breakid == P_BREAKTYPE_EXACT_VALUE_CUSTOM_ID)) {
			if (PColorRamp.MVA[this.indicator.id])
				colors = PColorRamp.MVA[this.indicator.id];
			else
				colors = this.colorramp.getColors();
		} else {
			colors = this.colorramp.getColors();

			var negativecolors = [new PColor('F5D6B8'),new PColor('F5D4B0'),new PColor('F5CFA2'),new PColor('F7CE97'),new PColor('F7CA8B'),new PColor('F5C57D'),new PColor('F5C071'),new PColor('F5BD64')];
			var negativelength = negativecolors.length;
			var negativecount = 0;

			// count number of negative breaks
			if (this.getBreaks() != "") {
				var breaks = this.getBreaks().split(",");
				var l = breaks.length - 1;
				for (var i=0; i<l; i++) {
					if (breaks[i] < 0 && !(this.indicator.nodata && breaks[i] == this.indicator.nodata))
						negativecount++;
				}
			}

			// add negative colors from colorramp (darkest being most negative)
			for (var i=0; i<negativecount; i++) {
				var colorIndex =  (i*Math.floor(negativelength/negativecount))%negativelength + 1;
				if (i == negativecount-1)
					colorramp.push(negativecolors[0].getColor());
				else
					colorramp.push(negativecolors[negativelength-colorIndex].getColor());
			}

		}
		var numbreaks = this.getNumberOfBreaks() - colorramp.length;
		var colorslength = colors.length;
		if (this.indicator.nodata)
			numbreaks += 1;
		for (var i=0; i<numbreaks; i++) {
			var colorIndex =  (i*Math.floor(colorslength/numbreaks))%colorslength;
			if (i == numbreaks-1)
				colorramp.push(colors[colorslength-1].getColor());
			// fix for 5 positive breaks (makes the colors from light to dark be
			// 1,2,3,5,8 instead of 1,2,3,4,8)
			else if (numbreaks == 5 && i == 3)
				colorramp.push(colors[colorIndex+1].getColor());
			else
				colorramp.push(colors[colorIndex].getColor());
		}
		return colorramp;
	}

// add this to return if current boundary and scale is prerendered
	PLegend.prototype.isPrerenderedBoundary = function(indicator, indz) {
		if (!indicator)
			indicator = this.indicator;

		if (this.map && this.map.kamap && indicator) {
			var scale = this.map.kamap.getCurrentScale();
			var curIndPlaceType = null;
			var indPlaceTypes = indicator.getPlaceTypes();

			if( indPlaceTypes && indPlaceTypes.length && indicator.curPerIndex )
				curIndPlaceType = indPlaceTypes[indicator.curPerIndex]; 

			if ( indicator.maxscale && curIndPlaceType && scale > curIndPlaceType.maxScale )
				return true;
			else
				return false;
		}
		return false;
	}

// check if more than one place type exists for prerendered boundaries
	PLegend.prototype.getNumberOfAvailablePlaces = function() {
		if (this.map && this.map.kamap && this.indicator) {
			var places = this.indicator.getPlaceTypes();
			var placesl = places.length;
			var zooms = this.typeconfig.zooms;
			var l = zooms.length;
			var count = 0;
			for (var i=0; i<l; i++) {
				if (zooms[i]) {
					if (zooms[i].length) {
						for (var j=0; j<zooms[i].length; j++) {
							for (var k=0; k<placesl; k++) {
								if (zooms[i][j] == places[k])
									count++;
							}
						}
					} else {
						for (var k=0; k<placesl; k++) {
							if (zooms[i] == places[k])
								count++;
						}
					}
				}
			}
		}
		return count;
	}

//	overwrite to return default if prerendered boundary
	PLegend.prototype.getNumberOfBreaks = function() {
		var breaks = this.numbreaks;

		// need to get number of breaks from breaks string for exact values
		if (this.indicator != null && (this.indicator.breakid == P_BREAKTYPE_EXACT_VALUE_ID || this.indicator.breakid == P_BREAKTYPE_EXACT_VALUE_CUSTOM_ID)
			&& this.getBoundaryType() && this.indicator.breaks[this.indicator.curPerIndex][this.getBoundaryType().id])
			breaks = this.indicator.breaks[this.indicator.curPerIndex][this.getBoundaryType().id].length-1;
		else if (this.isPrerenderedBoundary() && this.getBoundaryType())
			breaks = DEFAULT_NUMBREAKS;

		return breaks;
	}

// overwrite to allow 2 breaks
	PLegend.prototype.setNumberOfBreaks = function(numbreaks) {
		if (numbreaks >= 2 && numbreaks <= 8) {
			this.numbreaks = numbreaks;
			this.redraw();
		}
	}

	PLegend.prototype.compareCustomBreaks = function(index, condition) {
		var retVal = false;
		if (this.indicator && this.indicator.custombreaks.length > 0) {
			var placetype = this.getBoundaryType();
			var numbreaks = this.getNumberOfBreaks();
			if (this.indicator.custombreaks[this.indicator.curPerIndex] && this.indicator.custombreaks[this.indicator.curPerIndex][placetype.id] && this.indicator.custombreaks[this.indicator.curPerIndex][placetype.id][numbreaks]) {
				if (this.indicator.nodata)
					index--;
				var custombreaks = this.indicator.custombreaks[this.indicator.curPerIndex][placetype.id][numbreaks].split(",");
				var breaks = this.indicator.breaks[this.indicator.curPerIndex][placetype.id][numbreaks].split(",");
				if (custombreaks[index] && breaks[index]) {
					if (condition == ">") {
						if (parseFloat(custombreaks[index]) > parseFloat(breaks[index]))
							retVal = true;
					} else if (condition == "<") {
						if (parseFloat(custombreaks[index]) < parseFloat(breaks[index]))
							retVal = true;
					}
				}
			}
		}
		return retVal;
	}

// create JSON object for current indicator's custom breaks
	PIndicator.prototype.createCustomBreaksJSON = function(numbreaks, curper) {
		var custombreaks = this.custombreaks;
		var obj = null;
		var i, perlen;
		if (curper) {
			i = this.curPerIndex;
			perlen = i+1;
		} else {
			i = 0;
			perlen = this.periodids.length;
		}
		var places = this.getPlaceTypes();
		var placeslen = places.length;
		var nb;
		if (numbreaks)
			nb = [numbreaks];
		else
			nb = [2,3,4,5,6,7,8];

		var breaksobj = {};
		var foundbreaks = false;
		for (i; i<perlen; i++) {
			var period = i;
			if (custombreaks[period]) {
				breaksobj[period] = {};
				for (var j=0; j<placeslen; j++) {
					var pid = places[j].id;
					if (custombreaks[period][pid]) {
						breaksobj[period][pid] = {};
						for (var k=0; k<nb.length; k++) {
							if (custombreaks[period][pid][nb[k]]) {
								breaksobj[period][pid][nb[k]] = custombreaks[period][pid][nb[k]];
								foundbreaks = true;
							}
						}
					}
				}
			}
		}
		if (foundbreaks)
			obj = breaksobj;
		return obj;
	}

// sets custom breaks from a json object
	PIndicator.prototype.setCustomBreaksByJSON = function(breaks) {
		var id = this.id;
		var per = this.periodids;
		var perlen = per.length;
		var places = this.getPlaceTypes();
		var placeslen = places.length;
		if (breaks && breaks[id]) {
			for (var i=0; i<perlen; i++) {
				if (breaks[id][i]) {
					var breaksper = breaks[id][i];
					if (!this.custombreaks[i])
						this.custombreaks[i] = [];
					for (var j=0; j<placeslen; j++) {
						var pid = places[j].id;
						if (breaksper[pid]) {
							var breaksplace = breaksper[pid];
							if (!this.custombreaks[i][pid])
								this.custombreaks[i][pid] = [];
							for (var k in breaksplace) {
								if (breaksplace[k])
									this.custombreaks[i][pid][k] = breaksplace[k];
							}
						}
					}
				}
			}
		}
	}

// Summing that returns null if any place has null (for custom regions)
	PCube.prototype.getSumOfPlacesNullCheck = function(ind,per,mean) {
		var indid;
		if (ind) indid = ind.id; else indid = this.indicators[0].id;
		var period;
		if (per) period = per; else period = this.periods[0];
		var sum = null;
		var placelength = 0;
		var l = this.places.length;
		for (var i=0; i<l; i++) {
			var value = this.values[period][this.places[i].id][indid];
			if (!(ind.nodata && value == ind.nodata) && value != null) {
				sum += value;
				placelength++;
			} else
				return null;
		}
		return sum;
	}

// Returns false if at least one place has null data.
/*
	PCube.prototype.hasCompleteData = function( indicators, periods, places ) { // parms
																				// serve
																				// as
																				// optional
																				// filters.
		var _places = ( places && places.constructor == Array ) ? places : this.places;
		var _indicators = ( indicators && indicators.constructor == Array ) ? indicators : this.indicators;
		var _periods = ( periods && periods.constructor == Array ) ? periods : this.periods;
		for( var i = 0; i < _places.length; ++i ) {
			for( var j = 0; j < this.places.length; ++j ) {
				if( _places[i].id == this.places[j].id ) {
					var place = this.places[j];
					for( var k = 0; k < _indicators.length; ++k ) {
						for( var l = 0; l < this.indicators.length; ++l ) {
							if( _indicators[k].id == this.indicators[l].id ) {
								var indicator = this.indicators[l];
								for( var m = 0; m < _periods.length; ++m ) {
									for( var n = 0; n < this.periods.length; ++n ) {
										if( _periods[m] == this.periods[n] ) {
											var period = this.periods[n];
											var value = this.values[period][place.id][indicator.id];
											if( value === null || value == indicator.nodata ) {
												return false;
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return true;
	}
*/

// Number formatter
	PWebUtil.formatNumber = function(value, unit, dec) {
		if (value == null || isNaN(value) || value == Infinity || value == -9999)
			return "N/A";
		else if (dec || dec == 0)
			value = Number(value).toFixed(dec);
		else
			value = Math.round(value*100)/100;

		var neg = false;
		if (value < 0)
			neg = true;

		value = value.toString();

		// special case for indicators that have years as the value, in which case we don't want a comma in the value
		// just need to return the value with no unit
		if (unit == "year_") {
			return value;
		}

		var x = value.split('.');
		var x1 = x[0];
		var x2 = x.length > 1 ? x[1] : '0';
		if (unit && unit == "$_" && x2.length == 1)
			x2 += "0";

		var rgx = /(\d+)(\d{3})/;
		while (rgx.test(x1))
			x1 = x1.replace(rgx, '$1' + ',' + '$2');
		value = x1;
		if (dec)
			value += "." + x2;
		else if (parseInt(x2, 10) != 0)
			value += "." + x2.substr(0,2);

		if (unit && unit != "") {
			var fix = unit.split("_");
			if (fix[0] != "") {
				value = fix[0] + value;

				// if value is negative than put minus sign in front of unit
				if (neg == true) {
					var arr = value.split("-");
					value = "-" + arr[0] + arr[1];
				}
			}

			if (fix[1] != "")
				value += fix[1];

		}

		return value;
	}

	/***************************************************************************
	 * ******** Functions below can be removed once API is fixed and released
	 * *********
	 **************************************************************************/
// create query per filter (api not lowercased queries)
	POverlaySet.prototype.createFilterQuery = function(filter) {
		var query = "";
		if (filter.columnType == PAttribute.NUMBER || filter.columnType == PAttribute.DATE) {
			if (filter.type == PAttributeFilter.RANGE) {
				var start = filter.values[0];
				var end = filter.values[1];

				if (filter.columnType == PAttribute.DATE) {
					start = "timestamp'" + filter.values[0].getFullYear()+'-'+filter.values[0].getMonth()+'-'+filter.values[0].getDate() + "'";
					end = "timestamp'" + filter.values[1].getFullYear()+'-'+filter.values[1].getMonth()+'-'+filter.values[1].getDate() + "'";
				}

				query += filter.columnName + " between " + start + " and " + end;
			} else if (filter.type == PAttributeFilter.EQUAL) {
				query += filter.columnName + " in (";
				for (var k=0; k < filter.values.length; k++) {
					var value = filter.values[k];
					if (filter.columnType == PAttribute.DATE)
						value = "timestamp'" + filter.values[k].getFullYear()+'-'+filter.values[k].getMonth()+'-'+filter.values[k].getDate() + "'"
						query += value;
					if (k < filter.values.length-1)
						query += ",";
				}
				query += ")";
			}
		} else if (filter.columnType == PAttribute.STRING) {
			var pct = "";
			if (filter.type == PAttributeFilter.RANGE)
				pct = "%25";
			for (var k=0; k < filter.values.length; k++) {
				query += filter.columnName + " ilike '" + pct + filter.values[k] + pct + "'";
				if (k < filter.values.length-1)
					query += " or ";
			}
		} else if (filter.columnType == PAttribute.BOOLEAN) {
			query += filter.columnName + " is " + filter.values[0];
		}
		return query;
	}

	PInfoWindow.prototype.updateTab = function(i) {
		var labelDiv = this.tabDivs[i].firstChild.nextSibling;
		if (i == this.selectedTab) {
			addPNGBackground(this.tabDivs[i].firstChild,this.map.kamap.server + "images/tab_front.png");
			labelDiv.innerHTML = "<b>" + this.tabs[i].label + "</b>";
			this.tabDivs[i].style.zIndex = this.tabs.length + 1;
		} else {
			addPNGBackground(this.tabDivs[i].firstChild,this.map.kamap.server + "images/tab_back.png");
			labelDiv.innerHTML = "<a href='#' onclick='return false;'>" + this.tabs[i].label + "</a>";
			this.tabDivs[i].style.zIndex = this.tabs.length - i;
		}
	}

	PInfoWindow.prototype.createTabs = function() {
		for (var i=0; i<this.tabs.length; i++) {
			var tabDivMain = document.createElement('div');
			tabDivMain.style.position = "absolute";
			tabDivMain.style.top = "0px";
			tabDivMain.style.height = "49px";
			tabDivMain.style.width = "103px";
			tabDivMain.style.left = "0px";

			var tabDiv = document.createElement('div');
			var tabLabel = document.createElement('div');
			tabDiv.style.position = "absolute";
			tabDiv.style.width = "103px";
			// tabDiv.style.overflow = "hidden"
			tabDiv.style.height = "49px";
			tabDiv.style.left = "0px";
			tabDiv.style.top = "0px";
			tabDiv.style.textAlign = "center";

			tabLabel.style.position = "absolute";
			tabLabel.style.top = "0px";
			tabLabel.style.left = "0px";
			tabLabel.style.textAlign = "center";
			tabLabel.style.width = "103px"; // Need to set a smaller width, but
											// need to find a way to keep div
											// centered
			tabLabel.style.cursor = "pointer";
			tabLabel.style.paddingTop = "4px";

			tabDivMain.appendChild(tabDiv);
			tabDivMain.appendChild(tabLabel);
			this.tabDivs.push(tabDivMain);

			this.updateTab(i);
			this.tabsWidth += parseInt(tabDiv.style.width);
			if (i < this.tabs.length-1)
				this.tabsWidth -= this.tabOverlap;

			tabLabel.infowin = this;
			tabLabel.index = i;
			this.listeners.push(PEvent.addListener(tabLabel, 'click', function() { this.infowin.selectTab(this.index); }));
		}
		this.tabsWidth -= this.nwDiff;
	}

// added this.create() to redraw the infowindow when a tab is selected
	PInfoWindow.prototype.selectTab = function(index) {
		if (index < this.tabs.length)
			this.selectedTab = index;
		this.updateTabs();
		this.updateInfoDiv();
		this.create();
	}

	// Fix so bubble opens touching the icon
	// Needed to rewrite this function because it doesn't check for serverside
	// markers when doing the adjustment
	// A better fix needs to be added to the api, this one i hardcoded some
	// values
	PInfoWindow.prototype.adjustBubble = function() {
	        // if there's a marker offset the shadow
        	var offsetX = 0;
	        var offsetY = 0;
        	var shadowOffsetX = 0;
	        var shadowOffsetY = 0;
	        if (this.marker || this.map.getOverlaySets().length > 0) {
        	        if (this.marker)
                	        var icon = this.marker.icon;
	                else
        	                var icon = this.map.getOverlaySets()[0].icon

	                offsetX = icon.infoWindowAnchor.x - icon.iconAnchor.x;
        	        offsetY = icon.infoWindowAnchor.y - icon.iconAnchor.y;

/*
 * shadowOffsetX = icon.shadowAnchor.x - icon.infoWindowAnchor.x; if
 * (icon.shadowSize) shadowOffsetX += icon.shadowSize.width*0.5; shadowOffsetY =
 * icon.infoWindowAnchor.y - icon.shadowAnchor.y; if (icon.shadowSize)
 * shadowOffsetY += icon.shadowSize.height*0.5;
 */
			// Was having difficulty gettin the shadow to go in the right place,
			// so hardcoded the offset values
			shadowOffsetX = -5;
			shadowOffsetY = 5;
	        }
        	this.bubbleDiv.xOffset = -parseInt(this.pointer.style.left) + offsetX;
	        this.bubbleDiv.yOffset = -this.getHeight() + offsetY;
        	this.shadowDiv.xOffset = -parseInt(this.s_pointer.style.left) + shadowOffsetX;
	        this.shadowDiv.yOffset = -(parseInt(this.s_c.style.height) + parseInt(this.s_pointer.style.height) + parseInt(this.s_n.style.height)) + shadowOffsetY;
	}

	/***************************************************************************
	 * ********* End functions that can be removed once API is fixed and
	 * released ************
	 **************************************************************************/

}

// copying original identify markers function from api to extend it
// TODO: recode the function once we release these api patches
// overwrite to include hack for polygon points
PMap.prototype.identifyMarker0 = function(latlng, callback, pinmax) {
// TODO: the setting of pinmax will only happen localy.. might need fixing
	if (!(pinmax || pinmax == 0))
		var pinmax = 100;		
	var datasetids = "";
	var filters = "";
	var bufferString = "";
	for (var i=0; i<this.overlaySets.length; i++) {
		var overlaySet = this.overlaySets[i];
		// get overlay sets with ids that are on and filters if they exist
		if (overlaySet.display && overlaySet.id) {
			datasetids += overlaySet.id + ",";
			var icon = overlaySet.icon;

			// build buffer extents
			var pixels = this.fromLatLngToDivPixel(latlng);

			// these values make a little adjustment to the positioning of the bufferstring so the center of the bufferstring is the center of the click
			var adjustx = 1;
			var adjusty = 2;

			var zoom = map.getZoom();
			var extendx = 0;
			var extendy = 0;

			// Kluge :: At close range zooms sometimes clicks on sites were not registering as a click on a site.
			// So this increases the buffer string incrementally for close range zooms to send a larger area to find the site.
			if (zoom > 10) {
				extendx = (zoom-10)*0.4;
				extendy = (zoom-10)*0.4;
			}

			var minxy = this.fromDivPixelToLatLng(new PPoint(pixels.x - icon.iconAnchor.x - adjustx - extendx, pixels.y + icon.iconAnchor.y + adjusty + extendy));
			var maxxy = this.fromDivPixelToLatLng(new PPoint(pixels.x + icon.iconSize.width - icon.iconAnchor.x - adjustx + extendx, pixels.y - icon.iconSize.height + icon.iconAnchor.y + adjusty - extendy));

			bufferString += minxy.lng() + "," + minxy.lat() + "," + maxxy.lng() + "," + maxxy.lat() + ";";
			filters += encodeURIComponent(overlaySet.createQuery()) + "/,";

			// hack for polygon point set
			var pointpolysets = POverlaySet.PointAndPolygonSets;
			if (pointpolysets[overlaySet.id]) {
				// The polygon id and regular overlay set id are the same when adding just a polygon set
				// So don't add overlayset id again when only a polygon set is being added since the id has already been added to datasetids just above
				if (overlaySet.id != pointpolysets[overlaySet.id].polygonID)
					datasetids += pointpolysets[overlaySet.id].polygonID + ",";
				filters += encodeURIComponent(overlaySet.createQuery()) + "/,";
				bufferString += minxy.lng() + "," + minxy.lat() + "," + maxxy.lng() + "," + maxxy.lat() + ";";
			}
		}
	}
	var datasetidsIndex = datasetids.lastIndexOf(',');
	var filtersIndex = filters.lastIndexOf('/,');
	var bufferIndex = bufferString.lastIndexOf(';');
	if (datasetidsIndex != -1)
		datasetids = datasetids.substring(0, datasetidsIndex);
	if (filtersIndex != -1)
		filters = filters.substring(0, filtersIndex);
	if (bufferIndex != -1)
		bufferString = bufferString.substring(0, bufferIndex);

	var buffer = this.getScale() / 50000000;

	// indicator later as well
	if (datasetids != "")
		PAsync.call(PEnvironment.pinUrl + '/pin/js?cid=284&did='+ datasetids + '&ftr=' + filters + '&lat=' + latlng.lat()+ '&lng=' + latlng.lng() + '&buf=' + bufferString + '&mxp=' + pinmax, this, callback);
	// var version = PGetVersion() == "1.3" ? 1.4 : PGetVersion();
// PAsync.call(PEnvironment.pinUrl + '/' + version + '/point.ppjs?cid=284&did='+
// datasetids + '&ftr=' + filters + '&lat=' + latlng.lat()+ '&lng=' +
// latlng.lng() + '&buf=' + bufferString + '&mxp=' + pinmax + (typeof
// P_SIGNATURE == 'undefined' ? '' : '&signature=' + P_SIGNATURE), this,
// callback);
}



PMap.prototype.identifyMarker = function(context) {
	var that = this;
	var f = function(markers){
		context.markers = markers;

		// optimize for the case where we're only showing marker result
		// if a marker is found (i.e. don't query places + indicator)
		if (context.options.nonmixedresult && markers && markers.length > 0) {
			// end chain
			that.identifyEnd(context);
		} else {
			// continue chain
			that.callNext(context);
		}
	};
	// get original function to the real work
	this.identifyMarker0(context.latlng, f, context.options.pinmax);
}

PMap.prototype.setLegendMerger = function(legendMerger) {
	this.legendMerger = legendMerger;
}

PMap.prototype.getLegendMerger = function() {
	return this.legendMerger;
}

PMap.prototype.identify = function(latlng, callback, options) {
	// keep track of active identifications (used for handling double click)
	this.identifyCount++;
	// console.log(this.identifyCount, this.cancelIdentify, "(identify)");

	var context = { 
			"latlng": latlng, 
			"callback": callback, 
			opreatorindex: 0, 
			"options": options, 
			"indicator": this.getIndicator(),
			"legendMerger": this.getLegendMerger()};

	var that = this;

	// create chain of operators (each is responsible for continuing the chain
	// of execution - i.e. calling the next in the chain)
	var ops = []
	           context.ops = ops;
	context.opsIndex = 0;
	var overlaySets = this.getOverlaySets();
	if (options.showmarker && overlaySets && overlaySets.length > 0 ) 
		ops.push( function(){that.identifyMarker(context)} );
	if (options.showplaces || (options.showindicator && this.getIndicator())) 
		ops.push( function(){that.identifyPlaces(context)} );
	if (options.showindicator && (this.getIndicator() || ( this.getLegendMerger() && this.getLegendMerger().getIndicatorCount() > 0))) 
		ops.push( function(){that.identifyIndicator(context)} );
	ops.push( function(){that.identifyEnd(context)} );

	// start the chain
	this.callNext(context);
}

PMap.prototype.callNext = function(context) {
	if (this.cancelIdentify) {
		this.identifyCancel(context);
	} else {
		var ops = context.ops;
		var i = context.opsIndex;
		if (i < ops.length) {
			ops[i](context); 
			context.opsIndex++;
		}
	}
}

PMap.prototype.identifyPlaces = function(context) {
	var opt = context.options;
	var that = this;
	var f = function(places) { 
		context.places = places;
		// continue chain
		that.callNext(context)
	}
	var geocoder = new PClientGeocoder();

	
	if(opt.boundaryTypes && opt.boundaryTypes.length){
		// find place for each boundary type, using point rather than
		// containments.
		geocoder.getPlaceContaining(context.latlng, opt.boundaryTypes, f);
	}else{
		var placeType;
		if (opt.uptotype)
			placeType = opt.uptotype;
		else
			placeType = PPlaceTypeConfig.PPLACECONTAINMENT.getTypeByAbsoluteZoom(16);
	
		// find places through hierarchy of containment
		geocoder.getPlaceContainment( context.latlng, placeType, f );
	}
}

PMap.prototype.identifyIndicator = function(context) {
	var indicator = context.indicator;
	var legendMerger = context.legendMerger;
	var period = indicator ? indicator.getPeriod() : null;
	var cube = null;
	if(indicator){
		cube = new PCube([indicator],context.places,[period]);
	} else if(legendMerger) {
		var indicators = [];
		var periods = [];
		for(var i=0;i<3; i++){
			if(legendMerger.getLegend(i).getIndicator()){
				indicators[indicators.length] = legendMerger.getLegend(i).getIndicator();
				periods[periods.length] = legendMerger.getLegend(i).getIndicator().getPeriod();
			}
		}
		if(indicators.length > 0)
			cube = new PCube(indicators, context.places, periods);
	}
	context.cube = cube;
	var that = this;
	
	cube.loadValues(function(values) {
		cube.values = values;
		context.values = values;

		// continue chain
		that.callNext(context);
	});
}

/**
 * This function merely flips the identify cancelation flag. It sets the flag to
 * true to indicate to the other asynchronous processes that they should abort
 * their identification process (e.g. displaying info bubbles, etc). If the flag
 * is true, then it identifies do not complete.
 */
PMap.prototype.identifyCancel = function(context) {
	/**
	 * Counter is used to handle multiple asynchronous identifies that must be
	 * aborted (i.e. due to double click in browsers actually calling 3
	 * different events: click, click, doubleclick (in that order). Cancellation
	 * isn't turned off (cancelIdentify isn't made false) until all identifies
	 * have been cancelled.
	 */
	this.identifyCount--;	
	// console.log(this.identifyCount, this.cancelIdentify, "(cancel)");

	if (this.identifyCount == 0) {
		this.cancelIdentify = false;
		// console.log(this.cancelIdentify, "(cancel)");
	}
}

PMap.prototype.cancelIdentify = false;
PMap.prototype.identifyCount = 0;

PMap.prototype.identifyEnd = function(context) {
	this.identifyCount = 0;
	this.cancelIdentify = false;
	// console.log(this.identifyCount, this.cancelIdentify, "(end)");
	context.callback(context.markers, context.places, context.cube);
}

// checks for matching year (quarters included)
PIndicator.prototype.hasYear = function(year) {
	var l = this.periods.length;
	for (var i=0; i<l; i++) {
		if (this.periods[i].match(year))
			return true;
	}
	return false;
}

// gets the latest quarter available
PIndicator.prototype.getQuarter = function(year) {
	var l = this.periods.length;
	var qtr = 0;
	for (var i=0; i<l; i++) {
		if (this.periods[i].match(year)) {
			var q = this.periods[i].split("q");
			if (q[1] && q[1] > qtr)
				qtr = q[1];
		}
	}
	if (qtr == 0)
		return null;
	else
		return year + "q" + qtr;
}

// gets the latest month available
PIndicator.prototype.getMonth = function(year) {
	var l = this.periods.length;
	var month = 0;
	for (var i=0; i<l; i++) {
		if (this.periods[i].match(year)) {
			var m = this.periods[i].split("-");
			if (m[1] && m[1] > month) {
				month = m[1];
				break;
			}
		}
	}
	if (month == 0)
		return null;
	else
		return year + "-" + month;
}

// gets data directory string used for link
PIndicator.prototype.getDataDirectory = function() {
	if (this.dataDirectory)
		return this.dataDirectory.replace(/'/,"%27");
	else 
		return "";
}

// Kluge for Claritas data that needs different short labels for different periods
// TODO: Make indicators be able to have multiple short labels if necessary, like they do with long labels and descriptions.
PIndicator.prototype.getLabel = function() {
	if (this.getSource().indexOf("Claritas") != -1) {
		if (this.getPeriod() == "2000")
			return this.displayName.replace(/Projected/,"");
		else if (this.getPeriod() == "2009")
			return this.displayName.replace(/Projected/,"Estimated");
	}
	return this.displayName; 
}

kaToolCustomizer.prototype.ondblclick = function(e)
{
	if (this._ondblclick)
		this._ondblclick.apply(this, [e]);
	if (this.pmap && this.pmap.enableDblClickZoom && this.pmap.getScale(this.pmap.getZoom()+1)) {
		var pos = this.katool.getMousePosition(e);
		var aPixPos = this.katool.adjustPixPosition(pos[0], pos[1]);
		var geo = this.kaMap.pixToGeo(aPixPos[0],aPixPos[1]);
		this.kaMap.zoomTo(geo[0], geo[1], this.pmap.getScale(this.pmap.getZoom()+1));
		this.pmap.triggerEvent('dblclick', e);
	} else {
		if (this.pmap)
			this.pmap.triggerEvent('dblclick', e);
		else if (this._ondblclick) this._ondblclick.apply(this, [e]);
		this.katool.ondblclick.apply(this.katool, [e]);
	}
}

PPolygon.prototype.getArea = function(proj) {
	if( this.area ) {
		return this.area;
	} else {
		this.area = 0.0;
	}
	var pointCount = this.getVertexCount();
	var proj = (proj) ? proj : new PProjection(54004);
	var area = 0.0;
	if( pointCount >= 3 ) {
		var p0 = proj.fromLatLngToMeters(this.getVertex(0));
		var cross,p1,p2,x1,y1,x2,y2;
		for( var i = 1; i + 1 < pointCount; ++i ) {
			p1 = proj.fromLatLngToMeters(this.getVertex(i));
			p2 = proj.fromLatLngToMeters(this.getVertex(i+1));
			x1 = p1.x - p0.x;
			y1 = p1.y - p0.y;
			x2 = p2.x - p0.x;
			y2 = p2.y - p0.y;
			cross = ( x1 * y2 ) - ( x2 * y1 );
			area += cross;
		}
	}
	this.area = Math.abs( area / 2.0 );
	return this.area;
}


PPlace.prototype.getPlacesWithinPage = function(placetype, callback, overlap, maxResults, order, quota, context, offset, query) {
	new PClientGeocoder().getPlacesWithinPage(this, placetype, callback, overlap, maxResults, order, quota, context, offset, query);
}
PPlace.prototype.getPlacesWithinCSV = function(placetype, overlap, order, quota, query) {
	new PClientGeocoder().getPlacesWithinCSV(this, placetype, overlap, order, quota, query);
}
PPlace.prototype.getPlacesWithin = function(placetype, callback, overlap, maxResults, order, quota) {
	new PClientGeocoder().getPlacesWithin(this, placetype, callback, overlap, maxResults, order, quota);
}
PPlace.prototype.getPlacesWithinCount = function(placetype, callback, overlap, quota, context) {
	new PClientGeocoder().getPlacesWithinCount(this, placetype, callback, overlap, quota, context);
}
PPlace.prototype.getPlacesContaining = function(placetype, callback, overlap, maxResults, order, quota) {
	new PClientGeocoder().getPlacesContaining(this, placetype, callback, overlap, maxResults, order, quota);
}
PPlace.prototype.getPlacesContainingCount = function(placetype, callback, overlap, quota) {
	new PClientGeocoder().getPlacesContainingCount(this, placetype, callback, overlap, quota);
}
PPlace.prototype.getPlacesOverlapping = function( placetype, callback, quota, maxResults, order ) {
	new PClientGeocoder().getPlacesOverlapping( this, placetype, callback, quota, maxResults, order );
}
PPlace.prototype.getPlacesOverlappingCount = function( placetype, callback, quota ) {
	new PClientGeocoder().getPlacesOverlappingCount( this, placetype, callback, quota );
}
PPolygon.prototype.getPlacesWithinPage = function(placetype, callback, overlap, maxResults, order, quota, context, offset, query) {
	new PClientGeocoder().getPlacesWithinPage(this.points, placetype, callback, overlap, maxResults, order, quota, context, offset, query);
}
PPolygon.prototype.getPlacesWithinCSV = function(placetype, overlap, order, quota, query) {
	new PClientGeocoder().getPlacesWithinCSV(this.points, placetype, overlap, order, quota, query);
}
PPolygon.prototype.getPlacesWithin = function(placetype, callback, overlap, maxResults, order, quota) {
	new PClientGeocoder().getPlacesWithin(this.points, placetype, callback, overlap, maxResults, order, quota);
}
PPolygon.prototype.getPlacesWithinCount = function(placetype, callback, overlap, quota, context) {
	new PClientGeocoder().getPlacesWithinCount(this.points, placetype, callback, overlap, quota, context);
}
PPolygon.prototype.getPlacesContaining = function(placetype, callback, overlap, maxResults, order, quota) {
	new PClientGeocoder().getPlacesContaining(this.points, placetype, callback, overlap, maxResults, order,quota);
}
PPolygon.prototype.getPlacesContainingCount = function(placetype, callback, overlap, quota) {
	new PClientGeocoder().getPlacesContainingCount(this.points, placetype, callback, overlap, quota);
}
PPolygon.prototype.getPlacesOverlapping = function( placetype, callback, quota, maxResults, order ) {
	new PClientGeocoder().getPlacesOverlapping( this, placetype, callback, quota, maxResults, order );
}
PPolygon.prototype.getPlacesOverlappingCount = function( placetype, callback, quota ) {
	new PClientGeocoder().getPlacesOverlappingCount( this, placetype, callback, quota );
}
PLatLngBounds.prototype.getPlacesWithinPage = function(placetype, callback, overlap, maxResults, order, quota, context, offset, query) {
	new PClientGeocoder().getPlacesWithinPage(this, placetype, callback, overlap, maxResults, order, quota, context, offset, query);
}
PLatLngBounds.prototype.getPlacesWithinCSV = function(placetype, overlap, order, quota, query) {
	new PClientGeocoder().getPlacesWithinCSV(this, placetype, overlap, order, quota, query);
}
PLatLngBounds.prototype.getPlacesWithin = function(placetype, callback, overlap, maxResults, order, quota ) {
	new PClientGeocoder().getPlacesWithin(this, placetype, callback, overlap, maxResults, order, quota );
}
PLatLngBounds.prototype.getPlacesWithinCount = function(placetype, callback, overlap, quota ) {
	new PClientGeocoder().getPlacesWithinCount(this, placetype, callback, overlap, quota);
}
PLatLngBounds.prototype.getPlacesContaining = function( placetype, callback, overlap, maxResults, order, quota ) {
	new PClientGeocoder().getPlacesContaining(this, placetype, callback, overlap, maxResults, order, quota );
}
PLatLngBounds.prototype.getPlacesContainingCount = function( placetype, callback, overlap, quota ) {
	new PClientGeocoder().getPlacesContainingCount(this, placetype, callback, overlap, quota );
}
PLatLngBounds.prototype.getPlacesOverlapping = function( placetype, callback, quota, maxResults, order, quota ) {
	new PClientGeocoder().getPlacesOverlapping( this, placetype, callback, quota, maxResults, order, quota );
}
PLatLngBounds.prototype.getPlacesOverlappingCount = function( placetype, callback, quota, quota ) {
	new PClientGeocoder().getPlacesOverlappingCount( this, placetype, callback, quota, quota );
}

PClientGeocoder.prototype.getPlacesWithinPage = function(bounds, placetype, callback, overlap, maxResults, order, quota, context, offset, query) {
	this.getPlacesQueryPage("wit", bounds, placetype, callback, overlap, maxResults, order, quota, context, offset, query);
}
PClientGeocoder.prototype.getPlacesWithinCSV = function(bounds, placetype, overlap, order, quota, query) {
	this.getPlacesQueryCSV("wit", bounds, placetype, overlap, order, quota, query);
}
PClientGeocoder.prototype.getPlacesWithin = function(bounds, placetype, callback, overlap, maxResults, order, quota) {
	this.getPlacesQuery("wit", bounds, placetype, callback, overlap, maxResults, order, quota);
}
PClientGeocoder.prototype.getPlacesWithinCount = function(bounds, placetype, callback, overlap, quota, context) {
	this.getPlacesQuery("wit", bounds, placetype, callback, overlap, null, null, quota, true, context);
}

PClientGeocoder.prototype.getPlacesContaining = function(bounds, placetype, callback, overlap, maxResults, order, quota) {
	this.getPlacesQuery("ctg", bounds, placetype, callback, overlap, maxResults, order, quota);
}
PClientGeocoder.prototype.getPlacesContainingCount = function(bounds, placetype, callback, overlap, quota) {
	this.getPlacesQuery("ctg", bounds, placetype, callback, overlap, null, null, quota, true);
}

PClientGeocoder.prototype.getPlacesOverlapping = function( bounds, placetype, callback, quota, maxResults, order ) {
	this.getPlacesQuery("olp", bounds, placetype, callback, overlap, maxResults, order, quota);
}
PClientGeocoder.prototype.getPlacesOverlappingCount = function( bounds, placetype, callback, order ) {
	this.getPlacesQuery("olp", bounds, placetype, callback, overlap, null, null, quota, true);
}
/* should be rewritten to accept an options obj */
PClientGeocoder.prototype.getPlacesQuery = function(action, bounds, placetype, callback, overlap, maxResults, order, quota, count, context, offset, query) {
	query = query || "";
	query += "&" + this.getBoundsQuery(bounds);
	query += "&pti=" + placetype.id;
	if( quota == undefined ) {
		var olap = true;
		if (arguments.length > 3 && overlap != undefined)
			olap = overlap;
		query += "&olp=" + olap;
	}
	if (maxResults)
		query += "&max=" + maxResults;
	if (order)
		query += "&ord=" + order;
	if (quota != undefined && quota != null)
		query += "&qta=" + quota;
	if (count != undefined && count != null)
		query += "&cnt=1";

	var callbackContext = context || this;
	PAsync.call(PEnvironment.placeUrl + '/boundary/js?act=' + action + query + "&key=" + this.key, callbackContext, callback);
}

PClientGeocoder.prototype.getPlacesQueryPage = function(action, bounds, placetype, callback, overlap, maxResults, order, quota, context, offset, query) {
	query = query || "";
	query += "&fmt=json";
	query += "&" + this.getBoundsQuery(bounds);
	query += "&pti=" + placetype.id;
	if( quota == undefined ) {
		var olap = true;
		if (arguments.length > 3 && overlap != undefined)
			olap = overlap;
		query += "&olp=" + olap;
	}
	if (maxResults)
		query += "&max=" + maxResults;
	if (order)
		query += "&ord=" + order;
	if (quota != undefined && quota != null)
		query += "&qta=" + quota;
	if (offset != undefined && offset != null)
		query += "&off=" + offset
	var callbackContext = context || this;
	PAsync2.call(PEnvironment.placeUrl + '/boundary/js?act=' + action + query + "&key=" + this.key, callback);
}

PClientGeocoder.prototype.getPlacesQueryCSV = function(action, bounds, placetype, overlap, order, quota, query) {
	query = query || "";
	query += "&fmt=csv";
	query += "&" + this.getBoundsQuery(bounds);
	query += "&pti=" + placetype.id;
	if( quota == undefined ) {
		var olap = true;
		if (overlap != undefined)
			olap = overlap;
		query += "&olp=" + olap;
	}
	if (order)
		query += "&ord=" + order;
	if (quota != undefined && quota != null)
		query += "&qta=" + quota;
	window.location = PEnvironment.placeUrl + '/boundary/js?act=' + action + query + "&key=" + this.key;
}

PPlace.prototype.getAggregateCount = function( placeType, callback, woverlap, wquota, wcontext ) {
	var self = this;
	self.getPlacesWithinCount( placeType, function( placesWithinCount ) {
		callback( placesWithinCount );
	}, woverlap, wquota, wcontext );
}

PPolygon.prototype.getAggregateCount = function( placeType, callback, woverlap, wquota, wcontext ) {
	var self = this;
	self.getPlacesWithinCount( placeType, function( placesWithinCount ) {
		callback( placesWithinCount );
	}, woverlap, wquota, wcontext );
}

PPlace.prototype.getAggregates = function( placeType, callback, woverlap, wmax, worder, wquota, coverlap, cmax, corder, cquota ) {
	var self = this;
	if( self.getType() == placeType ) {
		callback( [] );
	} else {
		self.getPlacesWithin( placeType, function( placesWithin ) {
			if( placesWithin.length > 0 ) {
				callback( placesWithin );			
			} else {
				self.getPlacesContaining( placeType, function( placesContaining ) {
					callback( placesContaining );
				}, coverlap, cmax, corder, cquota );
			}
		}, woverlap, wmax, worder, wquota );
	}
}

PPolygon.prototype.getAggregates = function( placeType, callback, woverlap, wmax, worder, wquota, coverlap, cmax, corder, cquota ) {
	var self = this;
	self.getPlacesWithin( placeType, function( placesWithin ) {
		if( placesWithin.length > 0 ) {
			callback( placesWithin );
		} else {
			self.getPlacesContaining( placeType, function( placesContaining ) {
				callback( placesContaining );
			}, coverlap, cmax, corder, cquota );
		}
	}, woverlap, wmax, worder, wquota );
}

PLatLngBounds.prototype.getAggregates = function( placeType, callback, woverlap, wmax, worder, wquota, coverlap, cmax, corder, cquota ) {
	var self = this;
	self.getPlacesWithin( placeType, function( placesWithin ) {
		if( placesWithin.length > 0 ) {
			callback( placesWithin );
		} else {
			self.getPlacesContaining( placeType, function( placesContaining ) {
				callback( placesContaining );
			}, coverlap, cmax, corder, cquota );
		}
	}, woverlap, wmax, worder, wquota );
}

PMap.prototype.setCenterBoundsAndGetZoomLevelByViewPort = function( poly, width, height ) {
	var bounds = ( poly instanceof PLatLngBounds ) ? poly : poly.getBounds(); 

	var oldWidth = this.kamap.viewportWidth;
	var oldHeight = this.kamap.viewportHeight;
	this.kamap.viewportWidth = width;
	this.kamap.viewportHeight = height;
	this.setCenterBounds( bounds );
	var zoomLevel = this.getZoom();
	this.kamap.viewportWidth = oldWidth;
	this.kamap.viewportHeight = oldHeight;
	this.setCenterBounds( bounds );
	return zoomLevel; 
}

/**
 * Refreshes and redraws this chart
 */
PChart.prototype.refresh = function() {
	this.clear();

	var labels = [];
	var data = [];
	// get xaxis var
	var xVar = [];
	if (this.options.xAxis == PChartOptions.PLACE)
		xVar = this.cube.places;
	else if (this.options.xAxis == PChartOptions.INDICATOR)
		xVar = this.cube.indicators;
	else
		xVar = this.cube.periods;

	// get grouped var
	var groupVar = [];
	if (this.options.group == PChartOptions.PLACE)
		groupVar = this.cube.places;
	else if (this.options.group == PChartOptions.INDICATOR)
		groupVar = this.cube.indicators;
	else
		groupVar = this.cube.periods;

	// deduce remaining var
	var rVar = [];
	if ((this.options.xAxis == PChartOptions.PLACE && this.options.group == PChartOptions.INDICATOR) || (this.options.xAxis == PChartOptions.INDICATOR && this.options.group == PChartOptions.PLACE))
		rVar = this.cube.periods;
	else if ((this.options.xAxis == PChartOptions.PLACE && this.options.group == PChartOptions.PERIOD) || (this.options.xAxis == PChartOptions.PERIOD && this.options.group == PChartOptions.PLACE))
		rVar = this.cube.indicators;
	else
		rVar = this.cube.places;

	// loop through x vars and label
	if (this.options.displayXAxisLabel) {
		var l = xVar.length;
		for (var i=0; i<l; i++) {
			// set x labels
			var label = "";
			if (xVar[i] instanceof PPlace)
				label = this.getPlaceLabel(xVar[i]);
			else if (xVar[i] instanceof PIndicator)
				label = this.getIndicatorLabel(xVar[i]);
			else
				label = xVar[i];
			var xticks = {v: i, label: label};
			labels.push(xticks);
		}
		if (l == 1) {
			labels.push({v: 1, label: ""});
		}
	}

	// loop through all vars
	var periodid = null;
	var placeid = null;
	var indid = null;
	var nodata = null;
	var maxValue = 0;
	var l1 = groupVar.length;
	var l2 = xVar.length;
	var l3 = rVar.length;
	for (var i=0; i<l1; i++) {
		var values = [];
		if (groupVar[i] instanceof PPlace)
			placeid = groupVar[i].id;
		else if (groupVar[i] instanceof PIndicator){
			indid = groupVar[i].id;
			nodata = groupVar[i].nodata;
		}else
			periodid = groupVar[i];

		for (var j=0; j<l2; j++) {
			if (xVar[j] instanceof PPlace)
				placeid = xVar[j].id;
			else if (xVar[j] instanceof PIndicator){
				indid = xVar[j].id;
				nodata = xVar[j].nodata;
			}else
				periodid = xVar[j];

			for (var k=0; k<l3; k++) {
				if (rVar[k] instanceof PPlace)
					placeid = rVar[k].id;
				else if (rVar[k] instanceof PIndicator){
					indid = rVar[k].id;
					nodata = rVar[k].nodata;
				}else
					periodid = rVar[k];
				
				// In the event of an aggregate place that does not have any sufficient
				// enough boundaries to get an aggregate total for, set to null.
				if(this.cube.values[periodid][placeid] === undefined) {
					this.cube.values[periodid][placeid] = [];
					this.cube.values[periodid][placeid][indid] = null;
				}
				
				var value = this.cube.values[periodid][placeid][indid];
				
				// Set the current item's value in the chart to null if it isn't
				// numeric (an error to be displayed in the table, for example)
				// or if it is equal to nodata.
				if(typeof value != "number" || value == nodata)
					value = null;
				
				// Ensure the value doesn't exceed the max value.
				if (value && value > maxValue)
					maxValue = value;

				// Kluge to fix bug for when the first place added has a null value it ruins the layout of the charts as more places are added.
				// In the api file plotkit/Layout.js (line 527) added this kluge
				// it'll check for this string and then not show it as a value for the line chart.
				if (value == null)
					value = '0.00000';

				values.push([j, value]);
				if (l2 == 1)
					values.push([1,0]);
			}
		}
		data.push(values);
	}

	if (this.options.yMax)
		maxValue = this.options.yMax;
	else {
		var interval = "1";
		var maxInt = maxValue.toString().split(".");
		for (var i=1; i<maxInt[0].toString().length; i++)
			interval += "0";
		maxValue += parseInt(interval);
		maxValue = parseInt(maxValue / interval) * interval;
	}
	if(maxValue < 0) maxValue = 0;
	var intervals = this.options.yIntervals;
	if (intervals == null) {
		intervals = maxValue.toString().charAt(0);
		if (intervals < 2)
			intervals = intervals * 4;
		else if (intervals < 4)
			intervals = intervals * 2;
	}

	this.layoutOptions = {
			"xTicks": labels,
			"yNumberOfTicks": intervals
//			"yTickPrecision": this.options.yDecimal,
//			"yOriginIsZero": false
	};

	var hexColor = MochiKit.Color.Color.fromHexString;

	var color;
	if (this.options.baseColor)
		color = PlotKit.Base.palette(hexColor(this.options.baseColor));
	else {
		var colors = this.options.colorRamp.colors;
		if (groupVar.length > colors.length) {
			for (var i=0; i<this.ogColors.length; i++) {
				colors.push(this.ogColors[i]);
			}
		}
		color = this.options.colorRamp.convertToHex();
	}

	var shouldFill = true;
	if (this.options.type == PChartOptions.LINE)
		shouldFill = false;

	var paddingLeft = 70;
	if (this.options.type == PChartOptions.PIE)
		paddingLeft = 0;

	this.renderOptions = {
			"colorScheme": color,
			"padding": {left: paddingLeft, right: 0, top: 10, bottom: 30},
			"shouldFill": shouldFill,
			"backgroundColor": hexColor(this.options.backgroundColor),
			"displayValue": this.options.displayValue
	};

	var charttype = "bar";
	if (this.options.type == PChartOptions.LINE)
		charttype = "line";
	else if (this.options.type == PChartOptions.PIE)
		charttype = "pie";

	var layout = new PlotKit.Layout(charttype, this.layoutOptions);

	for (var i=0; i<data.length; i++)
		layout.addDataset(i, data[i]);

	layout.evaluate();

	var plotter = new PlotKit.SweetCanvasRenderer(this.canvas, layout, this.renderOptions);
	this.plotkitLayout = layout;
	plotter.render();
}

PlotKit.SweetCanvasRenderer.prototype._renderLineChart = function() {
	var context = this.element.getContext("2d");
	var colorCount = this.options.colorScheme.length;
	var colorScheme = this.options.colorScheme;
	var setNames = PlotKit.Base.keys(this.layout.datasets);
	var setCount = setNames.length;
	var bind = MochiKit.Base.bind;

	for (var i = 0; i < setCount; i++) {
		var setName = setNames[i];
		var color = colorScheme[i%colorCount];


		// create paths
		var makePath = function(ctx) {
			this.ctx_points = new Array();
			
			var open = true;

			var lines = [[]];
			var line = 0;
			for(var j=0; j<this.layout.points.length; j++){
				if (this.layout.points[j].name == setName){
					this.ctx_points.push(new Array(this.area.w * this.layout.points[j].x + this.area.x, this.area.h * this.layout.points[j].y + this.area.y));
					if(!isNaN(this.layout.points[j].x)  && !isNaN(this.layout.points[j].y)){
						lines[line].push(this.layout.points[j]);
					}else if(lines[line].length > 0){
						line++;
						lines[line] = [];
					}
				}
			}

			for(var j=0;j<lines.length;j++){
				ctx.save();
				ctx.shadowBlur = 5.0;
				ctx.shadowColor = Color.fromHexString("#888888").toRGBString();
				ctx.fillStyle = color.toRGBString();
				ctx.lineWidth = 2.0;
				ctx.strokeStyle = color.toRGBString();
				
				ctx.beginPath();
				for(var k=0;k<lines[j].length;k++){
					if(k==0)
						ctx.moveTo(this.area.w * lines[j][k].x + this.area.x, this.area.h * lines[j][k].y + this.area.y);
					else
						ctx.lineTo(this.area.w * lines[j][k].x + this.area.x, this.area.h * lines[j][k].y + this.area.y);
				}
				for(var k=lines[j].length-2;k>=0;k--){
					ctx.lineTo(this.area.w * lines[j][k].x + this.area.x,this.area.h * (lines[j][k].y) + this.area.y);
				}
				
				ctx.closePath();
				ctx.stroke();
				ctx.restore();
			}
		};
		bind(makePath, this)(context);
	}
};

// Don't think these 2 function lockBoundaryType() and unlockBoundaryType() are needed anymore 
// but it's used in LegendComponents a lot so leaving it in for now.
// They're not needed because now each indicator is put in the sessions with it's locked boundary id.
PLegend.prototype.lockBoundaryType = function() {
	this._lockBoundaryType = true;
}

PLegend.prototype.unlockBoundaryType = function() {
	this._lockBoundaryType = false;
	this.clearStoredScale();
}

PLegend.prototype.boundaryTypeIsLocked = function(indid) {
        var btds = ( session2.get( this.instanceId + 'btds' ) && session2.get( this.instanceId + 'btds' ).constructor == Object ) ? session2.get( this.instanceId +'btds' ) : {};
	if (btds[indid])
		return true;
	else
		return false;
//	return this._lockBoundaryType;
}
PLegend.prototype.setBoundaryType = function( btd ) {
	var old = this._boundaryType;
	if( btd && btd.constructor == PPlaceType ) {
		this._boundaryType = btd;
		return btd;
	} 
	var candidates = this.getBoundaryTypes();
	for( var i = 0; i < candidates.length; ++i ) {
		if( candidates[i].id == btd ) {
			this._boundaryType = candidates[i];
			return candidates[i];
			break;
		}
	}
}
PLegend.prototype.isValidBoundaryType = function( btd, noPreRendered) {
	var bt = ( btd && btd.constructor == PPlaceType ) ? btd.id : btd;  
	var candidates = this.getBoundaryTypes(noPreRendered);
	var isValid = false;
	for( var i = 0; i < candidates.length; ++i ) {
		if( candidates[i].id == bt ) {
			isValid = true;
			break;
		}
	}
	return isValid;
}

// used in getBoundaryType so calculated boundaryType can be re-used
PLegend.prototype.clearStoredScale = function() {
	this.scale = null;
}

/* making map optional */ 
PLegend.prototype.getBoundaryType = function() {
	// if shadeby was changed for the indicator the boundary type is stored in sessions and returned here.
	// TODO: could save the boundary type so setBoundaryType doesn't need to be called as many times.
        var btds = ( session2.get( this.instanceId + 'btds' ) && session2.get( this.instanceId + 'btds' ).constructor == Object ) ? session2.get( this.instanceId +'btds' ) : {};
        if (this.indicator && btds[this.indicator.id] && this.isValidBoundaryType(btds[this.indicator.id])) {
                return this.setBoundaryType(btds[this.indicator.id]);
	}

	/* kluge, if legend doesn't have a map check for a global one */
	var scale = null;
	if(this.map && this.map.kamap)
		scale = this.map.kamap.getCurrentScale();
	else if(map && map.kamap)
		scale = map.kamap.getCurrentScale();

	// this function is called an awful lot on the map page, so no need to run the full function every time.
	// return already set _boundaryType when zoom and indicator are the same. this.scale is reset in zoomend and setindicator events
	if (this.scale && this.scale == scale && this._boundaryType) {
		return this._boundaryType;
	}
	this.scale = scale;

	if (scale && this.indicator) {
		var zooms = this.typeconfig.zooms;
		var l = zooms.length;
		for (var i=l-1; i>=0; i--) {
			if (zooms[i]) {
				if (zooms[i].length) {
					var l2 = zooms[i].length;
					for (var j=0; j<l2; j++) {
						if ( this.showTypeInShadeBy(zooms[i][j].id) && scale <= P_SCALES_DEFAULT[i] && this.getBoundaryDefinitionID(zooms[i][j].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i][j].id]) {
							this._boundaryType = zooms[i][j];
							return this._boundaryType;
						}
					}
				} else if ( this.showTypeInShadeBy(zooms[i].id) && scale <= P_SCALES_DEFAULT[i] && this.getBoundaryDefinitionID(zooms[i].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i].id]) {
					this._boundaryType = zooms[i];
					return this._boundaryType;
				}
			}
		}
// if haven't found any boundaries for this zoom, check for prerendered custom
// scales (start from highest scale)
		if (this.indicator.maxscale && scale <= this.indicator.maxscale) {
			for (var i=0; i<l; i++) {
				if (zooms[i]) {
					if (zooms[i].length) {
						for (var j=0; j<zooms[i].length; j++) {
							if ( this.showTypeInShadeBy(zooms[i][j].id) && this.getBoundaryDefinitionID(zooms[i][j].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i][j].id]) {
								this._boundaryType = zooms[i][j];
								return this._boundaryType;
							}
						}
					} else if ( this.showTypeInShadeBy(zooms[i].id) && this.getBoundaryDefinitionID(zooms[i].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i].id]) {
						this._boundaryType = zooms[i];
						return this._boundaryType;
					}
				}
			}
		}
	}
	return null;
}
PLegend.prototype.getBoundaryTypes = function(noPreRendered) {
	var results = [];
	/* kluge, if legend doesn't have a map check for a global one */
	var scale = null;
	if(this.map && this.map.kamap)
		scale = this.map.kamap.getCurrentScale();
	else if(map && map.kamap)
		scale = map.kamap.getCurrentScale();
	
	if (scale && this.indicator) {
		var zooms = this.typeconfig.zooms;
		var l = zooms.length;
		for (var i=l-1; i>=0; i--) {
			if (zooms[i]) {
				if (zooms[i].length) {
					var l2 = zooms[i].length;
					for (var j=0; j<l2; j++) {
						if ((scale <= P_SCALES_DEFAULT[i] || this.allowTypeAlways(zooms[i][j])) && this.getBoundaryDefinitionID(zooms[i][j].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i][j].id]) {
							results.push(zooms[i][j]);
						}
					}
				} else if (scale <= P_SCALES_DEFAULT[i] && this.getBoundaryDefinitionID(zooms[i].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i].id]) {
					results.push(zooms[i]);
				}
			}
		}
// if haven't found any boundaries for this zoom, check for prerendered custom
// scales (start from highest scale)
		if (noPreRendered != true && results.length <= 0 && this.indicator.maxscale && scale <= this.indicator.maxscale) {
			for (var i=0; i<l && results.length<=0; i++) {
				if (zooms[i]) {
					if (zooms[i].length) {
						for (var j=0; j<zooms[i].length; j++) {
							if (this.getBoundaryDefinitionID(zooms[i][j].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i][j].id]) {
								results.push( zooms[i][j] );
								break;
							}
						}
					} else if (this.getBoundaryDefinitionID(zooms[i].id) != null && this.indicator.breaks[this.indicator.curPerIndex][zooms[i].id]) {
						results.push( zooms[i] );
					}
				}
			}
		}
	}
	return results;
}

PLegend.prototype.showTypeInShadeBy = function(typeid) {
	// when indicator only has a normally ignored type it has to be the default 
	if (this.indicator.getPlaceTypes().length == 1)
		return true;
	var ignoreTypes = [PPlaceType.CITY, PPlaceType.CBSA_07, PPlaceType.SLD_UPPER, PPlaceType.SLD_LOWER, PPlaceType.CONGRESSIONAL, PPlaceType.CDBG, PPlaceType.COUNTY_SUBDIVISION,PPlaceType.MD];
	// kluge to not use some placetypes in the default zooms
	for (var p=0; p<ignoreTypes.length; p++) {
		if (typeid == ignoreTypes[p].id)
			return false;
	}
	return true;
}

// This will allow displaying indicators for the specified placetypes at all zoom levels.
// Added this because we need States to stay the default for the outer zooms but also need to allow counties to be seen.
PLegend.prototype.allowTypeAlways = function(type) {
	var allow = [PPlaceType.COUNTY];
	for (var i=0; i<allow.length; i++) {
		if (allow[i].id == type.id)
			return true;
	}
	return false;
}

PTable.prototype.createRemove = function(elem, floatStyle) {
	var table = this;
	var remove = document.createElement("div");
	var title = "";
	if (elem instanceof PIndicator)
		title = "Remove Indicator";
	else if (elem instanceof PPlace)
		title = "Remove Place";
	else
		title = "Remove Period";
	remove.title = title;
	var item = elem;
	var cube = this.cube;
	this.listeners.push(PEvent.addListener(remove, 'click', function() { 
		cube.remove(item);
	}));
	remove.style.cursor = "pointer";
	remove.style.cssFloat = floatStyle;
	remove.style.styleFloat = floatStyle;
	remove.appendChild(this.options.removeButton.cloneNode(true));
	return remove;
}

PlotKit.Layout.prototype._evaluateLineTicksForYAxis = function() {
	var isNil = MochiKit.Base.isUndefinedOrNull;
	var unit = "";
	if(cube && cube.indicators && cube.indicators.length > 0 && cube.indicators[0].unit)
		unit = cube.indicators[0].unit;
	if (this.options.yTicks) {
		this.yticks = new Array();
		var makeTicks = function(tick) {
			var label = tick.label;
			if (isNil(label))
				label = tick.v.toString();
			var pos = 1.0 - (this.yscale * (tick.v - this.minyval));
			if ((pos >= 0.0) && (pos <= 1.0)) {
				this.yticks.push([pos, PWebUtil.formatNumber(label,unit)]);
			}
		};
		MochiKit.Iter.forEach(this.options.yTicks, bind(makeTicks, this));
	}
	else if (this.options.yNumberOfTicks) {
		// We use the optionally defined number of ticks as a guide
		this.yticks = new Array();

		// if we get this separation right, we'll have good looking graphs
		var roundInt = PlotKit.Base.roundInterval;
		var prec = this.options.yTickPrecision;
		var roughSeparation = roundInt(this.yrange, 
				this.options.yNumberOfTicks, prec);
        var roughOffset = 0;
        for (var i = 0; i <= this.options.yNumberOfTicks; i++){
            var yval = this.minyval + (i * roughSeparation);
            if(yval == 0){
            	break;
            }else if((yval>0 && yval - (roughSeparation/2) <= 0) || (yval<0 && yval + (roughSeparation/2) >= 0)){
            	roughOffset = yval;
            	break;
            }
        }

		// round off each value of the y-axis to the precision
		// eg. 1.3333 at precision 1 -> 1.3
		for (var i = 0; i <= this.options.yNumberOfTicks; i++) {
			var yval = this.minyval + (i * roughSeparation) - roughOffset;
			var pos = 1.0 - ((yval - this.minyval) * this.yscale);
			if ((pos > 1.0) || (pos < 0.0))
				continue;
			this.yticks.push([pos, PWebUtil.formatNumber(MochiKit.Format.roundToFixed(yval, prec), unit)]);
		}
	}
};

// Added class name to x axis labels
PlotKit.CanvasRenderer.prototype._renderAxis = function() {
    if (!this.options.drawXAxis && !this.options.drawYAxis)
        return;

    var context = this.element.getContext("2d");

    var labelStyle = {"style":
         {"position": "absolute",
          "fontSize": this.options.axisLabelFontSize + "px",
          "zIndex": 10,
          "color": this.options.axisLabelColor.toRGBString(),
          "width": this.options.axisLabelWidth + "px",
          "overflow": "hidden"
         }
    };

    // axis lines
    context.save();
    context.strokeStyle = this.options.axisLineColor.toRGBString();
    context.lineWidth = this.options.axisLineWidth;


    if (this.options.drawYAxis) {
        if (this.layout.yticks) {
            var drawTick = function(tick) {
                if (typeof(tick) == "function") return;
                var x = this.area.x;
                var y = this.area.y + tick[0] * this.area.h;
                context.beginPath();
                context.moveTo(x, y);
                context.lineTo(x - this.options.axisTickSize, y);
                context.closePath();
                context.stroke();

                var label = DIV(labelStyle, tick[1]);
                label.style.top = (y - this.options.axisLabelFontSize) + "px";
                label.style.left = (x - this.options.padding.left - this.options.axisTickSize) + "px";
                label.style.textAlign = "right";
                label.style.width = (this.options.padding.left - this.options.axisTickSize * 2) + "px";
                MochiKit.DOM.appendChildNodes(this.container, label);
                this.ylabels.push(label);
            };
            
            MochiKit.Iter.forEach(this.layout.yticks, bind(drawTick, this));
        }

        context.beginPath();
        context.moveTo(this.area.x, this.area.y);
        context.lineTo(this.area.x, this.area.y + this.area.h);
        context.closePath();
        context.stroke();
    }

    if (this.options.drawXAxis) {
        if (this.layout.xticks) {
            var drawTick = function(tick) {
                if (typeof(dataset) == "function") return;

                var x = this.area.x + tick[0] * this.area.w;
                var y = this.area.y + this.area.h;
                context.beginPath();
                context.moveTo(x, y);
                context.lineTo(x, y + this.options.axisTickSize);
                context.closePath();
                context.stroke();

                var label = DIV(labelStyle, tick[1]);
                label.style.top = (y + this.options.axisTickSize) + "px";
                label.style.left = (x - this.options.axisLabelWidth/2) + "px";
                label.style.textAlign = "center";
                label.className = "xLabel";
                label.style.width = this.options.axisLabelWidth + "px";
                MochiKit.DOM.appendChildNodes(this.container, label);
                this.xlabels.push(label);
            };
            
            MochiKit.Iter.forEach(this.layout.xticks, bind(drawTick, this));
        }

       	var yoffset = this.area.y + (this.layout.maxyval * this.layout.yscale * this.area.h);
        context.beginPath();
        context.moveTo(this.area.x, yoffset);
        context.lineTo(this.area.x + this.area.w, yoffset);
        context.closePath();
        context.stroke();
        context.beginPath();
        context.moveTo(this.area.x + this.area.w, this.area.y);
        context.lineTo(this.area.x + this.area.w, this.area.y + this.area.h);
        context.closePath();
       context.stroke();

    }

    context.restore();

};

// Added legendMerger layer.
_layer.prototype.buildLayerList = function(scale) {
	var layers = "";
	if (this.mergedLayers.length > 0) {
		for (var i=0; i < this.mergedLayers.length; i++) {
			if (this.mergedLayers[i].minScale <= scale && this.mergedLayers[i].maxScale >= scale) {
				var name = this.mergedLayers[i].name;
				// swap for thin lines or labels if thematic is on
				if (this.legend || typeof legendMerger != 'undefined') {
					if (name == "pointline") layers += "t_pointline";
					else if (name == "pp2_line") layers += "pp2_tline";
					else if (name == "pp2_labels") layers += "pp2_tlabels";
					else if (name == "pp2tg_pointline") layers += "pp2tg_tpointline";
					else if (name == "pp2tg_line") layers += "pp2tg_tline";
					else if (name == "pp2tg_streetlabels") layers += "pp2tg_tstreetlabels";
					else layers += name;
				} else 
					layers += name;

				if (i == 0 && this.legend != null && this.legend.getBreaks() != "") {
					// Colors are stored in an array. Breaks are stored in a string.
					var colors = [];
					var breaks = "";
					
					// Check if the current indicator uses exact breaks and the mvalayers session variable is not null.
					// If mvalayers is null, Start Over was probably clicked. In this case, we want to just display all of the layers. This is the done the same way as displaying a non-exact-break indicator (handled in the else below).
					if((editState & PIndicatorBreaks.EDITSTATE.exactBreaks) && session2.get("mvalayers") != null) {
						// MVA datasets can have layers added or removed. Here we'll determine the layers to draw, and adjust the arguments being sent to the tile renderer accordingly.
						// Breaks are split into an array temporarily as we go through each one. They're put back into a string when we're finished.
						var mvaLayers = session2.get("mvalayers");
						var tempColors = this.legend.getColors();
						var tempBreaks = this.legend.getEncodedBreaks().split(":");
						// If 'Insufficient Data' exists, we need to add one to the number of breaks.
						if(editState & PIndicatorBreaks.EDITSTATE.noData)
							var numBreaks = this.legend.getNumberOfBreaks() + 1;
						else
							var numBreaks = this.legend.getNumberOfBreaks();
						
						// mvaLayers[] stores whether or not a layer in the list is to be displayed.
						// Cycle through each layer and add it to the list if it should be displayed.
						var k = 0;
						for(var j=0; j<numBreaks; j++) {
							if(mvaLayers[j]) {
								colors[k] = tempColors[j];
								if(k != 0)
									breaks += ":";
								breaks += tempBreaks[j];
								k++;
							}
						}
					} else {
						colors = this.legend.getColors();
						breaks = this.legend.getEncodedBreaks();
					}
					var legendString = "p_" + this.legend.indicator.periodids[this.legend.indicator.curPerIndex] + ".id_" + this.legend.indicator.id + ".colors_" + colors.join(":") + ".breaksdata_" + breaks + ".boundary_definition_" + this.legend.getBoundaryDefinitionID(this.legend.getBoundaryType().id);
					
					// Check to see if island printing was selected.
					if(isIslandPrint == true) {
						// Island printing. Attach the selected boundary or current custom region.
						if(session2.get("p")) {
							legendString += ".isl_" + session2.get("p");
							legendString += ".islt_pd";
							// Hide the boundary outline for pre-determined regions.
							hideOutline = true;
						} else if(session2.get("cp")) {
							// We now allow multiple custom regions. Include all custom regions
							// on the map, but separate them with semicolons instead of commas.
							var cpids = session2.get("cp").replace(/,/g, ";");
							legendString += ".isl_" + cpids;
							legendString += ".islt_cus";
							// Display the boundary outline for custom regions.
							hideOutline = false;
						}
						// isIslandPrint is only set to true from the print dialog to
						// ensure we only perform island printing for prints, and not
						// for live map rendering. Now that we've generated the
						// arguments for the print, set it back to false.
						isIslandPrint = false;
					}

					var nd = 0;
					if (this.legend.indicator.nodata)
						nd++;
					if (this.legend.indicator.breakid == P_BREAKTYPE_EXACT_VALUE_ID || this.legend.indicator.breakid == P_BREAKTYPE_EXACT_VALUE_CUSTOM_ID)
						nd += this.legend.getNumberOfBreaks();
					if (nd > 0)
						legendString += ":nd_" + nd;
					layers += "," + legendString;
				}
				if (this.mergedLayers[i].opacity < 1 && !this.mergedLayers[i].client)
					layers += ":o_" + this.mergedLayers[i].opacity;
				if(i==0 && typeof legendMerger != 'undefined' && legendMerger.getName().length > 0)
					layers += "," + legendMerger.getName();

				layers += ",";
			}
		}
		layers = layers.substring(0,layers.length-1);
	}
	layers = layers.replace(/&/g, '%26');
	layers = layers.replace(/#/g, '%23');
	return layers;
}

/*
 * Revised function so that it only returns placetypes that are available for
 * the current period
 */
PIndicator.prototype.getPlaceTypes = function() {
	var places = [];
	for (var i=0; i<this.boundaries.length; i++) {
		var btype = this.boundaries[i].split(":");
		for (var j=0; j<P_PLACETYPES.length; j++) {
			if (btype[0] == P_PLACETYPES[j].id && this.breaks[this.curPerIndex][btype[0]]) {
				places.push(P_PLACETYPES[j]);
				break;
			}
		}
	}
	return places;
}

PClientGeocoder.prototype.getPlaceContaining = function(center, placetypes, callback) {
	var ptids = "";
	for(var i=0;i<placetypes.length;i++)
		ptids += placetypes[i].id+",";
	ptids = ptids.replace(/\,$/,'');
	PAsync.call(PEnvironment.placeUrl + '/boundary/js?act=ctg&x=' + center.lng() + '&y=' + center.lat() + '&pti=' + ptids + "&key=" + this.key, this, callback);
}



PClientGeocoder.prototype.getMarkersWithin = function(bounds, overlayset, callback) {
        var query = "&" + this.getBoundsQuery(bounds);
        if(overlayset.limit != null)
                query += "&lim=" + overlayset.limit;
        if(overlayset.offset != null)
                query += "&off=" + overlayset.offset;

        var url = PEnvironment.pinUrl + '/pin/js?cid=' + PEnvironment.custId + '&did=' + overlayset.id + query + '&mxp=' + overlayset.pinmax + '&ftr=' + encodeURIComponent(overlayset.createQuery());

        if( overlayset.pinType ) {
                url += "&typ=poly";
        }

        if(overlayset.format == "csv"){
                url += "&fmt=csv";
                window.location = url;
        }else{
                PAsync.call(url, this, callback);
        }
}
_layer.prototype.buildTileURL = function(t, l, mapName, scale, force, group, layer, imageFormat) {
	var kamap = this._map.kaMap;
	var server = this.tileServers.isEmpty() == true ? kamap.server : this.tileServers.next( (t+l)/kamap.tileWidth );
	
	if (kamap.isDirectTileAccess == true) {
		var layers = this.buildLayerList(scale);
		var src = "";
		if (scale && layers != "") {
			var metaTop = Math.floor(t / 1000) * 1000;
			var metaLeft = Math.floor(l / 1000) * 1000;
			src = server
				+ '/' + mapName
				+ '/' + scale
				+ '/' + layer
				+ '/' + layers
				+ '/meta_t' + metaTop
				+ '/t' + metaTop + 'l' + metaLeft
				+ '/t' + t + 'l' + l
				+ '.' + 'png'
				+ '?maprev='+this._map.revision
				+ '&key='+kamap.apiKey;
		}
		// if directory name is too long, go straight to tile_r
		if (layers.length > 255 || layers.match(".*pins_.*:data_.*")) {
			var url = server.split('/');
			if (server.match("https")) {
				src = url[0] + "//" + url[2] + "/" + url[3] + "/" + url[4] + "/" + kamap.directTilePath + '?s='+scale+'&t='+t+'&l='+l+'&layers='+layers+'&key='+kamap.apiKey;
			} else {
				src = url[0] + "//" + url[2] + "/" + kamap.directTilePath + '?s='+scale+'&t='+t+'&l='+l+'&layers='+layers+'&key='+kamap.apiKey;;
			}
		}
	
		//document.title = src;
		return src;
	}
}

// rewrite from api to add ctypeid and polysetid checks
// this way custom regions and polygons from polysets can be queried using their ids and not vertices
PClientGeocoder.prototype.getBoundsQuery = function(bounds) {
	var query = "";
	if (bounds instanceof PPlace) {
		if (bounds.typeid == 0)
			query += "&customid=" + bounds.id;
		else if (bounds.polysetid) {
			query += "&polyid=" + bounds.id;
			query += "&polysetid=" + bounds.polysetid;
		}
		else
			query += "pid=" + bounds.id;
	} else if (bounds instanceof Array) {
		query += "vtc=";
		// ensure polygon and closed
		if (bounds.length < 3) return;
		if (!bounds[0].equals(bounds[bounds.length-1])) bounds.push(bounds[0]);
		for (var i=0; i<bounds.length; i++) {
			query += bounds[i].lat() + "," + bounds[i].lng();
			if (i < bounds.length-1)
				query += ";";
		}
	} else if (bounds instanceof PLatLngBounds) {
		var sw = bounds.getSouthWest();
		var ne = bounds.getNorthEast();
		query += "mnx=" + sw.lng() + "&mny=" + sw.lat() + "&mxx=" + ne.lng() + "&mxy=" + ne.lat();
	} else if (bounds instanceof PLatLng) {
		query += "x=" + bounds.lng() + "&y=" + bounds.lat();
	}
	return query;
}

// Checks if place is a custom region or a polygon from a polyset
PPlace.prototype.isCustomOrPoly = function() {
	if (this.typeid == 95 || this.typeid == 0)
		return true;
	else
		return false;
}

POverlaySet.prototype.isPolySet = function(id, map) {
	var pps = POverlaySet.PointAndPolygonSets;

	// first see if this id is a polyset that lives alone
	if(pps[id] && pps[id] == pps[id].polygonID)
		return true;
	else {
		// check if polyset is connected to a pointset
		var sets = map.getOverlaySets();
		for(var i=0; i<sets.length; i++) {
			if (pps[sets[i].id] && pps[sets[i].id].polygonID == id)
				return true;
		}
	}

	return false;
}

