UNPKG

googlemaps-v3-utility-library

Version:
3,790 lines 145 kB
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
/**
 * @preserve http://google-maps-utility-library-v3.googlecode.com
 */
/**
 * @name ArcGIS Server Link for Google Maps JavaScript API V3
 * @version 1.0
 * @author: Nianwei Liu (nianwei at gmail dot com)
 * @fileoverview 
 *  <p><a href="examples.html">Examples</a>
 *   </p> 
 *  <p>This library lets you add map resources accessible via 
 *    <a href = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/index.html'> 
 *    ESRI ArcGIS Server&#0153; REST API</a> into <a 
 *    href='http://code.google.com/apis/maps/documentation/javascript/'>
 *    Google Maps API V3</a> and provide some additional support for map tiles created 
 *    with different spatial reference and tiling scheme.</p>
 *    </p>.
 *    <table>
 *    <tr>
 *    <td style = 'width:200px'>
 *    {@link TileLayer}<br/>
 *    {@link TileLayerOptions}<br/>
 *    {@link MapType}<br/>
 *    {@link MapTypeOptions}<br/>
 *    {@link MapOverlay}<br/>
 *    {@link MapOverlayOptions}<br/>
 *    {@link Projection}<br/>
 *    </td>
 *    <td style = 'width:200px'>
 *    {@link Catalog}<br/>
 *    {@link MapService}<br/></b>
 *    {@link Layer}<br/>
 *    {@link GeocodeService}<br/>
 *    {@link GeometryService}<br/>
 *    {@link GPService}<br/>
 *    {@link GPTask}<br/>
 *    {@link RouteTask}<br/>
 *     <br/></td>
 *     <td style = 'width:200px'>
 *    {@link SpatialReference}<br/>
 *    {@link Geographic}<br/>
 *    {@link LambertConformalConic}<br/>
 *    {@link TransverseMercator}<br/>
 *    {@link SphereMercator}<br/>
 *    {@link Albers}<br/>
 *    {@link SpatialRelationship}<br/>
 *     </td>
 *     <td style = 'width:200px'>
 *    {@link Util} <br/> 
 *    {@link Config} <br/> 
 *    {@link Error} <br/> 
 *     </td>
 *    </tr></table>
 *    <p> There are many objects used in the REST API that do not require 
 *    a constructor and can be
 *    used just as object literal in the operation:</p>
 *    <table><tr>
 *    <td style = 'width:200px'>
 *    {@link Field}<br/>
 *    {@link TileInfo}<br/>
 *    {@link LOD}<br/>
 *    {@link ExportMapOptions}<br/>
 *    {@link MapImage}<br/>
 *    {@link IdentifyOptions}<br/>
 *    {@link IdentifyResults}<br/>
 *    {@link IdentifyResult}<br/>
 *     <br/></td>
 *     <td style = 'width:200px'>
 *    {@link QueryOptions}<br/>
 *    {@link ResultSet}<br/>
 *    {@link FindOptions}<br/>
 *    {@link FindResults}<br/>
 *    {@link FindResult}<br/>
 *    {@link Feature}<br/>
 *     </td>
 *     <td style = 'width:200px'>
 *    {@link GeocodeOptions}<br/>
 *    {@link GeocodeResults}<br/>
 *    {@link GeocodeResult}<br/>
 *    {@link ReverseGeocodeOptions}<br/>
 *    {@link ReverseGeocodeResult}<br/>
 *    {@link BufferOptions}<br/>
 *    {@link BufferResults}<br/> 
 *    {@link ProjectOptions}<br/>
 *    {@link ProjectResults}<br/> 
 *    </td>
 *     <td style = 'width:200px'>
 *    {@link RouteOptions}<br/>
 *    {@link RouteResults}<br/>  
 *    </td>
 *    </tr></table>
 */
  
  /*jslint evil: true, sub: true */ 
  /*global escape ActiveXObject */
var gmaps = gmaps || {};

/** @const */
var RAD_DEG = Math.PI / 180;
var jsonpID_ = 0;
window['ags_jsonp'] = window['ags_jsonp'] || {};


var G = google.maps; 
var WGS84, NAD83, WEB_MERCATOR, WEB_MERCATOR_AUX;

/**
 * @name Config
 * @class This is an object literal that sets common configuration values used across the lib.
 * @property {String} [proxyUrl] The URL to the web proxy page used in case the length of the URL request to an ArcGIS Server REST resource exceeds 2000 characters.
 * @property {Boolean} [alwaysUseProxy] whether to always use proxy page when send request to server.
 */
var Config = {
  proxyUrl:null,
  alwaysUseProxy: false
};

/**
 * an internal collection of Spatial Refeneces supported in the application.
 * The key of the collection is the wkid/wkt, and value is an instance of
 * {@link SpatialReference}.
 */
var spatialReferences_ = {};

/**
 * A set of utilities ((<code>Util</code>)
 * for commonly used functions.
 * @name Util
 * @namespace
 */
var Util = {};
/**
 * Extract the substring from full string, between start string and end string
 * @param {String} full
 * @param {String} start
 * @param {String} end
 */
function extractString_(full, start, end) {
  var i = (start === '') ? 0 : full.indexOf(start);
  var e = end === '' ? full.length : full.indexOf(end, i + start.length);
  return full.substring(i + start.length, e);
}

/**
 * Check if the object is String
 * @param {Object} o
 */
function isString_(o) {
  return o && typeof o === 'string';
}
  
  /**
   * Check if the object is array
   * @param {Object} o
   */
function isArray_(o) {
  return o && o.splice;
}
  
function isNumber_(o) {
  return typeof o === 'number';
}
 
   /**
   * Add the property of the source object to destination object 
   * if not already exists.
   * @param {Object} dest
   * @param {Object} src
   * @param {Boolean} force
   * @return {Object}
   */
function augmentObject_(src, dest, force) {
    if (src && dest) {
      var p;
      for (p in src) {
        if (force || !(p in dest)) {
          dest[p] = src[p];
        }
      }
    }
    return dest;
}
  
/**
 * Wrapper around google.maps.event.trigger
 * @param {Object} src
 * @param {String} evtName
 * @param {Object} args
 */
function triggerEvent_(src, evtName, args) {
  G.event.trigger.apply(this, arguments);
}

/**
 * handle JSON error
 * @param {Object} errback
 * @param {Object} json
 */
function handleErr_(errback, json) {
  if (errback && json && json.error) {
    errback(json.error);
  }
}
/**
 * get REST format for 2 time
 * @param {Date} time
 * @param {Date} endTime
 */
function formatTimeString_(time, endTime) {
  var ret = '';
  if (time) {
    ret += (time.getTime() - time.getTimezoneOffset() * 60000);
  }
  if (endTime) {
    ret += ', ' + (endTime.getTime() - endTime.getTimezoneOffset() * 60000);
  }
  return ret;
}

/**
 * Set opacity of a node.
 * @param {Node} node
 * @param {Number} 0-1
 */
function setNodeOpacity_(node, op) {
  // closure compiler removed?
  op = Math.min(Math.max(op, 0), 1);
  if (node) {
    var st = node.style;
    if (typeof st.opacity !== 'undefined') {
      st.opacity = op;
    }
    if (typeof st.filters !== 'undefined') {
      st.filters.alpha.opacity = Math.floor(100 * op);
    }
    if (typeof st.filter !== 'undefined') {
      st.filter = "alpha(opacity:" + Math.floor(op * 100) + ")";
    }
  }
}
/**
 * get the layerdef text string from an object literal
 * @param {Object} defs
 */
function getLayerDefsString_(defs) {
  var strDefs = '';
  for (var x in defs) {
    if (defs.hasOwnProperty(x)) {
      if (strDefs.length > 0) {
        strDefs += ';';
      }
      strDefs += (x + ':' + defs[x]);
    }
  }
  return strDefs;
}

function getXmlHttp_() {
  if (typeof XMLHttpRequest === "undefined") {
    try {
      return new ActiveXObject("Msxml2.XMLHTTP.6.0");
    } catch (e) {
    }
    try {
      return new ActiveXObject("Msxml2.XMLHTTP.3.0");
    } catch (e1) {
    }
    try {
      return new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e2) {
    }
    throw new Error("This browser does not support XMLHttpRequest.");
  } else {
    return new XMLHttpRequest();
  }
}

/**
 * @name GeometryType
 * @enum {String}
 * @const
 * @class List of Geometry type supported by ArcGIS server.
 * @property {String} [POINT] esriGeometryPoint
 * @property {String} [MULTIPOINT] esriGeometryMultipoint
 * @property {String} [POLYLINE] esriGeometryPolyline
 * @property {String} [POLYGON] esriGeometryPolygon
 * @property {String} [ENVELOPE] esriGeometryEnvelope
 */
var GeometryType = {
  POINT: 'esriGeometryPoint',
  MULTIPOINT: 'esriGeometryMultipoint',
  POLYLINE: 'esriGeometryPolyline',
  POLYGON: 'esriGeometryPolygon',
  ENVELOPE: 'esriGeometryEnvelope'
};

function getGeometryType_(obj) {
  var o = obj;
  if (isArray_(obj) && obj.length > 0) {
    o = obj[0];
  }
  if (o instanceof G.LatLng || o instanceof G.Marker) {
    if (isArray_(obj) && obj.length > 1) {
      return GeometryType.MULTIPOINT;
    } else {
      return GeometryType.POINT;
    }
  } else if (o instanceof G.Polyline) {
    return GeometryType.POLYLINE;
  } else if (o instanceof G.Polygon) {
    return GeometryType.POLYGON;
  } else if (o instanceof G.LatLngBounds) {
    return GeometryType.ENVELOPE;
  } else if (o.x !== undefined && o.y !== undefined) {
    return GeometryType.POINT;
  } else if (o.points) {
    return GeometryType.MULTIPOINT;
  } else if (o.paths) {
    return GeometryType.POLYLINE;
  } else if (o.rings) {
    return GeometryType.POLYGON;
  }
  return null;
}

/**
 * Is the object an Google Overlay?
 * @param {Object} obj
 * @return {Boolean}
 */
function isOverlay_(obj) {
  var o = obj;
  if (isArray_(obj) && obj.length > 0) {
    o = obj[0];
  }
  if (isArray_(o) && o.length > 0) {
    o = o[0];
  }
  if (o instanceof G.LatLng || o instanceof G.Marker ||
  o instanceof G.Polyline ||
  o instanceof G.Polygon ||
  o instanceof G.LatLngBounds) {
    return true;
  }
  return false;
}
  
function formatSRParam_(sr) {
  if (!sr) {
    return null;
  }
  // for 9.3 compatibility, return wkid if possible.
  return isNumber_(sr) ? sr : sr.wkid ? sr.wkid : sr.toJSON();
}

/**
 * @param {MVCArrayOfLatLng} pts
 */
function fromLatLngsToJSON_(pts, close) {
  var arr = [];
  var latlng;
  for (var i = 0, c = pts.getLength(); i < c; i++) {
    latlng = pts.getAt(i);
    arr.push('[' + latlng.lng() + ',' + latlng.lat() + ']');
  }
  if (close && arr.length > 0) {
    arr.push('[' + pts.getAt(0).lng() + ',' + pts.getAt(0).lat() + ']');
  }
  return arr.join(',');
}

/**
 * Convert overlays (Marker, Polyline, Polygons) to JSON string in AGS format.
 * @param {OverlayView|Array.OverlayView} geom
 */
function fromOverlaysToJSON_(geom) {
  var gtype = getGeometryType_(geom);
  var g, gs, i, pts;
  var json = '{';
  switch (gtype) {
  case GeometryType.POINT:
    g = isArray_(geom) ? geom[0] : geom;
    if (g instanceof G.Marker) {
      g = g.getPosition();
    }
    json += 'x:' + g.lng() + ',y:' + g.lat();
    break;
  case GeometryType.MULTIPOINT:
    pts = [];
    for (i = 0; i < geom.length; i++) {
      if (geom[i] instanceof G.Marker) {
        g = geom[i].getPosition();
      } else {
        g = geom[i];
      }
      pts.push('[' + g.lng() + ',' + g.lat() + ']');
    }
    json += 'points: [' + pts.join(',') + ']';
    break;
  case GeometryType.POLYLINE:
    // V3 does not support multiple paths yet
    pts = [];
    gs = isArray_(geom) ? geom : [geom];
    for (i = 0; i < gs.length; i++) {
      pts.push('[' + fromLatLngsToJSON_(gs[i].getPath()) + ']');
    }
    json += 'paths:[' + pts.join(',') + ']';
    break;
  case GeometryType.POLYGON:
    pts = [];
    g = isArray_(geom) ? geom[0] : geom;
    var paths = g.getPaths();
    for (i = 0; i < paths.getLength(); i++) {
      pts.push('[' + fromLatLngsToJSON_(paths.getAt(i), true) + ']');
    }
    json += 'rings:[' + pts.join(',') + ']';
    
    break;
  case GeometryType.ENVELOPE:
    g = isArray_(geom) ? geom[0] : geom;
    json += 'xmin:' + g.getSouthWest().lng() + ',ymin:' + g.getSouthWest().lat() + ',xmax:' + g.getNorthEast().lng() + ',ymax:' + g.getNorthEast().lat();
    break;
  }
  json += ', spatialReference:{wkid:4326}';
  json += '}';
  return json;
}
/**
 * From ESRI geometry format to JSON String, primarily used in Geometry service
 * @param {Object} geom
 */
function fromGeometryToJSON_(geom) {
  function fromPointsToJSON(pts) {
    var arr = [];
    for (var i = 0, c = pts.length; i < c; i++) {
      arr.push('[' + pts[i][0] + ',' + pts[i][1] + ']');
    }
    return '[' + arr.join(',') + ']';
  }
  function fromLinesToJSON(lines) {
    var arr = [];
    for (var i = 0, c = lines.length; i < c; i++) {
      arr.push(fromPointsToJSON(lines[i]));
    }
    return '[' + arr.join(',') + ']';
  }
  
  var json = '{';
  if (geom.x) {
    json += 'x:' + geom.x + ',y:' + geom.y;
  } else if (geom.xmin) {
    json += 'xmin:' + geom.xmin + ',ymin:' + geom.ymin + ',xmax:' + geom.xmax + ',ymax:' + geom.ymax;
  } else if (geom.points) {
    json += 'points:' + fromPointsToJSON(geom.points);
  } else if (geom.paths) {
    json += 'paths:' + fromLinesToJSON(geom.paths);
  } else if (geom.rings) {
    json += 'rings:' + fromLinesToJSON(geom.rings);
  }
  json += '}';
  return json;
}

/**
 * Helper method to convert an Envelope object to <code>google.maps.LatLngBounds</code>
 * @private
 * @param {Object} extent
 * @return {google.maps.LatLngBounds} gLatLngBounds
 */
function fromEnvelopeToLatLngBounds_(extent) {
  var sr = spatialReferences_[extent.spatialReference.wkid || extent.spatialReference.wkt];
  sr = sr || WGS84;
  var sw = sr.inverse([extent.xmin, extent.ymin]);
  var ne = sr.inverse([extent.xmax, extent.ymax]);
  return new G.LatLngBounds(new G.LatLng(sw[1], sw[0]), new G.LatLng(ne[1], ne[0]));
}

/**
 * Convert a ArcGIS Geometry JSON object to core Google Maps API
 * overlays such as  <code>google.maps.Marker</code>, <code>google.maps.Polyline</code> or <code>google.maps.Polygon</code>
 * Note ArcGIS Geometry may have multiple parts, but the coresponding OverlayView
 * may (Polygon) or may not (Polyline) support multi-parts, so the result is an array for consistency.
 * @param {Object} json geometry
 * @param {OverlayOptions} opts see {@link OverlayOptions}
 * @return {Array.OverlayView}
 */
function fromJSONToOverlays_(geom, opts) {
  var ovs = null;
  var ov;
  var i, ic, j, jc, parts, part, lnglat, latlngs;
  opts = opts || {};
  if (geom) {
    ovs = [];
    if (geom.x) {
      ov = new G.Marker(augmentObject_(opts.markerOptions || opts, {
        'position': new G.LatLng(geom.y, geom.x)
      }));
      ovs.push(ov);
    } else {
      //mulpt, line and poly
      parts = geom.points || geom.paths || geom.rings;
      if (!parts) {
        return ovs;
      }
      var rings = [];
      for (i = 0, ic = parts.length; i < ic; i++) {
        part = parts[i];
        if (geom.points) {
          // multipoint
          ov = new G.Marker(augmentObject_(opts.markerOptions || opts, {
            'position': new G.LatLng(part[1], part[0])
          }));
          ovs.push(ov);
        } else {
          latlngs = [];
          for (j = 0, jc = part.length; j < jc; j++) {
            lnglat = part[j];
            latlngs.push(new G.LatLng(lnglat[1], lnglat[0]));
          }
          if (geom.paths) {
            ov = new G.Polyline(augmentObject_(opts.polylineOptions || opts, {
              'path': latlngs
            }));
            ovs.push(ov);
          } else if (geom.rings) {
            // V3 supports multiple rings
            rings.push(latlngs);
          }
        }
      }
      if (geom.rings) {
        ov = new G.Polygon(augmentObject_(opts.polygonOptions || opts, {
          'paths': rings
        }));
        ovs.push(ov);
      }
    }
  }
  return ovs;
}
function parseFeatures_(features, ovOpts) {
  if (features) {
    var i, I, f;
    for (i = 0, I = features.length; i < I; i++) {
      f = features[i];
      if (f.geometry) {
        f.geometry = fromJSONToOverlays_(f.geometry, ovOpts);
      }
    }
  }
}

/**
 * get string as rest parameter
 * @param {Object} o
 */
function formatRequestString_(o) {
  var ret;
  if (typeof o === 'object') {
    if (isArray_(o)) {
      ret = [];
      for (var i = 0, I = o.length; i < I; i++) {
        ret.push(formatRequestString_(o[i]));
      }
      return '[' + ret.join(',') + ']';
    } else if (isOverlay_(o)) {
      return fromOverlaysToJSON_(o);
    } else if (o.toJSON) {
      return o.toJSON();
    } else {
      ret = '';
      for (var x in o) {
        if (o.hasOwnProperty(x)) {
          if (ret.length > 0) {
            ret += ', ';
          }
          ret += x + ':' + formatRequestString_(o[x]);
        }
      }
      return '{' + ret + '}';
    }
  }
  return o.toString();
}

function fromLatLngsToFeatureSet_(latlngs) {
  var i, I, latlng;
  var features = [];
  for (i = 0, I = latlngs.length; i < I; i++) {
    latlng = latlngs[i];
    if (latlng instanceof G.Marker) {
      latlng = latlng.getPosition();
    }
    features.push({
      'geometry': {
        'x': latlng.lng(),
        'y': latlng.lat(),
        'spatialReference': {
          'wkid': 4326
        }
      }
    });
  }
  return {
    'type': '"features"',
    'features': features,
    'doNotLocateOnRestrictedElements': false
  };
}

function prepareGeometryParams_(p) {
  var params = {};
  if (!p) {
    return null;
  }
  var json = [];
  var g, isOv;
  if (p.geometries && p.geometries.length > 0) {
    g = p.geometries[0];
    isOv = isOverlay_(g);
    for (var i = 0, c = p.geometries.length; i < c; i++) {
      if (isOv) {
        json.push(fromOverlaysToJSON_(p.geometries[i]));
      } else {
        json.push(fromGeometryToJSON_(p.geometries[i]));
      }
    }
  }
  if (!p.geometryType) {
    p.geometryType = getGeometryType_(g);
  }
  if (isOv) {
    params.inSR = WGS84.wkid;
  } else if (p.inSpatialReference) {
    params.inSR = formatSRParam_(p.inSpatialReference);
  }
  if (p.outSpatialReference) {
    params.outSR = formatSRParam_(p.outSpatialReference);
  }
  params.geometries = '{geometryType:"' + p.geometryType + '", geometries:[' + json.join(',') + ']}';
  return params;
}

function log_(msg) {
  if (window.console) {
    window.console.log(msg);
  } else {
    var l = document.getElementById('_ags_log');
    if (l) {
      l.innerHTML = l.innerHTML + msg + '<br/>';
    }
  }
}

/**
 * Format params to URL string
 * @param {Object} params
 */
function formatParams_(params) {
  var query = '';
  if (params) {
    params.f = params.f || 'json';
    for (var x in params) {
      if (params.hasOwnProperty(x) && params[x] !== null && params[x] !== undefined) { // wont sent undefined.
        //jslint complaint about escape cause NN does not support it.
        var val = formatRequestString_(params[x]); 
        query += (query.length > 0?'&':'')+(x + '=' + (escape ? escape(val) : encodeURIComponent(val)));
      }
    }
  }
  return query;
}

/** create a callback closure
 * @private
 * @param {Object} fn
 * @param {Object} obj
 */
function callback_(fn, obj) {
  var args = [];
  for (var i = 2, c = arguments.length; i < c; i++) {
    args.push(arguments[i]);
  }
  return function() {
    fn.apply(obj, args);
  };
}
function addCopyrightInfo_(cpArray, mapService, map) {
  if (mapService.hasLoaded()) {
    cpArray.push(mapService.copyrightText);
  } else {
    G.event.addListenerOnce(mapService, 'load', function() {
      setCopyrightInfo_(map);
    });
  }
}
/**
 * Find copyright control in the map
 * @param {Object} map
 */
function setCopyrightInfo_(map) {
  var div = null;
  if (map) {
    var mvc = map.controls[G.ControlPosition.BOTTOM_RIGHT];
    if (mvc) {
      for (var i = 0, c = mvc.getLength(); i < c; i++) {
        if (mvc.getAt(i).id === 'agsCopyrights') {
          div = mvc.getAt(i);
          break;
        }
      }
    }
    //var callback = callback_(setCopyrightInfo_, null, map);
    if (!div) {
      div = document.createElement('div');
      div.style.fontFamily = 'Arial,sans-serif';
      div.style.fontSize = '10px';
      div.style.textAlign = 'right';
      div.id = 'agsCopyrights';
      map.controls[G.ControlPosition.BOTTOM_RIGHT].push(div);
      G.event.addListener(map, 'maptypeid_changed', function() {
        setCopyrightInfo_(map);
      });
    }
    var ovs = map.agsOverlays;
    var cp = [];
    var svc, type;
    if (ovs) {
      for (var i = 0, c = ovs.getLength(); i < c; i++) {
        addCopyrightInfo_(cp, ovs.getAt(i).mapService_, map);
      }
    }
    var ovTypes = map.overlayMapTypes;
    if (ovTypes) {
      for (var i = 0, c = ovTypes.getLength(); i < c; i++) {
        type = ovTypes.getAt(i);
        if (type instanceof MapType) {
          for (var j = 0, cj = type.tileLayers_.length; j < cj; j++) {
            addCopyrightInfo_(cp, type.tileLayers_[j].mapService_, map);
          }
        }
      }
    }
    type = map.mapTypes.get(map.getMapTypeId());
    if (type instanceof MapType) {
      for (var i = 0, c = type.tileLayers_.length; i < c; i++) {
        addCopyrightInfo_(cp, type.tileLayers_[i].mapService_, map);
      }
      if (type.negative) {
        div.style.color = '#ffffff';
      } else {
        div.style.color = '#000000';
      }
    }
    div.innerHTML = cp.join('<br/>');
  }
}
function getJSON_(url, params, callbackName, callbackFn) {
  var sid = 'ags_jsonp_' + (jsonpID_++) + '_' + Math.floor(Math.random() * 1000000);
  var script = null;
  params = params || {};
 // AGS10.1 escapes && so had to take it off.
  params[callbackName || 'callback'] = 'ags_jsonp.' + sid;
  var query = formatParams_(params);
  var head = document.getElementsByTagName("head")[0];
  if (!head) {
    throw new Error("document must have header tag");
  }
  var jsonpcallback = function() {
    if (window['ags_jsonp'][sid]) {
      delete window['ags_jsonp'][sid]; //['ags_jsonp']
    }
    if (script) {
      head.removeChild(script);
    }
    script = null;
    callbackFn.apply(null, arguments);
    /**
     * This event is fired after a REST JSONP response was returned by server.
     * @name Util#jsonpend
     * @param {String} scriptID
     * @event
     */
    triggerEvent_(Util, 'jsonpend', sid);
  };
  window['ags_jsonp'][sid] = jsonpcallback;
  
  if ((query + url).length < 2000 && !Config.alwaysUseProxy) {
    script = document.createElement("script");
    script.src = url + (url.indexOf('?') === -1 ? '?' : '&') + query;
    script.id = sid;
    head.appendChild(script);
  } else {
    // check if same host
    var loc = window.location;
    var dom = loc.protocol + '//' + loc.hostname + (!loc.port || loc.port === 80 ? '' : ':' + loc.port + '/');
    var useProxy = true;
    if (url.toLowerCase().indexOf(dom.toLowerCase()) !== -1) {
      useProxy = false;
    }
    if (Config.alwaysUseProxy) {
      useProxy = true;
    }
    if (useProxy && !Config.proxyUrl) {
      throw new Error('No proxyUrl property in Config is defined');
    }
    var xmlhttp = getXmlHttp_();
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState === 4) {
        if (xmlhttp.status === 200) {
          eval(xmlhttp.responseText);
        } else {
          throw new Error("Error code " + xmlhttp.status);
        }
      }
    };
    xmlhttp.open('POST', useProxy ? Config.proxyUrl + '?' + url : url, true);
    xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xmlhttp.send(query);
  }
  /**
   * This event is fired before a REST request sent to server.
   * @name Util#jsonpstart
   * @param {String} scriptID
   * @event
   */
  triggerEvent_(Util, 'jsonpstart', sid);
  return sid;
}
  
/**
 * Make Cross Domain Calls. This function returns the
 * script ID which can be used to track the requests. parameters:
 * <ul>
 * <li>url: url of server resource
 * <li>params: an object with name,value pairs. value must be string
 * <li>callbackName: Callback parameter name the server is expecting.e.g:'callback'
 * <li>callbackFn: the actual callback function.
 * </ul>
 * @param {String} url
 * @param {Object} params
 * @param {String} callbackName
 * @param {Function} callbackFn
 * @return {String} scriptID
 */
Util.getJSON = function(url, params, callbackName, callbackFn) {
  getJSON_(url, params, callbackName, callbackFn);
};


/**
 * Add a list of overlays to map
 * @param {google.maps.Map} map
 * @param {Array.OverlayView} overlays
 */
Util.addToMap = function(map, overlays) {
  if (isArray_(overlays)) {
    var ov;
    for (var i = 0, I = overlays.length; i < I; i++) {
      ov = overlays[i];
      if (isArray_(ov)) {
        Util.addToMap(map, ov);
      } else if (isOverlay_(ov)) {
        ov.setMap(map);
      }
    }
  }
};
  /**
   * Add a list of overlays to map
   * @param {Array.OverlayView} overlays
   * @param {Boolean} clearArray
   */
Util.removeFromMap = function(overlays, clearArray) {
  Util.addToMap(null, overlays);
    if (clearArray) {
      overlays.length = 0;
    }
  };
  
  
/**
   * Create A Generic Spatial Reference Object
   * The <code>params </code> passed in constructor is a javascript object literal and depends on
   * the type of Coordinate System to construct.
   * @name SpatialReference
   * @class This  class (<code>SpatialReference</code>) is for coordinate systems that converts value
   * between geographic and real-world coordinates. The following classes extend this class:
   *    {@link Geographic}, {@link SphereMercator}, {@link LambertConformalConic}, and {@link TransverseMercator}.
   * @constructor
   * @property {Number} [wkid] well-known coodinate system id (EPSG code)
   * @property {String} [wkt] well-known coodinate system text
   * @param {Object} params
   */
  function SpatialReference(params) {
    params = params || {};
    this.wkid = params.wkid;
    this.wkt = params.wkt;
  }

  /**
   * Convert Lat Lng to real-world coordinates.
   * Note both input and output are array of [x,y], although their values in different units.
 * @param {Array.number} lnglat
 * @return {Array.number}
   */
SpatialReference.prototype.forward = function(lnglat) {
    return lnglat;
  };
  /**
   * Convert real-world coordinates  to Lat Lng.
   * Note both input and output are are array of [x,y], although their values are different.
 * @param {Array.number}  coords
 * @return {Array.number}
   */
SpatialReference.prototype.inverse = function(coords) {
    return coords;
  };
  /**
   * Get the map the periodicity in x-direction, in map units NOT pixels
 * @return {number} periodicity in x-direction
   */
SpatialReference.prototype.getCircum = function() {
    return 360;
  };
  /**
   * To JSON String
   * @return String
   */
SpatialReference.prototype.toJSON = function() {
    return '{' + (this.wkid ? ' wkid:' + this.wkid : 'wkt: \'' + this.wkt + '\'') + '}';
  };
  /**
   * Creates a Geographic Coordinate System. e.g.:<br/>
 * <code> var g2 = new Geographic({wkid:4326});
   * </code>
   * @name Geographic
   * @class This class (<code>Geographic</code>) will simply retuns same LatLng as Coordinates. 
   *   The <code>param</code> should have wkid property. Any Geographic Coordinate Systems (eg. WGS84(4326)) can 
   *   use this class As-Is. 
   *   <br/>Note:<b> This class does not support datum transformation</b>.
 * @constructor
   * @extends SpatialReference
   * @param {Object} params
   */
function Geographic (params) {
    params  = params || {};
  SpatialReference.call(this, params);
}

Geographic.prototype = new SpatialReference();

/**
 * Create a Lambert Conformal Conic Projection based Spatial Reference. The <code>params</code> passed in construction should
 * include the following properties:<code>
 * <br/>-wkid: well-known id
 * <br/>-semi_major:  ellipsoidal semi-major axis in meter
 * <br/>-unit: meters per unit
 * <br/>-inverse_flattening: inverse of flattening of the ellipsoid where 1/f  =  a/(a - b)
 * <br/>-standard_parallel_1: phi1, latitude of the first standard parallel
 * <br/>-standard_parallel_2: phi2, latitude of the second standard parallel
 * <br/>-latitude_of_origin: phi0, latitude of the false origin
 * <br/>-central_meridian: lamda0, longitude of the false origin  (with respect to the prime meridian)
 * <br/>-false_easting: FE, false easting, the Eastings value assigned to the natural origin
 * <br/>-false_northing: FN, false northing, the Northings value assigned to the natural origin
 * </code>
 * <br/> e.g. North Carolina State Plane NAD83 Feet: <br/>
 * <code> var ncsp82  = new LambertConformalConic({wkid:2264, semi_major: 6378137.0,inverse_flattening: 298.257222101,
 *   standard_parallel_1: 34.33333333333334, standard_parallel_2: 36.16666666666666,
 *   central_meridian: -79.0, latitude_of_origin: 33.75,false_easting: 2000000.002616666,
 *   'false_northing': 0, unit: 0.3048006096012192 }); </code>
 * @name LambertConformalConic
 * @class This class (<code>LambertConformalConic</code>) represents a Spatial Reference System based on <a target  = wiki href  = 'http://en.wikipedia.org/wiki/Lambert_conformal_conic_projection'>Lambert Conformal Conic Projection</a>. 
 * @extends SpatialReference
 * @constructor
 * @param {Object} params
 */
function LambertConformalConic(params) {
    //http://pubs.er.usgs.gov/djvu/PP/PP_1395.pdf
    //for NCSP83: GLatLng(35.102363,-80.5666)<  === > GPoint(1531463.95, 495879.744);
    params = params || {};
  SpatialReference.call(this, params);
    var f_i = params.inverse_flattening;
    var phi1 = params.standard_parallel_1 * RAD_DEG;
    var phi2 = params.standard_parallel_2 * RAD_DEG;
    var phi0 = params.latitude_of_origin * RAD_DEG;
    this.a_ = params.semi_major / params.unit;
    this.lamda0_ = params.central_meridian * RAD_DEG;
    this.FE_ = params.false_easting;
    this.FN_ = params.false_northing;
    
    var f = 1.0 / f_i; //e: eccentricity of the ellipsoid where e^2  =  2f - f^2 
    var es = 2 * f - f * f;
    this.e_ = Math.sqrt(es);
    var m1 = this.calc_m_(phi1, es);
    var m2 = this.calc_m_(phi2, es);
    var tF = this.calc_t_(phi0, this.e_);
    var t1 = this.calc_t_(phi1, this.e_);
    var t2 = this.calc_t_(phi2, this.e_);
    this.n_ = Math.log(m1 / m2) / Math.log(t1 / t2);
    this.F_ = m1 / (this.n_ * Math.pow(t1, this.n_));
    this.rho0_ = this.calc_rho_(this.a_, this.F_, tF, this.n_);
}
  
LambertConformalConic.prototype = new SpatialReference();
  /**
   * calc_m_
 * @param {number} phi
 * @param {number} es e square
   */
LambertConformalConic.prototype.calc_m_ = function(phi, es) {
    var sinphi = Math.sin(phi);
    return Math.cos(phi) / Math.sqrt(1 - es * sinphi * sinphi);
  };
  /**
   * calc_t_
   * @param {Object} phi
   * @param {Object} e
   */
LambertConformalConic.prototype.calc_t_ = function(phi, e) {
    var esp = e * Math.sin(phi);
    return Math.tan(Math.PI / 4 - phi / 2) / Math.pow((1 - esp) / (1 + esp), e / 2);
  };
  /**
   * calc_rho (15-7)_
   * @param {Object} a
   * @param {Object} F
   * @param {Object} t
   * @param {Object} n
   */
LambertConformalConic.prototype.calc_rho_ = function(a, F, t, n) {
    return a * F * Math.pow(t, n);
  };
  /**
   * calc_phi_
   * @param {Object} t_i
   * @param {Object} e
   * @param {Object} phi
   */
LambertConformalConic.prototype.calc_phi_ = function(t, e, phi) {
    var esp = e * Math.sin(phi);
    return Math.PI / 2 - 2 * Math.atan(t * Math.pow((1 - esp) / (1 + esp), e / 2));
  };
  /**
   * solve phi iteratively.
   * @param {Object} t_i
   * @param {Object} e
   * @param {Object} init
   */
LambertConformalConic.prototype.solve_phi_ = function(t_i, e, init) {
    // iteration
    var i = 0;
    var phi = init;
    var newphi = this.calc_phi_(t_i, e, phi);//this.
    while (Math.abs(newphi - phi) > 0.000000001 && i < 10) {
      i++;
      phi = newphi;
      newphi = this.calc_phi_(t_i, e, phi);//this.
    }
    return newphi;
  };
  /** 
   * see {@link SpatialReference}
 * @param {Array.number} lnglat
 * @return {Array.number}
   */
LambertConformalConic.prototype.forward = function(lnglat) {
    var phi = lnglat[1] * RAD_DEG;// (Math.PI / 180);
    var lamda = lnglat[0] * RAD_DEG;
    var t = this.calc_t_(phi, this.e_);
    var rho = this.calc_rho_(this.a_, this.F_, t, this.n_);
    var theta = this.n_ * (lamda - this.lamda0_);
    var E = this.FE_ + rho * Math.sin(theta);
    var N = this.FN_ + this.rho0_ - rho * Math.cos(theta);
    return [E, N];
  };
  /**
   * see {@link SpatialReference}
 * @param {Array.number}  coords
 * @return {Array.number}
   */
LambertConformalConic.prototype.inverse = function(coords) {
    var E = coords[0] - this.FE_;
    var N = coords[1] - this.FN_;
    var theta = Math.atan(E / (this.rho0_ - N));
    var rho = (this.n_ > 0 ? 1 : -1) * Math.sqrt(E * E + (this.rho0_ - N) * (this.rho0_ - N));
    var t = Math.pow((rho / (this.a_ * this.F_)), 1 / this.n_);
    var init = Math.PI / 2 - 2 * Math.atan(t);
    var phi = this.solve_phi_(t, this.e_, init);
    var lamda = theta / this.n_ + this.lamda0_;
    return [lamda / RAD_DEG, phi / RAD_DEG];
  };
  /**
   *  see {@link SpatialReference}
 *  @return {number}
   */
LambertConformalConic.prototype.getCircum = function() {
    return Math.PI * 2 * this.a_;
  };
		
/**
 * Create a Transverse Mercator Projection. The <code>params</code> passed in constructor should contain the 
 * following properties: <br/>
 * <code>
 * <br/>-wkid: well-known id
 * <br/>-semi_major:  ellipsoidal semi-major axis in meters
 * <br/>-unit: meters per unit
 * <br/>-inverse_flattening: inverse of flattening of the ellipsoid where 1/f  =  a/(a - b)
 * <br/>-Scale Factor: scale factor at origin
 * <br/>-latitude_of_origin: phi0, latitude of the false origin
 * <br/>-central_meridian: lamda0, longitude of the false origin  (with respect to the prime meridian)
 * <br/>-false_easting: FE, false easting, the Eastings value assigned to the natural origin 
 * <br/>-false_northing: FN, false northing, the Northings value assigned to the natural origin 
 * </code>
 * <br/>e.g. Georgia West State Plane NAD83 Feet:  
 * <br/><code> var gawsp83  = new TransverseMercator({wkid: 102667, semi_major:6378137.0,
 *  inverse_flattening:298.257222101,central_meridian:-84.16666666666667, latitude_of_origin: 30.0,
 *  scale_factor:0.9999, false_easting:2296583.333333333, false_northing:0, unit: 0.3048006096012192});
 *  </code>
 * @param {Object} params 
 * @name TransverseMercator
 * @constructor
 * @class This class (<code>TransverseMercator</code>) represents a Spatial Reference System based on 
 * <a target  = wiki href  = 'http://en.wikipedia.org/wiki/Transverse_Mercator_projection'>Transverse Mercator Projection</a>
 * @extends SpatialReference
 */
function TransverseMercator(params) {
    params = params || {};
  SpatialReference.call(this, params);
    //GLatLng(33.74561,-84.454308)<  === >  GPoint(2209149.07977075, 1362617.71496891);
    this.a_ = params.semi_major / params.unit;//this.
    var f_i = params.inverse_flattening;
    this.k0_ = params.scale_factor;
    var phi0 = params.latitude_of_origin * RAD_DEG;//(Math.PI / 180);
    this.lamda0_ = params.central_meridian * RAD_DEG;
    this.FE_ = params.false_easting;//this.
    this.FN_ = params.false_northing;//this.
    var f = 1.0 / f_i;//this.
    /*e: eccentricity of the ellipsoid where e^2  =  2f - f^2 */
    this.es_ = 2 * f - f * f;
    //var _e  =  Math.sqrt(this.es_);
    /* e^4 */
    this.ep4_ = this.es_ * this.es_;
    /* e^6 */
    this.ep6_ = this.ep4_ * this.es_;
    /* e'  second eccentricity where e'^2  =  e^2 / (1-e^2) */
    this.eas_ = this.es_ / (1 - this.es_);
    this.M0_ = this.calc_m_(phi0, this.a_, this.es_, this.ep4_, this.ep6_);
}
  
TransverseMercator.prototype = new SpatialReference();
  /**
   * calc_m_
   * @param {Object} phi
   * @param {Object} a
   * @param {Object} es
   * @param {Object} ep4
   * @param {Object} ep6
   */
TransverseMercator.prototype.calc_m_ = function(phi, a, es, ep4, ep6) {
    return a * ((1 - es / 4 - 3 * ep4 / 64 - 5 * ep6 / 256) * phi - (3 * es / 8 + 3 * ep4 / 32 + 45 * ep6 / 1024) * Math.sin(2 * phi) + (15 * ep4 / 256 + 45 * ep6 / 1024) * Math.sin(4 * phi) - (35 * ep6 / 3072) * Math.sin(6 * phi));
  };
  /**
   * see {@link SpatialReference}
 * @param {Array.number} lnglat
 * @return {Array.number}
   */
TransverseMercator.prototype.forward = function(lnglat) {
    var phi = lnglat[1] * RAD_DEG;// (Math.PI / 180);
    var lamda = lnglat[0] * RAD_DEG;//(Math.PI / 180);
    var nu = this.a_ / Math.sqrt(1 - this.es_ * Math.pow(Math.sin(phi), 2));
    var T = Math.pow(Math.tan(phi), 2);
    var C = this.eas_ * Math.pow(Math.cos(phi), 2);
    var A = (lamda - this.lamda0_) * Math.cos(phi);
    var M = this.calc_m_(phi, this.a_, this.es_, this.ep4_, this.ep6_);
    var E = this.FE_ + this.k0_ * nu * (A + (1 - T + C) * Math.pow(A, 3) / 6 + (5 - 18 * T + T * T + 72 * C - 58 * this.eas_) * Math.pow(A, 5) / 120);
    var N = this.FN_ + this.k0_ * (M - this.M0_) + nu * Math.tan(phi) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * Math.pow(A, 4) / 120 + (61 - 58 * T + T * T + 600 * C - 330 * this.eas_) * Math.pow(A, 6) / 720);
    return [E, N];
  };
  /**
   * see {@link SpatialReference}
 * @param {Array.number}  coords
 * @return {Array.number}
   */
TransverseMercator.prototype.inverse = function(coords) {
    var E = coords[0];
    var N = coords[1];
    var e1 = (1 - Math.sqrt(1 - this.es_)) / (1 + Math.sqrt(1 - this.es_));
    var M1 = this.M0_ + (N - this.FN_) / this.k0_;
    var mu1 = M1 / (this.a_ * (1 - this.es_ / 4 - 3 * this.ep4_ / 64 - 5 * this.ep6_ / 256));
    var phi1 = mu1 + (3 * e1 / 2 - 27 * Math.pow(e1, 3) / 32) * Math.sin(2 * mu1) + (21 * e1 * e1 / 16 - 55 * Math.pow(e1, 4) / 32) * Math.sin(4 * mu1) + (151 * Math.pow(e1, 3) / 6) * Math.sin(6 * mu1) + (1097 * Math.pow(e1, 4) / 512) * Math.sin(8 * mu1);
    var C1 = this.eas_ * Math.pow(Math.cos(phi1), 2);
    var T1 = Math.pow(Math.tan(phi1), 2);
    var N1 = this.a_ / Math.sqrt(1 - this.es_ * Math.pow(Math.sin(phi1), 2));
    var R1 = this.a_ * (1 - this.es_) / Math.pow((1 - this.es_ * Math.pow(Math.sin(phi1), 2)), 3 / 2);
    var D = (E - this.FE_) / (N1 * this.k0_);
    var phi = phi1 - (N1 * Math.tan(phi1) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * this.eas_) * Math.pow(D, 4) / 24 + (61 + 90 * T1 + 28 * C1 + 45 * T1 * T1 - 252 * this.eas_ - 3 * C1 * C1) * Math.pow(D, 6) / 720);
    var lamda = this.lamda0_ + (D - (1 + 2 * T1 + C1) * Math.pow(D, 3) / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * this.eas_ + 24 * T1 * T1) * Math.pow(D, 5) / 120) / Math.cos(phi1);
    return [lamda / RAD_DEG, phi / RAD_DEG];
  };
  /**
   * see {@link SpatialReference}
 * @return number
   */
TransverseMercator.prototype.getCircum = function() {
    return Math.PI * 2 * this.a_;
  };

/**
 * Creates a Spatial Reference based on Sphere Mercator Projection. 
 * The <code>params</code> passed in constructor should have the following properties:
 * <code><br/>-wkid: wkid
 * <br/>-semi_major:  ellipsoidal semi-major axis 
 * <br/>-unit: meters per unit
 * <br/>-central_meridian: lamda0, longitude of the false origin  (with respect to the prime meridian)
 * </code>
 * <br/>e.g. The "Web Mercator" used in ArcGIS Server:<br/>
 * <code> var web_mercator  = new SphereMercator({wkid: 102113,  semi_major:6378137.0,  central_meridian:0, unit: 1 });
 * </code>
 * @name SphereMercator
 * @class This class (<code>SphereMercator</code>) is the Projection Default Google Maps uses. It is a special form of Mercator.
 * @constructor
 * @param {Object} params 
 * @extends SpatialReference
 */
function SphereMercator(params) {
    /*  =========== parameters  =  ===================== */
  params = params || {};
  SpatialReference.call(this, params);
    this.a_ = (params.semi_major || 6378137.0) / (params.unit || 1);
    this.lamda0_ = (params.central_meridian || 0.0) * RAD_DEG;
}
  
SphereMercator.prototype = new SpatialReference();
  
  /**
   * See {@link SpatialReference}
 * @param {Array.number} lnglat
 * @return {Array.number}
   */
SphereMercator.prototype.forward = function(lnglat) {
    var phi = lnglat[1] * RAD_DEG;
    var lamda = lnglat[0] * RAD_DEG;
    var E = this.a_ * (lamda - this.lamda0_);
    var N = (this.a_ / 2) * Math.log((1 + Math.sin(phi)) / (1 - Math.sin(phi)));
    return [E, N];
  };
  /**
   * See {@link SpatialReference}
 * @param {Array.number}  coords
 * @return {Array.number}
   */
SphereMercator.prototype.inverse = function(coords) {
    var E = coords[0];
    var N = coords[1];
    var phi = Math.PI / 2 - 2 * Math.atan(Math.exp(-N / this.a_));
    var lamda = E / this.a_ + this.lamda0_;
   return [lamda / RAD_DEG, phi / RAD_DEG];
  };
  /**
   * See {@link SpatialReference}
   * @return {Number}
   */
  SphereMercator.prototype.getCircum = function () {
    return Math.PI * 2 * this.a_;
  };
  /**
 * Create a Albers Equal-Area Conic Projection based Spatial Reference. The <code>params</code> passed in construction should
 * include the following properties:<code>
 * <br/>-wkid: well-known id
 * <br/>-semi_major:  ellipsoidal semi-major axis in meter
 * <br/>-unit: meters per unit
 * <br/>-inverse_flattening: inverse of flattening of the ellipsoid where 1/f  =  a/(a - b)
 * <br/>-standard_parallel_1: phi1, latitude of the first standard parallel
 * <br/>-standard_parallel_2: phi2, latitude of the second standard parallel
 * <br/>-latitude_of_origin: phi0, latitude of the false origin
 * <br/>-central_meridian: lamda0, longitude of the false origin  (with respect to the prime meridian)
 * <br/>-false_easting: FE, false easting, the Eastings value assigned to the natural origin
 * <br/>-false_northing: FN, false northing, the Northings value assigned to the natural origin
 * </code>
 * <br/> e.g. 
 * <code> var albers  = new Albers({wkid:9999, semi_major: 6378206.4,inverse_flattening: 294.9786982,
 *   standard_parallel_1: 29.5, standard_parallel_2: 45.5,
 *   central_meridian: -96.0, latitude_of_origin: 23,false_easting: 0,
 *   'false_northing': 0, unit: 1 }); </code>
 * @name Albers
 * @class This class (<code>Albers</code>) represents a Spatial Reference System based on <a target=wiki href  = 'http://en.wikipedia.org/wiki/Albers_projection'>Albers Projection</a>. 
 * @extends SpatialReference
 * @constructor
 * @param {Object} params
 */
function Albers(params) {
    //http://pubs.er.usgs.gov/djvu/PP/PP_1395.pdf, page 101 &  292
    //for NAD_1983_Alaska_Albers: LatLng()<  === > Point();
    params = params || {};
  SpatialReference.call(this, params);
    var f_i = params.inverse_flattening;
    var phi1 = params.standard_parallel_1 * RAD_DEG;
    var phi2 = params.standard_parallel_2 * RAD_DEG;
    var phi0 = params.latitude_of_origin * RAD_DEG;
    this.a_ = params.semi_major / params.unit;
    this.lamda0_ = params.central_meridian * RAD_DEG;
    this.FE_ = params.false_easting;
    this.FN_ = params.false_northing;
    
    var f = 1.0 / f_i; //e: eccentricity of the ellipsoid where e^2  =  2f - f^2 
    var es = 2 * f - f * f;
    this.e_ = Math.sqrt(es);
    var m1 = this.calc_m_(phi1, es);
    var m2 = this.calc_m_(phi2, es);
    
    var q1 = this.calc_q_(phi1, this.e_);
    var q2 = this.calc_q_(phi2, this.e_);
    var q0 = this.calc_q_(phi0, this.e_);
    
    this.n_ = (m1 * m1 - m2 * m2) / (q2 - q1);
    this.C_ = m1 * m1 + this.n_ * q1;
    this.rho0_ = this.calc_rho_(this.a_, this.C_, this.n_, q0);
};
  
Albers.prototype = new SpatialReference();
  /**
   * calc_m_
 * @param {number} phi
 * @param {number} es e square
   */
Albers.prototype.calc_m_ = function(phi, es) {
    var sinphi = Math.sin(phi);
    return Math.cos(phi) / Math.sqrt(1 - es * sinphi * sinphi);
  };
  
  
  /**
   * formular (3-12) page 101
   * @param {Object} phi
   * @param {Object} e
   */
Albers.prototype.calc_q_ = function(phi, e) {
    var esp = e * Math.sin(phi);
    return (1 - e * e) * (Math.sin(phi) / (1 - esp * esp) - (1 / (2 * e)) * Math.log((1 - esp) / (1 + esp)));
  };
  
Albers.prototype.calc_rho_ = function(a, C, n, q) {
    return a * Math.sqrt(C - n * q) / n;
  };
    
Albers.prototype.calc_phi_ = function(q, e, phi) {
    var esp = e * Math.sin(phi);
    return phi + (1 - esp * esp) * (1 - esp * esp) / (2 * Math.cos(phi)) * (q / (1 - e * e) - Math.sin(phi) / (1 - esp * esp) + Math.log((1 - esp) / (1 + esp)) / (2 * e));
  };
  
Albers.prototype.solve_phi_ = function(q, e, init) {
    // iteration
    var i = 0;
    var phi = init;
    var newphi = this.calc_phi_(q, e, phi);
    while (Math.abs(newphi - phi) > 0.00000001 && i < 10) {
      i++;
      phi = newphi;
      newphi = this.calc_phi_(q, e, phi);
    }
    return newphi;
  };

  /** 
   * see {@link SpatialReference}
 * @param {Array.number} lnglat
 * @return {Array.number}
   */
Albers.prototype.forward = function(lnglat) {
    var phi = lnglat[1] * RAD_DEG;
    var lamda = lnglat[0] * RAD_DEG;
    var q = this.calc_q_(phi, this.e_);
    var rho = this.calc_rho_(this.a_, this.C_, this.n_, q);
    var theta = this.n_ * (lamda - this.lamda0_);
    var E = this.FE_ + rho * Math.sin(theta);
    var N = this.FN_ + this.rho0_ - rho * Math.cos(theta);
    return [E, N];
  };
  /**
   * see {@link SpatialReference}
 * @param {Array.number}  coords
 * @return {Array.number}
   */
Albers.prototype.inverse = function(coords) {
    var E = coords[0] - this.FE_;
    var N = coords[1] - this.FN_;
    var rho = Math.sqrt(E * E + (this.rho0_ - N) * (this.rho0_ - N)); 
    var adj = this.n_ > 0 ? 1 : -1;
    var theta = Math.atan(adj * E / (adj * this.rho0_  - adj * N));
    var q = (this.C_ - rho * rho * this.n_ * this.n_ / (this.a_ * this.a_)) / this.n_;
    var init = Math.asin(q / 2);
    var phi = this.solve_phi_(q, this.e_, init);
    var lamda = theta / this.n_ + this.lamda0_;
    return [lamda / RAD_DEG, phi / RAD_DEG];
  };
  /**
   *  see {@link SpatialReference}
 *  @return number
   */
Albers.prototype.getCircum = function() {
    return Math.PI * 2 * this.a_;
  };
  /**
   * See {@link SpatialReference}
 * @return {number}
   */
Albers.prototype.getCircum = function() {
    return Math.PI * 2 * this.a_;
  };
  
WGS84 = new Geographic({
  wkid: 4326
  });
NAD83 = new Geographic({
  wkid: 4269
  });
WEB_MERCATOR = new SphereMercator({
    wkid: 102113,
    semi_major: 6378137.0,
    central_meridian: 0,
    unit: 1
});
WEB_MERCATOR_AUX = new SphereMercator({
      wkid: 102100,
      semi_major: 6378137.0,
      central_meridian: 0,
      unit: 1
    });
	
  // declared early but assign here to avoid dependency error by jslint
  spatialReferences_ = {
    '4326': WGS84,
    '4269': NAD83,
    '102113': WEB_MERCATOR,
    '102100': WEB_MERCATOR_AUX
  };
  
SpatialReference.WGS84 = WGS84;
SpatialReference.NAD83 = NAD83;
//TODO: check advanced compile impact
SpatialReference.WEB_MERCATOR = WEB_MERCATOR;
SpatialReference.WEB_MERCATOR_AUX = WEB_MERCATOR_AUX;
    
  /**
   * <b> static</b> method. Call with Syntax <code>SpatialReference.register(..)</code>. 
   * Add A Spatial Reference to the internal collection of Spatial References.
   * the <code>wktOrSR</code> parameter can be String format of "well-known text" of the
   * Spatial Reference, or an instance of {@link SpatialReference}.
   * <br/><li> If passes in String WKT format, to be consistent, it should use the same format as listed
   * in <a  href  = 'http://edndoc.esri.com/arcims/9.2/elements/pcs.htm'>
   * ESRI documentation</a>. For example, add NC State Plane NAD83 as String:
   * <br/><code>
   * SpatialReference.register(2264,'PROJCS["NAD_1983_StatePlane_North_Carolina_FIPS_3200_Feet",
   * GEOGCS["GCS_North_American_1983",
   * DATUM["D_North_American_1983",
   * SPHEROID["GRS_1980",6378137.0,298.257222101]],
   * PRIMEM["Greenwich",0.0],
   * UNIT["Degree",0.0174532925199433]],
   * PROJECTION["Lambert_Conformal_Conic"],
   * PARAMETER["False_Easting",2000000.002616666],
   * PARAMETER["False_Northing",0.0],
   * PARAMETER["Central_Meridian",-79.0],
   * PARAMETER["Standard_Parallel_1",34.33333333333334],
   * PARAMETER["Standard_Parallel_2",36.16666666666666],
   * PARAMETER["Latitude_Of_Origin",33.75],
   * UNIT["Foot_US",0.3048006096012192]]');
   * <br/></code>
   * Note: only <b>Lambert Conformal Conic</b> and <b>Transverse Mercator</b> Projection
   * based Spatial References are supported if added via WKT String.
   * <br/><li> If passes in an instance of {@link SpatialReference}, it can be one of the
   * built in classes, or a class that extends SpatialReference. For example, add NC State Plane NAD83 as SR:
   * <br/><code>
   * SpatialReferences.register(2264: new LambertConformalConic({
   * wkid: 2264,
   * semi_major: 6378137.0,
   * inverse_flattening: 298.257222101,
   * standard_parallel_1: 34.33333333333334,
   * standard_parallel_2: 36.16666666666666,
   * central_meridian: -79.0,
   * latitude_of_origin: 33.75,
   * 'false_easting': 2000000.002616666,
   * 'false_northing': 0,
   * unit: 0.3048006096012192
   * });
   * <br/></code>
   * @static
   * @param {Number|String} wkid/wkt
   * @param {Object} wktOrSR
   * @return {SpatialReference} registered SR
   */
Util.registerSR = function(wkidt, wktOrSR) {
    var sr = spatialReferences_['' + wkidt];
    if (sr) {
      return sr;
    }
    if (wktOrSR instanceof SpatialReference) {
      spatialReferences_['' + wkidt] = wktOrSR;
      sr = wktOrSR;
      
    } else {
      var wkt = wktOrSR || wkidt; // only one param is passed in.
      var params = {
        'wkt': wkidt
      };
      if (wkidt === parseInt(wkidt, 10)) {
        params = {
          'wkid': wkidt
        };
      }
      var prj = extractString_(wkt, "PROJECTION[\"", "\"]");
      var spheroid = extractString_(wkt, "SPHEROID[", "]").split(",");
      if (prj !== "") {
        params.unit = parseFloat(extractString_(extractString_(wkt, "PROJECTION", ""), "UNIT[", "]").split(",")[1]);
        params.semi_major = parseFloat(spheroid[1]);
        params.inverse_flattening = parseFloat(spheroid[2]);
        params.latitude_of_origin = parseFloat(extractString_(wkt, "\"Latitude_Of_Origin\",", "]"));
        params.central_meridian = parseFloat(extractString_(wkt, "\"Central_Meridian\",", "]"));
        params.false_easting = parseFloat(extractString_(wkt, "\"False_Easting\",", "]"));
        params.false_northing = parseFloat(extractString_(wkt, "\"False_Northing\",", "]"));
      }
      switch (prj) {
      case "":
        sr = new SpatialReference(params);
        break;
      case "Lambert_Conformal_Conic":
        params.standard_parallel_1 = parseFloat(extractString_(wkt, "\"Standard_Parallel_1\",", "]"));
        params.standard_parallel_2 = parseFloat(extractString_(wkt, "\"Standard_Parallel_2\",", "]"));
        sr = new LambertConformalConic(params);
        break;
      case "Transverse_Mercator":
        params.scale_factor = parseFloat(extractString_(wkt, "\"Scale_Factor\",", "]"));
        sr = new TransverseMercator(params);
        break;
      case "Albers":
        params.standard_parallel_1 = parseFloat(extractString_(wkt, "\"Standard_Parallel_1\",", "]"));
        params.standard_parallel_2 = parseFloat(extractString_(wkt, "\"Standard_Parallel_2\",", "]"));
        sr = new Albers(params);
        break;
      // more implementations here.
      default:
        throw new Error(prj + "  not supported");
      }
      if (sr) {
        spatialReferences_['' + wkidt] = sr;
      }
    }
    
    return sr;
  };
  
  //end of projection related code//
/**
 * @name Error
   * @class Error returned from Server.
   * Syntax:
   * <pre>
   * {
   "error" : 
  {
    "code" : 500, 
    "message" : "Object reference not set to an instance of an object.", 
    "details" : [
      "'geometry' parameter is invalid"
    ]
  }
  }
  </pre>
   */
  /**
   * Create a ArcGIS service catalog instance using it's url:<code> http://&lt;host>/&lt;instance>/rest/services</code>
   * @name Catalog
   * @constructor
   * @class  The catalog resource is the root node and initial entry point into an ArcGIS Server host.
   * This resource represents a catalog of folders and services published on the host.
   *  @param {String} url
   * @property {String} [currentVersion] currentVersion
 * @property {Array.string} [folders] folders list
 * @property {Array.string} [services] list of services. Each has <code>name, type</code> property.
   */
  function Catalog(url) {
    this.url = url;
    var me = this;
    getJSON_(url, {}, '', function(json) {
      augmentObject_(json, me);
      /**
       * This event is fired when the catalog info is loaded.
       * @name Catalog#load
       * @event
       */
      triggerEvent_(me, 'load');
    });
  }

  /**
   * @name Field
   * @class This class represents a field in a {@link Layer}. It is accessed from
   * the <code> fields</code> property. There is no constructor for this class,
   *  use Object Literal.
   * @property {String} [name] field Name
   * @property {String} [type] field type (esriFieldTypeOID|esriFieldTypeString|esriFieldTypeInteger|esriFieldTypeGeometry}.
   * @property {String} [alias] field alias.
   * @property {Domain} [domain] domain
   * @property {Int} [length] length.
   */
  /**
   * Create a ArcGIS map Layer using it's url (http://[mapservice-url]/[layerId])
   * @name Layer
   * @class This class (<code>Layer</code>) The layer / table(v10+)
   *  resource represents a single layer / table in a map of a map service 
   *  published by ArcGIS Server.
   * @constructor
   * @param {String} url
   * @property {Number} [id] layer ID
   * @property {String} [name] layer Name
   * @property {String} [type] Feature Layer|Image Layer
   * @property {String} [description] description
   * @property {String} [definitionExpression] Layer definition.
   * @property {String} [geometryType] geometryType type(esriGeometryPoint|..), only available after load.
   * @property {String} [copyrightText] copyrightText, only available after load.
   * @property {Layer} [parentLayer] parent Layer {@link Layer}
   * @property {Boolean} [defaultVisibility] defaultVisibility
 * @property {Array.Layer} [subLayers] sub Layers. {@link Layer}.
   * @property {Boolean} [visibility] Visibility of this layer
   * @property {Number} [minScale] minScale
   * @property {Number} [maxScale] maxScale
   * @property {TimeInfo} [timeInfo] timeInfo
   * @property {DrawingInfo} [drawingInfo] rendering info See {@link DrawingInfo}
   * @property {Boolean} [hasAttachments] hasAttachments
   * @property {String} [typeIdField] typeIdField
 * @property {Array.Field} [fields] fields, only available after load. See {@link Field}
 * @property {Array.String} [types] subtypes: id, name, domains.
 * @property {Array.String} [relationships] relationships (id, name, relatedTableId)
   */
function Layer(url) {
    this.url = url;
    this.definition = null;
}

  /**
   * Load extra information such as it's fields from layer resource.
   * If opt_callback function will be called after it is loaded
   */
Layer.prototype.load = function() {
    var me = this;
    if (this.loaded_) {
      return;
    }
    getJSON_(this.url, {}, '', function (json) {
    augmentObject_(json, me);
      me.loaded_ = true;
      /**
       * This event is fired when layer's service info is loaded.
       * @name Layer#load
       * @event
       */
      triggerEvent_(me, 'load');
    });
  };

  
  /**
   * Whether the layer is viewable at given scale
   * @param {Number} scale
   * @return {Boolean}
   */
Layer.prototype.isInScale = function(scale) {
    // note if the layer's extra info is not loaded, it will return true
    if (this.maxScale && this.maxScale > scale) {
      return false;
    }
    if (this.minScale && this.minScale < scale) {
      return false;
    }
    return true;
  };
  /**
   * @name SpatialRelationship
   * @enum
   * @class This is actually a list of constants that represent spatial 
   * relationship types. 
   * @property {String} [INTERSECTS] esriSpatialRelIntersects 
   * @property {String} [CONTAINS] esriSpatialRelContains
   * @property {String} [CROSSES] esriSpatialRelCrosses
   * @property {String} [ENVELOPE_INTERSECTS] esriSpatialRelEnvelopeIntersects
   * @property {String} [INDEX_INTERSECTS] esriSpatialRelIndexIntersects
   * @property {String} [OVERLAPS] esriSpatialRelOverlaps
   * @property {String} [TOUCHES] esriSpatialRelTouches
   * @property {String} [WITHIN] esriSpatialRelWithin
  */
  var SpatialRelationship = {
    INTERSECTS: 'esriSpatialRelIntersects',
    CONTAINS: 'esriSpatialRelContains',
    CROSSES: 'esriSpatialRelCrosses',
    ENVELOPE_INTERSECTS: 'esriSpatialRelEnvelopeIntersects',
    INDEX_INTERSECTS: 'esriSpatialRelIndexIntersects',
    OVERLAPS: 'esriSpatialRelOverlaps',
    TOUCHES: 'esriSpatialRelTouches',
    WITHIN: 'esriSpatialRelWithin'
  };
  /**
   * @name QueryOptions
   * @class This class represent the parameters needed in an query operation for a {@link Layer}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/query.html'>Query Operation</a>.
   * @property {String} [text]  A literal search text. If the layer has a display field
   *   associated with it, the server searches for this text in this field.
   *   This parameter is a short hand for a where clause of:
   *   where [displayField]like '%[text]%'. The text is case sensitive.
   *   This parameter is ignored if the where parameter is specified.
   * @property {OverlayView|Array.OverlayView} [geometry] The geometry to apply as the spatial filter.
   * @property {SpatialRelationship} [spatialRelationship] The spatial relationship to be applied on the
   *    input geometry while performing the query. The supported spatial relationships
   *    include intersects, contains, envelope intersects, within, etc.
   *    The default spatial relationship is intersects. See {@link SpatialRelationship}
   * @property {String} [where] A where clause for the query filter. Any legal SQL where clause operating on the fields in the layer is allowed.
   * @property {Array.string} [outFields] The list of fields to be included in the returned resultset.
   * @property {Boolean} [returnGeometry] If true, If true, the resultset will include the geometries associated with each result.
   * @property {Array.number} [objectIds] The object IDs of this layer / table to be queried
   * @property {Number} [maxAllowableOffset] This option can be used to specify the maximum allowable offset  to be used for generalizing geometries returned by the query operation
   * @property {Boolean} [returnIdsOnly] If true, the response only includes an array of object IDs. Otherwise the response is a feature set. The default is false.
   * @property {OverlayOptions} [overlayOptions] See {@link OverlayOptions}
   */
  /**
   * @name ResultSet
   * @class This class represent the results of an query operation for a {@link Layer}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/query.html'>Query Operation</a>.
   * @property {String} [displayFieldName] display Field Name for layer
   * @property {Object} [fieldAliases] Field Name's Aliases. key is field name, value is alias.
   * @property {GemetryType} [geometryType] esriGeometryPoint | esriGeometryMultipoint | esriGeometryPolygon | esriGeometryPolyline
   * @property {Array.feature} [features] result as array of {@link Feature}
   * @property {String} [objectIdFieldName] objectIdFieldName when returnIdsOnly=true
   * @property {Array.int} [objectIds] objectIds when returnIdsOnly=true
   */
  /**
   * The query operation is performed on a layer resource. The result of this operation is a resultset resource that will be
   * passed in the callback function. param is an instance of {@link QueryOptions}
   * <br/>For more info see <a href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/query.html'>Query Operation</a>.
   * @param {QueryOptions} params
   * @param {Function} callback
   * @param {Function} errback
   */
  Layer.prototype.query = function(p, callback, errback) {
    if (!p) {
      return;
    }
    // handle text, where, relationParam, objectIds, maxAllowableOffset
    var params = augmentObject_(p, {});
    if (p.geometry && !isString_(p.geometry)) {
      params.geometry = fromOverlaysToJSON_(p.geometry);
      params.geometryType = getGeometryType_(p.geometry);
      params.inSR = 4326;
    }
    if (p.spatialRelationship) {
      params.spatialRel = p.spatialRelationship;
      delete params.spatialRelationship;
    }
    if (p.outFields && isArray_(p.outFields)) {
      params.outFields = p.outFields.join(',');
    }
    if (p.objectIds) {
      params.objectIds = p.objectIds.join(',');
    }
    if (p.time) {
      params.time = formatTimeString_(p.time, p.endTime);
    }
    params.outSR = 4326;
    params.returnGeometry = p.returnGeometry === false ? false : true;
    params.returnIdsOnly = p.returnIdsOnly === true ? true : false;
    delete params.overlayOptions;
    getJSON_(this.url + '/query', params, '', function(json) {
      parseFeatures_(json.features, p.overlayOptions);
      callback(json, json.error);
      handleErr_(errback, json);
    });
  };
  /**
   * @name QueryRelatedRecordsOptions
   * @class This class represent the parameters needed in an query related records operation for a {@link Layer}.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/queryrelatedrecords.html'>Query Related Records Operation</a>.
 * @property {Array.number} [objectIds] The object IDs of this layer / table to be queried
   * @property {Int} [relatioshipId] The ID of the relationship to be queried
 * @property {Array.string} [outFields] The list of fields to be included in the returned resultset. This list is a comma delimited list of field names.
   * @property {String} [definitionExpression]  The definition expression to be applied to the related table / layer. From the list of objectIds, only those records that conform to this expression will be returned.
   * @property {Boolean} [returnGeometry  = true] If true, the resultset will include the geometries associated with each result.
   * @property [Number] [maxAllowableOffset] This option can be used to specify the maximum allowable offset  to be used for generalizing geometries returned by the query operation
   * @property {Number} [outSR] The well-known ID of or the {@link SpatialReference} of the output geometries
   */
  /**
   * @name RelatedRecords
   * @class This class represent the results of an query related records operation for a {@link Layer}.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/queryrelatedrecords.html'>Query Operation</a>.
   * @property {String} [geometryType] esriGeometryPoint | esriGeometryMultipoint | esriGeometryPolygon | esriGeometryPolyline
   * @property {Object} [spatialReference] {@link SpatialReference}
   * @property {String} [displayFieldName] display Field Name for layer
 * @property {Array.object} [relatedRecordGroups] list of related records
   */
   /**
   * @name RelatedRecord
   * @class This class represent the result of an query related records operation for a {@link Layer}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/queryrelatedrecords.html'>Query Operation</a>.
   * @property {int} [objectId] objectid of original record
 * @property {Array.feature} [relatedRecords] list of {@link Feature}s.
   */
  /**
   * The query related records operation is performed on a layer / table resource. 
   * The result of this operation are featuresets grouped by source layer / table 
   * object IDs. Each featureset contains Feature objects including the values for 
   * the fields requested by the user. For related layers, if you request geometry 
   * information, the geometry of each feature is also returned in the featureset. 
   * For related tables, the featureset does not include geometries. 
   * @param {QueryRelatedRecordsParameters} params
   * @param {Function} callback
   * @param {Function} errback
   */
Layer.prototype.queryRelatedRecords = function(qparams, callback, errback) {
    if (!qparams) {
      return;
    } 
  var params = augmentObject_(qparams, {});
    params.f = params.f || 'json';
    if (params.outFields && !isString_(params.outFields)) {
      params.outFields = params.outFields.join(',');
    }
    params.returnGeometry = params.returnGeometry === false ? false : true;
    getJSON_(this.url + '/query', params, '', function (json) {
      handleErr_(errback, json);
      callback(json);
    });
  };
  
  /**
   * @name MapSerivceOptions
   * @class provides options to construct a {@link MapService}
   * @property {Number} delayLoad number of seconds to delay loading meta data on construction.
   */
  /**
   * Creates a MapService objects that can be used by UI components.
   * <ul><li> <code> url</code> (required) is the URL of the map servive, e.g. <code>
   * http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer</code>.
   * <ul/> Note the spatial reference of the map service must already exists
   * in the {@link spatialReferences_} if actual coordinates transformation is needed.
   * @name MapService
   * @class This class (<code>MapService</code>) is the core class for all map service operations.
   * It represents an ArcGIS Server map service that offer access to map and layer content
   * @constructor
   * @param {String} url
   * @property {String} [url] map service URL
   * @property {String} [serviceDescription] serviceDescription
   * @property {String} [mapName] map frame Name inside the map document
   * @property {String} [description] description
   * @property {String} [copyrightText] copyrightText
   * @property {Array.Layer} [layers] array of {@link Layer}s.
   * @property {Array.Layer} [tables] array of Tables of type {@link Layer}.
   * @property {SpatialReference} [spatialReference] see {@link SpatialReference}
   * @property {Boolean} [singleFusedMapCache] if map cache is singleFused
   * @property {TileInfo} [tileInfo] See {@link TileInfo}
   * @property {TimeInfo} [timeInfo] see {@link TimeInfo}
   * @property {String} [units] unit
   * @property {String} [supportedImageFormatTypes] supportedImageFormatTypes, comma delimited list.
   * @property {Object} [documentInfo] Object with the folloing properties: <code>Title, Author,Comments,Subject,Category,Keywords</code>
   */
  function MapService(url, opts) {
    this.url = url;
    this.loaded_ = false;
    var tks = url.split("/");
    this.name = tks[tks.length - 2].replace(/_/g, ' ');
    opts = opts || {};
    if (opts.delayLoad) {
      var me = this;
      window.setTimeout(function() {
        me.loadServiceInfo();
      }, opts.delayLoad * 1000);
    } else {
      this.loadServiceInfo();
    }
  }

  /**
   * Load serviceInfo
   */
  MapService.prototype.loadServiceInfo = function() {
    var me = this;
    getJSON_(this.url, {}, '', function(json) {
      me.init_(json);
    });
  };
  /**
   * initialize an ArcGIS Map Service from the meta data information.
   * The <code>json</code> parameter is the json object returned by Map Service.
   * @private
   * @param {Object} json
   */
  MapService.prototype.init_ = function (json) {
    var me = this;
    if (json.error) {
      throw new Error(json.error.message);
    }
    augmentObject_(json, this);
    if (json.spatialReference.wkt) {
      this.spatialReference = Util.registerSR(json.spatialReference.wkt);
    } else {
      this.spatialReference = spatialReferences_[json.spatialReference.wkid];
    }
    if (json.tables !== undefined) {
      // v10.0 +
      getJSON_(this.url + '/layers', {}, '', function (json2) {
        me.initLayers_(json2);
        // V10 SP1 
        getJSON_(me.url + '/legend', {}, '', function (json3){
          me.initLegend_(json3);
          me.setLoaded_();
        });
      });
    } else {
      // v9.3
      me.initLayers_(json);
      me.setLoaded_();
    }
  };
   
  MapService.prototype.setLoaded_ = function() {
    this.loaded_ = true;
    /**
     * This event is fired when the service and it's service info is loaded.
     * @name MapService#load
     * @event
     */
    triggerEvent_(this, "load");
  };
   /**
   * initialize an Layers.
   * The <code>json</code> parameter is the json object returned by Map Service or layers operation(v10+).
   * @private
   * @param {Object} json2
   */ 
  MapService.prototype.initLayers_ = function (json2) {
    var layers = [];
    var tables = [];
    this.layers = layers;
    if (json2.tables) {
      this.tables = tables;
    }
    var layer, i, c, info;
    for (i = 0, c = json2.layers.length; i < c; i++) {
      info = json2.layers[i];
      layer = new Layer(this.url + '/' + info.id);
      augmentObject_(info, layer);
      layer.visible = layer.defaultVisibility;
      layers.push(layer);
    }
    if (json2.tables) {
      for (i = 0, c = json2.tables.length; i < c; i++) {
        info = json2.tables[i];
        layer = new Layer(this.url + '/' + info.id);
        augmentObject_(info, layer);
        tables.push(layer);
      }
    }
    for (i = 0, c = layers.length; i < c; i++) {
      layer = layers[i];
      if (layer.subLayerIds) {
        layer.subLayers = [];
        for (var j = 0, jc = layer.subLayerIds.length; j < jc; j++) {
          var subLayer = this.getLayer(layer.subLayerIds[j]);
          layer.subLayers.push(subLayer);
          subLayer.parentLayer = layer;
        }
      }
    }
    
  };
  /**
   * initialize an Layers.
   * The <code>json</code> parameter is the json object returned by Map Service or layers operation(v10+).
   * @private
   * @param {Object} json2
   */ 
  MapService.prototype.initLegend_ = function(json3) {
    // if not AGS10 SP1, server will return error.
    var layers = this.layers;
    if (json3.layers) {
      var layer, i, c, info;
      for (i = 0, c = json3.layers.length; i < c; i++) {
        info = json3.layers[i];
        layer = layers[info.layerId]; // layers id should same as index.
        augmentObject_(info, layer);
      }
    }
  };
  /**
   * Get a map layer by it's name(String) or id (Number), return {@link Layer}.
   * @param {String|Number} nameOrId
   * @return {Layer}
   */
  MapService.prototype.getLayer = function (nameOrId) {
    var layers = this.layers;
    if (layers) {
      for (var i = 0, c = layers.length; i < c; i++) {
        if (nameOrId === layers[i].id) {
          return layers[i];
        }
        if (isString_(nameOrId) && layers[i].name.toLowerCase() === nameOrId.toLowerCase()) {
          return layers[i];
        }
      }
    }
    return null;
  };

  /**
   * Get the layer definitions.
   * @return {Object} key as id, value as string of definition expression.
   */
  MapService.prototype.getLayerDefs_ = function() {
    var ret = {};
    if (this.layers) {
      for (var i = 0, c = this.layers.length; i < c; i++) {
        var layer = this.layers[i];
        if (layer.definition) {
          ret[String(layer.id)] = layer.definition;
        }
      }
    }
    return ret;
  };
  /**
   * If the map service meta info is loaded
   * @return {Boolean}
   */
  MapService.prototype.hasLoaded = function () {
    return this.loaded_;
  }
  /**
   * get a  list of visible layer's Ids
   * @return {Array.number} null if not initialized
   */
  MapService.prototype.getVisibleLayerIds_ = function () {
    var ret = [];
    if (this.layers) { // in case service not loaded_
      var layer;
      // a special behavior of REST (as of 9.3.1): 
      // if partial group then parent must be off
      var i, c;
      for (i = 0, c = this.layers.length; i < c; i++) {
        layer = this.layers[i];
        if (layer.subLayers) {
          for (var j = 0, jc = layer.subLayers.length; j < jc; j++) {
            if (layer.subLayers[j].visible === false) {
              layer.visible = false;
              break;
            }
          }
        }
      }
      for (i = 0, c = this.layers.length; i < c; i++) {
        layer = this.layers[i];
        //2010-10-26: in AGS10, group layer behavior is opposite of 9.3.1. And UNDOUMENTED in REST API!
        if (layer.subLayers && layer.subLayers.length > 0) {
          continue;
        }
        if (layer.visible === true) {
          ret.push(layer.id);
        }
      }
    }
    return ret;
  };
  /**
   * get initial bounds of the map serivce
   * @return {google.maps.LatLngBounds}
   */
  MapService.prototype.getInitialBounds = function () {
    if (this.initialExtent) {
      this.initBounds_ = this.initBounds_ || fromEnvelopeToLatLngBounds_(this.initialExtent);
      return this.initBounds_;
    }
    return null;
  };
  /**
   * get full bounds of the map serivce
   * @return {google.maps.LatLngBounds}
   */
  MapService.prototype.getFullBounds = function () {
    if (this.fullExtent) {
      this.fullBounds_ = this.fullBounds_ || fromEnvelopeToLatLngBounds_(this.fullExtent)
      return this.fullBounds_;
    }
    return null;
  };
/**
 * @name ExportMapOptions
 * @class This class represent the parameters needed in an exportMap operation for a {@link MapService}.
  * <br/>For more info see <a  href='http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/export.html'>Export Operation</a>.
 * @property {Number} [width] width of image, in pixel;
 * @property {Number} [height] height of image, in pixel;
 * @property {SpatialReference} [imageSR] The well-known ID of the spatial reference of the exported image or instance of {@link SpatialReference}.
 * @property {String} [format  = png] The format of the exported image. png | png8 | png24 | jpg | pdf | bmp | gif | svg
 * @property {Number} [dpi] The dpi of the exported image, default 96
 * @property {Object} [layerDefinitions] Allows you to filter the features of individual layers in the exported map by specifying 
 *   definition expressions for those layers. Syntax: { "&lt;layerId1>" : "&lt;layerDef1>" , "&lt;layerId2>" : "&lt;layerDef2>" }
 *   key is layerId returned by server, value is definition for that layer.
 * @property {Array.number} [layerIds] list of layer ids. If not specified along with layerOptions, show list of visible layers. 
 * @property {String} [layerOptions] show | hide | include | exclude. If not specified with along layerIds, show list of visible layers. 
 * @property {Boolean} [transparent  = true] If true, the image will be exported with 
 *  the background color of the map set as its transparent color. note the REST API default value is false.
 * @property {google.maps.LatLngBounds} [bounds] bounds of map
 * @property {Date} [time] The time instant the exported map image if the service supports time (since AGS10).
 * @property {Date} [endTime] The end time instant the exported map image if the service supports time (since AGS10).
 *  time=&lt;timeInstant> or time=&lt;startTime>, &lt;endTime>, e.g. time=1199145600000, 1230768000000 (1 Jan 2008 00:00:00 GMT to 1 Jan 2009 00:00:00 GMT) 
 * @property {Object} [layerTimeOptions] layerTimeOptions The time options per layer. Users can indicate whether or not the layer should use the time extent
 *  specified by the time parameter or not, whether to draw the layer 
 *  features cumulatively or not and the time offsets for the layer. Syntax: <pre>
 *  {
  "&lt;layerId1>" : {
    //If true, use the time extent specified by the time parameter
    "useTime" : &lt; true | false >,
    //If true, draw all the features from the beginning of time for that data
    "timeDataCumulative" : &lt; true | false >,
    //Time offset for this layer so that it can be overlaid on the top of a previous or future time period
    "timeOffset" : &lt;timeOffset1>,
    "timeOffsetUnits" : "&lt;esriTimeUnitsCenturies | esriTimeUnitsDays | esriTimeUnitsDecades | 
                             esriTimeUnitsHours | esriTimeUnitsMilliseconds | esriTimeUnitsMinutes | 
                             esriTimeUnitsMonths | esriTimeUnitsSeconds | esriTimeUnitsWeeks | esriTimeUnitsYears |
                             esriTimeUnitsUnknown>"
  },
  "&lt;layerId2>" : {
    "useTime" : &lt; true | false >,
    "timeDataCumulative" : &lt; true | false >,
    "timeOffsetOffset" : &lt;timeOffset2>,
    "timeOffsetUnits" : "&lt;timeOffsetUnits2>"
  }
}
</pre>
 */

/**
 * @name MapImage
 * @class This is the result of {@link MapService}.exportMap operation.
 *   There is no constructor, use as JavaScript object literal.
 * @property {String} [href] URL of image
 * @property {google.maps.LatLngBounds} [bounds] The bounding box of the exported image. 
 * @property {Number} [width] width of the exported image.
 * @property {Number} [height] height of the exported image.
 * @property {Number} [scale] scale of the exported image.
 */

  /**
   * Export an image with given parameters.
   * For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/export.html'>Export Operation</a>.
   * <br/> The <code>params</code> is an instance of {@link ExportMapOptions}.
   * The following properties will be set automatically if not specified:...
   * <br/> The <code>callback</code> is the callback function with argument of
   * an instance of {@link MapImage}.
   * @param {ExportMapOptions} params
   * @param {Function} callback
   * @param {Function} errback
   * @return {String|None} url of image if f=image, none if f=json
   */
  MapService.prototype.exportMap = function (p, callback, errback) {
    if (!p || !p.bounds) {
      return;
    }
    // note: dynamic map may overlay on top of maptypes with different projection
    var params = {};// augmentObject_(p, );
    params.f = p.f;
    var bnds = p.bounds;
    var swx = bnds.getSouthWest().lng();
    var nex = bnds.getNorthEast().lng();
    if (swx> nex){
      swx = swx-180;
  //    nex = nex +180;
    }
    params.bbox = '' + swx + ',' + '' + bnds.getSouthWest().lat() + ',' +
    nex +
    ',' +
    '' +
    bnds.getNorthEast().lat();
    //delete params.bounds;
    //log_('send '+bnds.toUrlValue());
    params.size = '' + p.width + ',' + p.height;
    params.dpi = p.dpi;
    
    if (p.imageSR) {
      if (p.imageSR.wkid) {
        params.imageSR = p.imageSR.wkid;
      } else {
        params.imageSR = '{wkt:' + p.imageSR.wkt + '}';
      }
    }
    params.bboxSR = '4326';
    params.format = p.format;
    var defs = p.layerDefinitions;
    // there is a slightly difference between {} and undefined
    // if do not want use def at all, pass in {}, if want to use 
    // in service, do not pass in anything.
    if (defs === undefined) {
      defs = this.getLayerDefs_();
    } 
    // for 9.3 compatibility:
    params.layerDefs = getLayerDefsString_(defs);
    var vlayers = p.layerIds;
    var layerOpt = p.layerOption || 'show';   
    if (vlayers === undefined) {
      vlayers = this.getVisibleLayerIds_();
    }
    if (vlayers.length > 0) {
      params.layers =  layerOpt + ':' + vlayers.join(',');
    } else {
      // no layers visible, no need to go to server, note if vlayers is null means not init yet in which case do not send layers 
      if (this.loaded_ && callback) {
        callback({
          href: null
        });
        return;
      }
      
    }
    params.transparent = (p.transparent === false ? false : true);
    if (p.time) {
      params.time = formatTimeString_(p.time, p.endTime);
    }
    //TODO: finish once v10 released
    params.layerTimeOptions = p.layerTimeOptions;
    
    if (params.f === 'image') {
      return this.url + '/export?' + formatParams_(params);
    } else {
      getJSON_(this.url + '/export', params, '', function (json) {
        if (json.extent) {
          json.bounds = fromEnvelopeToLatLngBounds_(json.extent);
          //log_('got '+json.bounds.toUrlValue());
          delete json.extent;
          callback(json); 
        } else {
          handleErr_(errback, json.error);  
        }
      });
    }
  };
 /**
 * @name Feature
 * @class This class represent JSON feature object as returned by the REST API.
 *   There is no constructor, use JavaScript object literal.
 * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/feature.html'>Feature Object</a>.
 * Syntax:
 * <pre>
{
  "geometry" : &lt;overlays>,
  "attributes" : {
    "name1" : &lt;value1>,
    "name2" : &lt;value2>,
  }
}
 * </pre>
 * @property {Array.OverlayView} [geometry] geometries. Array of Marker, Polyline or Polygon.
 * @property {Object} [attributes] attributes as name-value JSON object.
 */
  /**
   * @name IdentifyOptions
   * @class This class represent the parameters needed in an identify operation for a {@link MapService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/identify.html'>Identify Operation</a>.
   * @property {Geometry} [geometry] The geometry to identify on, <code>google.maps.LatLng</code>, <code>Polyline</code>, or <code>Polygon</code>.
   * @property {Array.number} [layerIds] The layers to perform the identify operation on. 
   * @property {String} [layerOption] The layers to perform the identify operation on. 'top|visible|all'. 
   * @property {Number} [tolerance] The distance in screen pixels from the specified geometry within which the identify should be performed
   * @property {google.maps.LatLngBounds} [bounds] The bounding box of the map currently being viewed.
   * @property {Number} [width] width of image in pixel
   * @property {Number} [height] height of image in pixel
   * @property {Number} [dpi] dpi of image, default 96;
   * @property {Boolean} [returnGeometry  = true] If true, the resultset will include the geometries associated with each result.
   * @property {Number} [maxAllowableOffset] This option can be used to specify the maximum allowable offset  to be used for generalizing geometries returned by the identify operation
   * @property {OverlayOptions} [overlayOptions] how results should be rendered. See {@link OverlayOptions}
   */
  /**
   * @name IdentifyResults
   * @class This class represent the results of an identify operation for
   * a {@link MapService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/identify.html'>Identify Operation</a>.
   * @property {Array.IdentifyResult} [results] The identify results as an array of {@link IdentifyResult}
   */
  /**
   * @name IdentifyResult
   * @class This class represent one entry in the results of an identify operation for a {@link MapService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/identify.html'>Identify Operation</a>.
   * @property {Number} [layerId] layerId
   * @property {String} [layerName] layerName
   * @property {String} [value] value of the display field
   * @property {String} [displayFieldName] displayFieldName
   * @property {Feature} [feature] {@link Feature}
   */
  /**
   * Identify features on a particular Geographic location, using {@link IdentifyOptions} and
   * process {@link IdentifyResults} using the <code>callback</code> function.
   * For more info see <a
   * href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/identify.html'>Identify Operation</a>.
   * @param {IdentifyOptions} params
   * @param {Function} callback
   * @param {Function} errback
   */
  MapService.prototype.identify = function (p, callback, errback) {
    if (!p) {
      return;
    }
    var params = {};//augmentObject_(p, );
    params.geometry = fromOverlaysToJSON_(p.geometry);
    params.geometryType = getGeometryType_(p.geometry);
    params.mapExtent = fromOverlaysToJSON_(p.bounds);
    params.tolerance = p.tolerance || 2;
    params.sr = 4326;
    params.imageDisplay = '' + p.width + ',' + p.height + ',' + (p.dpi || 96);
    params.layers = (p.layerOption || 'all');
    if (p.layerIds) {
      params.layers += ':' + p.layerIds.join(',');
    }
    if (p.layerDefs) {
      params.layerDefs = getLayerDefsString_(p.layerDefs);
    }
    params.maxAllowableOffset = p.maxAllowableOffset;
    params.returnGeometry = (p.returnGeometry === false ? false : true);
    
    getJSON_(this.url + '/identify', params, '', function (json) {
      // process results;
      var rets = null;
      var i, js, g;
      if (json.results) {
        rets = [];
        for (i = 0; i < json.results.length; i++) {
          js = json.results[i];
          g = fromJSONToOverlays_(js.geometry, p.overlayOptions);
          js.feature = {
            geometry: g,
            attributes: js.attributes
          };
          delete js.attributes;
        }
      }
      callback(json);
      handleErr_(errback, json);
    });
  };
  /**
   * @name FindOptions
   * @class This class represent the parameters needed in an find operation for a {@link MapService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/find.html'>Find Operation</a>.
   * @property {String} [searchText] The search string. This is the text that is searched across the layers and the fields that the user specifies.
   * @property {Boolean} [contains  = true] If false, the operation searches for an exact match of
   *   the searchText string. An exact match is case sensitive.
   *   Otherwise, it searches for a value that contains the searchText provided.
   *    This search is not case sensitive. The default is true.
   * @property {Array.string} [searchFields] The names of the fields to search. 
   *    If this parameter is not specified, all fields are searched.
   * @property {Array.number} [layerIds] The layer Ids to perform the find operation on. The layers to perform the find operation on.
   * @property {Boolean} [returnGeometry  = true] If true, the resultset will include the geometries associated with each result.
   * @property {Number} [maxAllowableOffset] This option can be used to specify the maximum allowable offset  to be used for generalizing
   *             geometries returned by the find operation 
   */
  /**
   * @name FindResults
   * @class This class represent the results of a find operation for a {@link MapService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/find.html'>Find Operation</a>.
   * @property {Array.FindResult} [results] The find results as an array of {@link FindResult}
   */
  /**
   * @name FindResult
   * @class This class represent one entry in the results of a find operation for a {@link MapService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/find.html'>Find Operation</a>.
   * @property {Number} [layerId] layerId
   * @property {String} [layerName] layerName
   * @property {String} [value] value of the display field
   * @property {String} [displayFieldName] displayFieldName
   * @property {String} [foundFieldName] foundFieldName
   * @property {String} [geometryType] esriGeometryPoint | esriGeometryPolyline | esriGeometryPolygon | esriGeometryEnvelope
   * @property {Feature} [feature] {@link Feature}
   */
  /**
   * Find features using the {@link FindOptions} and process {@link FindResults}
   * using the <code>callback</code> function.
   * For more info see <a
   * href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/find.html'>Find Operation</a>.
   * @param {FindOptions} opts
   * @param {Function} callback
   * @param {Function} errback
   */
  MapService.prototype.find = function (opts, callback, errback) {
    if (!opts) {
      return;
    }
    // handle searchText, contains, maxAllowableOffset
    var params = augmentObject_(opts, {});
    if (opts.layerIds) {
      params.layers = opts.layerIds.join(',');
      delete params.layerIds;
    }
    if (opts.searchFields) {
      params.searchFields = opts.searchFields.join(',');
    }
    params.contains = (opts.contains === false ? false : true);
    if (opts.layerDefinitions) {
      params.layerDefs = getLayerDefsString_(opts.layerDefinitions);
      delete params.layerDefinitions;
    }
    params.sr = 4326;
    params.returnGeometry = (opts.returnGeometry === false ? false : true);
    getJSON_(this.url + '/find', params, '', function (json) {
      var rets = null;
      var i, js, g;
      if (json.results) {
        rets = [];
        for (i = 0; i < json.results.length; i++) {
          js = json.results[i];
          g = fromJSONToOverlays_(js.geometry, opts.overlayOptions);
          js.feature = {
            'geometry': g,
            'attributes': js.attributes
          };
          delete js.attributes;
        }
      }
      callback(json);
      handleErr_(errback, json);
    });
  };
  
  /**
   * Query a layer with given id or name using the {@link QueryOptions} and process {@link ResultSet}
   * using the <code>callback</code> function.
   * See {@link Layer}.
   * For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/query.html'>Query Layer Operation</a>.
   * @param {Number|String} layerNameOrId
   * @param {QueryOptions} params
   * @param {Function} callback
   * @param {Function} errback
   */
  MapService.prototype.queryLayer = function (layerNameOrId, params, callback, errback) {
    var layer = this.getLayer(layerNameOrId);
    if (layer) {
      layer.query(params, callback, errback);
    }
  };
 
   /**
 * Creates a GeocodeService class.
 * Params:<li><code>url</code>: URL of service, syntax:<code>	http://{catalogurl}/{serviceName}/GeocodeServer</code>
 * @name GeocodeService
 * @class This class (<code>GeocodeService</code>) represent an ArcGIS <a href="http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/geocodeserver.html">GeocodeServer</a>
 *  service.
 * @constructor
 * @param {String} url
 * @property {String} [serviceDescription] serviceDescription
 * @property {Array.Field} [addressFields] input fields. 
 *    Each entry is an object of type {@link Field}, plus <code>required(true|false)</code>
 * @property {Array.Field} [candidateFields] candidate Fields. 
 *    Each entry is an object of type {@link Field}
 * @property {Array.Field} [intersectionCandidateFields] intersectionCandidateFields
 *    Each entry is an object of type {@link Field}
 * @property {SpatialReference} [spatialReference] spatialReference
 * @property {Object} [locatorProperties] an object with key-value pair that is specific to Locator type.
 */
  function GeocodeService(url) {
    this.url = url;
    this.loaded_ = false;
    var me = this;
    getJSON_(url, {}, '', function (json) {
      me.init_(json);
    });
  }
  
  /**
   * init
   * @param {Object} json
   */
  GeocodeService.prototype.init_ = function (json) {
    augmentObject_(json, this);
    if (json.spatialReference) {
      this.spatialReference = spatialReferences_[json.spatialReference.wkid || json.spatialReference.wkt] || WGS84;
    }
    this.loaded_ = true;
    /**
     * This event is fired when the service and it's service info is loaded.
     * @name GeocodeService#load
     * @event
     */
    triggerEvent_(this, 'load');
  };
  
  
/**
 * @name GeocodeOptions
 * @class This class represent the parameters needed in a find address candidate operation
 *  on a {@link GeocodeService}.
 *   There is no constructor, use JavaScript object literal.
 * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/candidates.html'>Find Adddress Candidates Operation</a>.
 * @property {Object} [inputs] an object literal with name-value pair of input values.
 * @property {Array.string} [outFields] The list of fields to be included in the returned resultset. 
 * @property {int|SpatialReference} [outSR] output SR, see {@link SpatialReference}
 */
/**
 * @name GeocodeResults
 * @class This class represent the results of an find address candidate operation for a 
 *  {@link GeocodeService}.
 *   There is no constructor, use JavaScript object literal.
 * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/candidates.html'>Find Adddress Candidates Operation</a>.
 * @property {Array.GeocodeResult} [candidates] The find address results as 
 * an array of {@link GeocodeResult}
 */
/**
 * @name GeocodeResult
 * @class This class represent one entry in the results of a find address operation for a
 *  {@link GeocodeService}.
 *   There is no constructor, use JavaScript object literal.
 * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/candidates.html'>Find Adddress Candidates Operation</a>.
 * @property {String} [address] matched address
 * @property {google.maps.LatLng} [location] matched location
 * @property {Number} [score] matching score
 * @property {Object} [attributes] attributes as name-value JSON object. 
 */
/**
 * The findAddressCandidates operation is performed on a geocode service
 *  resource. The result of this operation is a resource representing 
 *  the list of address candidates. This resource provides information 
 *  about candidates including the address, location, and score.
 *  param is an instance of {@link GeocodeOptions}. An instance of
 *  {@link GeocodeResults} will be passed into callback function.
 * @param {GeocodeOptions} params
 * @param {Function} callback
 * @param {Function} errback
 */
  GeocodeService.prototype.findAddressCandidates = function (gparams, callback, errback) {
    var params = augmentObject_(gparams, {});
    if (params.inputs) {
      augmentObject_(params.inputs, params);
      delete params.inputs;
    }
    if (isArray_(params.outFields)) {
      params.outFields = params.outFields.join(',');
    }
    //params.outSR = 4326;
    var me = this;
    getJSON_(this.url + '/findAddressCandidates', params, '', function (json) {
      if (json.candidates) {
        var res, loc;
        var cands = [];
        for (var i = 0; i < json.candidates.length; i++) {
          res = json.candidates[i];
          loc = res.location;
          if (!isNaN(loc.x) &&  !isNaN(loc.y)) {
            var ll = [loc.x, loc.y];
            // problem: AGS9.31 does not support outSR, so it wil be ignored.
            // however 10.0 does not return wkid in the result.
            // as compromise, use outSR in 10's request, not included in 9.3.
            var sr = me.spatialReference; 
            if (gparams.outSR){
               sr = spatialReferences_[gparams.outSR];  
            } 
            if (sr) ll = sr.inverse(ll);
            res.location = new G.LatLng(ll[1], ll[0]);
            cands[cands.length] = res;
          }
        }
      }
      callback({
        candidates:cands
      });
      handleErr_(errback, json);
    });
  };
  /**
   * Alias of <code>GeocodeService.findAddressCandidates</code>;
   * @param {GeocodeOptions} params
   * @param {Function} callback
   */
  GeocodeService.prototype.geocode = function (params, callback) {
    this.findAddressCandidates(params, callback);
  };

/**
 * @name ReverseGeocodeOptions
 * @class This class represent the parameters needed in a reverseGeocode operation
 *  on a {@link GeocodeService}.
 *   There is no constructor, use JavaScript object literal.
 * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/inverse.html'>Reverse Geocode Operation</a>.
 * @property {google.maps.LatLng} [location] an object literal of LatLng. 
 * @property {Number} [distance] The distance in meters from the given location within which 
 *  a matching address should be searched.
 */
/**
 * @name ReverseGeocodeResult
 * @class This class represent one entry in the results of a find address operation for a
 *  {@link GeocodeService}.
 *   There is no constructor, use JavaScript object literal.
 * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/inverse.html'>Reverse Geocode Operation</a>.
 * @property {Object} [address] matched address, object literal with name-value address parts. 
 * @property {google.maps.LatLng} [location] matched location
 */
/**
 * The reverseGeocode operation is The reverseGeocode operation is performed on a geocode service resource. 
 * The result of this operation is a inverse geocoded address resource.
 *  param is an instance of {@link ReverseGeocodeOptions}. An instance of
 *  {@link ReverseGeocodeResult} will be passed into callback function.
 * @param {ReverseGeocodeOptions} params
 * @param {Function} callback
 * @param {Function} errback
 */
  GeocodeService.prototype.reverseGeocode = function (params, callback, errback) {
    if (!isString_(params.location)) {
      params.location = fromOverlaysToJSON_(params.location);
    }
    params.outSR = 4326;
    var me = this;
    getJSON_(this.url + '/reverseGeocode', params, '', function (json) {
      if (json.location) {
        var loc = json.location;
        if (!isNaN(loc.x) && !isNaN(loc.y)) {
          var ll = [loc.x, loc.y];
          if (me.spatialReference) {
            ll = me.spatialReference.inverse(ll);
          }
          json.location = new G.LatLng(ll[1], ll[0]);
        }
      }
      callback(json);
      handleErr_(errback, json);
    });
  };
  
  //TODO: implement more Geometry operations
 /**
 * Creates an GeometryService class.
 * Params:<li><code>url</code>: URL of service, syntax:<code>	http://{catalogurl}/{serviceName}/GeometryServer</code>
 * @name GeometryService
 * @constructor
 * @class This class (<code>GeometryService</code>) represent an ArcGIS 
 * <a href="http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/geometryserver.html">Geometry</a>
 *  service.
 * @param {String} url
 */
  function GeometryService(url) {
    this.url  = url;
    this.t = 'geocodeservice';
  }
  
  /**
   * @name ProjectOptions
   * @class This class represent the parameters needed in an project operation
   *  for a {@link GeometryService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/project.html'>Project Operation</a>.
 * @property {Array.OverlayView|Array.object} [geometries] Array of <code>google.maps.LatLng, Polyline, Polygon</code>, or ESRI Geometry format to project.
   * @property {GeometryType} [geometryType] esriGeometryPoint | esriGeometryPolyline | esriGeometryPolygon | esriGeometryEnvelope
   * @property {SpatialReference} [inSpatialReference] The well-known ID of or the {@link SpatialReference} of the input geometries
   * @property {SpatialReference} [outSpatialReference] The well-known ID of or the {@link SpatialReference} of the out geometries
   */
  /**
   * @name ProjectResults
   * @class This class represent the parameters needed in an project operation
   *  for a {@link GeometryService}.
   *   There is no constructor, use JavaScript object literal.
   * <br/>For more info see <a  href  = 'http://sampleserver3.arcgisonline.com/ArcGIS/SDK/REST/project.html'>Project Operation</a>.
   * @property {Array.OverlayView|Array.object} [geometries] Array of <code>google.maps.LatLng, Polyline, Polygon<code>, or ESRI Geometry format to project. 
    */
  /**
   * This resource projects an array of input geometries from an input spatial reference
   * to an output spatial reference. Result of type {@link ProjectResults} is passed in callback function.
   * @param {ProjectOptions} params
   * @param {Function} callback
   * @param {Function} errback
   */
  GeometryService.prototype.project = function (p, callback, errback) {
    var params = prepareGeometryParams_(p);
    getJSON_(this.url + '/project', params, "callback", function (json) {
      var geom = [];
      if (p.outSpatialReference === 4326 || p.outSpatialReference.wkid === 4326) {
        for (var i = 0, c = json.geometries.length; i < c; i++) {
          geom.push(fromJSONToOverlays_(json.geometries[i]));
        }
        json.geometries = geom;
      }
      callback(json);
      handleErr_(errback, json);
    });
  };
 
 /**
  * Common units code in spatialReferences. Used in buffer operation.
  * This only has the most common units, for a full list of supported units, see 
  * <a href=http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriGeometry/esriSRUnitType.htm>esriSRUnitType</a>
  * and <a href=http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriGeometry/esriSRUnit2Type.htm>esriSRUnit2Type</a>
   * @enum {Number}
  * @property {Number} [METER] 9001 International meter.
  * @property {Number} [FOOT] 9002 International meter.
  * @property {Number} [SURVEY_FOOT] 9003 US survey foot.
  * @property {Number} [SURVEY_MILE] 9035 US survey mile.
  * @property {Number} [KILLOMETER] 9036 killometer.
  * @property {Number} [RADIAN] 9101 radian.
  * @property {Number} [DEGREE] 9102 degree.
  */
  var SRUnit = {
    METER: 9001,
    FOOT: 9002,
    SURVEY_FOOT: 9003,
    SURVEY_MILE: 9035,
    KILLOMETER: 9036,
    RADIAN: 9101,
    DEGREE: 9102
  };
  /**
   * @name BufferOptions
   * @class This class represent the parameters needed in an buffer operation
   *  for a {@link GeometryService}.
   * @property {Array.OverlayView|Array.object} [geometries] Array of <code>google.maps.LatLng</code>, <code>Polyline</code>, <code>Polygon</code>, or ESRI Geometry format to buffer. 
   * @property {SpatialReference} [bufferSpatialReference] The well-known ID of or the {@link SpatialReference} of the buffer geometries
   * @property {Array.number} [distances] The distances the input geometries are buffered.
   * @property {Number} [unit] see <a href='http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriGeometry/esriSRUnitType.htm'>esriSRUnitType Constants </a> .
   * @property {Boolean} [unionResults] If true, all geometries buffered at a given distance are unioned into a single (possibly multipart) polygon, and the unioned geometry is placed in the output array.
   * @property {OverlayOptions} [overlayOptions] how to render result overlay. See {@link OverlayOptions}
   */
  /**
   * @name BufferResults
   * @class This class represent the parameters needed in an project operation
   *  for a {@link GeometryService}.
   *   There is no constructor, use JavaScript object literal.
   * @property {Array.OverlayView|Array.object} [geometries] Array of <code>google.maps.LatLng, Polyline, Polygon</code>, or ESRI Geometry format to project. 
   */
  /**
   * This resource projects an array of input geometries from an input spatial reference
   * to an output spatial reference. Result of type {@link BufferResults} is passed in callback function.
   * @param {BufferOptions} params
   * @param {Function} callback. 
   * @param {Function} errback
   */
  GeometryService.prototype.buffer = function (p, callback, errback) {
    var params = prepareGeometryParams_(p);
    if (p.bufferSpatialReference) {
      params.bufferSR = formatSRParam_(p.bufferSpatialReference);
    }
    params.outSR = 4326;
    params.distances = p.distances.join(',');
    if (p.unit) {
      params.unit = p.unit;
    }
    getJSON_(this.url + '/buffer', params, "callback", function (json) {
      var geom = [];
      if (json.geometries) {
        for (var i = 0, c = json.geometries.length; i < c; i++) {
          geom.push(fromJSONToOverlays_(json.geometries[i], p['overlayOptions']));
        }
      }
      json.geometries = geom;
      callback(json);
      handleErr_(errback, json);
    });
  };
  /**
   * @name GPService
   * @class GPService
   * @constructor
   * @property {String} [serviceDescription]
   * @property {Array.string} [tasks]
   * @property {String} [executionType]
   * @property {String} [resultMapServerName]
   * @param {String} url http://[catalog-url]/[serviceName]/GPServer 
   */
  function GPService(url) {
    this.url = url;
    this.loaded_ = false;
    var me = this;
    getJSON_(url, {}, '', function (json) {
      augmentObject_(json, me);
      me.loaded_ = true;
      /**
     * This event is fired when the service and it's service info is loaded.
     * @name GPService#load
     * @event
     */
      triggerEvent_(me, 'load');
    });
  }

  /**
   * @name GPParameter
   * @property {String} [name]
   * @property {String} [dataType]
   * @property {String} [displayName]
   * @property {String} [direction]
   * @property {Object} [defaultValue]
   * @property {Object} [parameterType]
   * @property {String} [category]
   * @property {Array.object} [choiceList]
   */
  /**
   * @name GPTask
   * @class GPTask
   * @constructor
   * @property {String} [name]
   * @property {String} [displayName]
   * @property {String} [category]
   * @property {String} [helpUrl]
   * @property {String} [executionType]
   * @property {Array.GPParameter} [parameters] see {@link GPParameter}
   * @property {String} [name]
   * @property {String} [name]
   * @property {Array.string} [tasks]
   * @property {String} [resultMapServerName]
   * @param {String} url http://[catalog-url]/[serviceName]/GPServer 
   */
  function GPTask(url) {
    this.url = url;
    this.loaded_ = false;
    var me = this;
    getJSON_(url, {}, '', function (json) {
      augmentObject_(json, me);
      me.loaded_ = true;
      /**
     * This event is fired when the service and it's service info is loaded.
     * @name GPService#load
     * @event
     */
      triggerEvent_(me, 'load');
    });
  }
  /**
   * @name GPOptions
   * @property {Object} [parameters] name-value pair of params. 
   * @property {Number|SpatialReference} [outSpatialReference] 
   * @property {Number|SpatialReference} [processSpatialReference] 
   */
  /**
   * execute a GeoProcessing task
   * @param {GPOptions} p
   * @param {Function} callback will pass {@link GPResults} 
   * @param {Function} errback pass in {@link Error}
   */
  GPTask.prototype.execute = function (p, callback, errback) {
    var params = {};
    if (p.parameters) {
      augmentObject_(p.parameters, params);
    }
    if (p.outSpatialReference) {
      params['env:outSR'] = formatSRParam_(p.outSpatialReference);
    } else {
      params['env:outSR'] = 4326;
    }
    if (p.processSpatialReference) {
      params['env:processSR'] = formatSRParam_(p.processSpatialReference);
    } 
    getJSON_(this.url + '/execute', params, '', function (json) {
      if (json.results) {
        var res, f;
        for (var i = 0; i < json.results.length; i++) {
          res = json.results[i];
          if (res.dataType === 'GPFeatureRecordSetLayer') {
            for (var j = 0, J = res.value.features.length; j < J; j++) {
              f = res.value.features[j];
              if (f.geometry) {
                f.geometry = fromJSONToOverlays_(f.geometry, p.overlayOptions);
              }
            }
          }
        }
      }
      callback(json);
      handleErr_(errback, json);
    });
  };
  
  /**
   * @name GPResults
   * @property {Array.string} messages
   * @property {Array.GPResult} results
   */
  /**
   * @name GPResult
   * @property {String} paramName
   * @property {String} dataType
   * @property {Object} value
   */
  /**
   * @name NetworkService
   * @class NetworkService
   * @constructor
   * @property {String} serviceDescription
   * @property {Array.string} routeLayers
   * @property {Array.string} serviceAreaLayers
   * @property {Array.string} closestFacilityLayers
   * @param {String} url http://[catalog-url]/[serviceName]/NAServer 
   */
  function NetworkService(url) {
    this.url = url;
    this.loaded_ = false;
    var me = this;
  getJSON_(url, {}, '', function(json) {
      augmentObject_(json, me);
      me.loaded_ = true;
     /**
     * This event is fired when the service and it's service info is loaded.
     * @name NetworkService#load
     * @event
     */
      triggerEvent_(me, 'load');
    });
  }

  /**
   * @name RouteOptions
   * @class intance that specify how a route should be solved.
   * @property {Array.google.maps.LatLng|Array.Marker} [stops] the locations the route must pass
   * @property {Array.google.maps.LatLng|Array.Marker} [barriers] the locations the route must avoid
   * @property {Boolean} [returnDirections] If true, directions will be generated and returned with the analysis results. Default is true
   * @property {Boolean} [returnRoutes] If true, routes will be returned with the analysis results. Default is true. 
   * @property {Boolean} [findBestSequence] If true, the solver should resequence the route in the optimal order. The default is as defined in the network layer. 
   * @property {Boolean} [preserveFirstStop] If true, the solver should resequence the route in the optimal order. The default is as defined in the network layer. 
   * @property {Boolean} [preserveLastStop] If true, the solver should resequence the route in the optimal order. The default is as defined in the network layer. 
   */
  /**
   * @name RouteResults
   * @class intance that specify the results of the solve operation.
   * @property {Array.google.maps.LatLng} [stops]
   */
  /**
   * Create a route task with the URL of the routing server resource.
   * @name RouteTask 
   * @class This class (<code>RouteTask</code>) represent a Network Layer resource deployed in a NetWorkService.
   * It can solve a route based on stops, barrier
   * @constructor
   * @param {String} url
   */
  function RouteTask(url) {
    this.url = url;
  }
  /**
   * Solve a route based on inputs such as stops and barriers. Result of type {@link RouteResults} 
   * is passed to Function callback, and error of type {@link Error} is passed to Function errback.
   * @param {RouteOptions} opt_Route
   * @param {Function} callback
   * @param {Function} errback
   */
  RouteTask.prototype.solve = function (opts, callback, errback) {
    if (!opts) {
      return;
    }
    // handle many other fields
    var params = augmentObject_(opts, {});
    //params['outSR'] = WGS84.wkid;
    if (isArray_(opts.stops)) {
      params.stops = fromLatLngsToFeatureSet_(opts.stops);
    }
    if (isArray_(opts.barriers)) {
      if (opts.barriers.length > 0) {
        params.barriers = fromLatLngsToFeatureSet_(opts.barriers);
      } else {
        delete params.barriers;
      }
    }
    params.returnRoutes = (opts.returnRoutes === false ? false : true);
    params.returnDirections = (opts.returnDirections === true ? true : false);
    params.returnBarriers = (opts.returnBarriers === true ? true : false);
    params.returnStops = (opts.returnStops === true ? true : false);
    
    getJSON_(this.url + '/solve', params, '', function (json) {
      if (json.routes) {
        parseFeatures_(json.routes.features, opts.overlayOptions);
      }
      callback(json);
      handleErr_(errback, json);
    });
  };
  
  
  /**
   * @name OverlayOptions
   * @class Instance of this classes that specify how
   *   the geometry features returned by ArcGIS server should be rendered in the browser.
   * @property {google.maps.MarkerOptions} [markerOptions] style option for points.
   * @property {google.maps.PolylineOptions} [polylineOptions] style option for polylines. <a href=http://code.google.com/apis/maps/documentation/javascript/reference.html#PolylineOptions>PolylineOptions</a>
   * @property {google.maps.PolygonOptions} [polygonOptions] style option for polygons. <a href=http://code.google.com/apis/maps/documentation/javascript/reference.html#PolygonOptions>PolygonOptions</a>
   * @property {Number} [strokeOpacity] The stroke opacity between 0.0 and 1.0
   * @property {Number} [fillOpacity] The fill opacity between 0.0 and 1.0
   * @property {String} [strokeColor] The stroke color in HTML hex style, ie. "#FFAA00"
   * @property {String} [fillColor] The fill color in HTML hex style, ie. "#FFAA00"
   * @property {Number} [strokeWeight] The stroke width in pixels.
   * @property {Number} [zIndex] The zIndex compared to other overlays.
   * @property {String|google.maps.MarkerImage} [icon] Icon for the foreground
   * @property {String|google.maps.MarkerImage} [shadow] Shadow image
   */
  /**
   * @name TileInfo
   * @class This class contains information about map tile infornation for a cached map service.
   *    <br/>There is no constructor for this class.
   * @property {Number} [rows] tile row size,  e.g. 512, must be same as cols
   * @property {Number} [cols] tile cols size,  e.g. 512, must be same as rows
   * @property {Number} [dpi] dot per inch for map tiles.
   * @property {String} [format] PNG8 | PNG24 | PNG32 | GIF | JPEG
   * @property {Number} [compressionQuality] JPEG only.0-100.
   * @property {Point} [origin] origin of tile system of type
   * @property {SpatialReference} [spatialReference] spatial reference.  <b>wkid info only</b>.
   * @property {Array.LOD} [lods] Array of Level of Details. See {@link LOD}
   */
  /**
   * @name LOD
   * @class This class contains information about one "Level Of Detail" for a cached map service.
   *   It is the type of {@link lods} property of {@link TileInfo}
   *   <br/>There is no constructor for this class. Use as object literal.
   * @property {Number} [level] zoom level.
   * @property {Number} [resolution] map unit per pixel
   * @property {Number} [scale] actual map scale. e.g a value of 5000 means 1:5000 scale.
   */
  /**
   * Creates an ArcGIS Map Tiling Reference System.
   * <ul>
   * <li><code>tileInfo</code> tiling information. An instance of {@link TileInfo}
   * </ul>Applications normally do not create instances of this class directly.
   * @name Projection
   * @implements {google.maps.Projection}
   * @constructor
   * @class This class (<code>Projection</code>) implements a custom
   * <a href  = 'http://code.google.com/apis/maps/documentation/javascript/reference.html#Projection'>google.maps.Projection</a>
   * from the core Google Maps API.
   *   It includes a real {@link SpatialReference} object to convert LatLng from/to
   *   map coordinates, and tiling scheme informations to convert
   *   map coordinates from/to pixel coordinates.
   * @param {TileInfo} tileInfo
   */
  function Projection(tileInfo) {
    //if (!tileInfo) {
    //  throw new Error('map service is not tiled');
    //}
    this.lods_ = tileInfo ? tileInfo.lods : null;
    this.spatialReference_ = tileInfo ? spatialReferences_[tileInfo.spatialReference.wkid || tileInfo.spatialReference.wkt] : WEB_MERCATOR;
    if (!this.spatialReference_) {
      throw new Error('unsupported Spatial Reference');
    }
    // resolution (unit/pixel) at lod level 0. Due to changes from V2-V3, 
    // zoom is no longer defined in Projection. It is assumed that level's zoom factor is 2. 
    this.resolution0_ = tileInfo ? tileInfo.lods[0].resolution : 156543.033928;
    // zoom offset of this tileinfo's zoom 0 to Google's zoom0
    this.minZoom = Math.floor(Math.log(this.spatialReference_.getCircum() / this.resolution0_ / 256) / Math.LN2 + 0.5);
    this.maxZoom = tileInfo ? this.minZoom + this.lods_.length - 1 : 20;
    if (G.Size) {
      this.tileSize_ = tileInfo ? new G.Size(tileInfo.cols, tileInfo.rows) : new G.Size(256, 256);
    }
    // Find out how the map units scaled to 1 tile at zoom 0. 
    // from V2-V3, coords must scaled to 256 pixel under Mercator at zoom 0.
    // scale can be considered under this SR, what's the actual pixel number to 256 to cover whole earth?
    this.scale_ = Math.pow(2, this.minZoom) * this.resolution0_;
    this.originX_ = tileInfo ? tileInfo.origin.x : -20037508.342787;
    this.originY_ = tileInfo ? tileInfo.origin.y : 20037508.342787;
    // validation check
    if (tileInfo) {
      var ratio;
      for (var i = 0; i < tileInfo.lods.length - 1; i++) {
        ratio = tileInfo.lods[i].resolution / tileInfo.lods[i + 1].resolution;
        if (ratio > 2.001 || ratio < 1.999) {
          throw new Error('This type of map cache is not supported in V3. \nScale ratio between zoom levels must be 2');
        }
      }
    }
  }
  
  /**
   * See <a href  = 'http://code.google.com/apis/maps/documentation/javascript/reference.html#Projection'>google.maps.Projection</a>.
   * @param {LatLng} gLatLng
   * @param {Point} opt_point
   * @return {Point} pixel
   */
  Projection.prototype.fromLatLngToPoint = function (latlng, opt_point) {
    if (!latlng || isNaN(latlng.lat()) || isNaN(latlng.lng())) {
      return null;
    }
    var coords = this.spatialReference_.forward([latlng.lng(), latlng.lat()]);
    var point = opt_point || new G.Point(0, 0);
    point.x = (coords[0] - this.originX_) / this.scale_;
    point.y = (this.originY_ - coords[1]) / this.scale_;
    return point;
  };
  // somehow externs was ignored in adv mode.
  Projection.prototype['fromLatLngToPoint'] = Projection.prototype.fromLatLngToPoint;
  /**
   * See <a href  = 'http://code.google.com/apis/maps/documentation/javascript/reference.html#Projection'>google.maps.Projection</a>.
   * @param {Point} pixel
   * @param {Boolean} opt_nowrap
   * @return {LatLng}
   */
  Projection.prototype.fromPointToLatLng = function (pixel, opt_nowrap) {
    //TODO: handle nowrap
    if (pixel === null) {
      return null;
    }
    var x = pixel.x * this.scale_ + this.originX_;
    var y = this.originY_ - pixel.y * this.scale_;
    var geo = this.spatialReference_.inverse([x, y]);
    return new G.LatLng(geo[1], geo[0]);
  };
  //Projection.prototype['fromLatLngToPoint'] = Projection.prototype.fromLatLngToPoint;
  /**
   * Get the scale at given level;
   * @param {Number} zoom
   * @return {Number}
   */
  Projection.prototype.getScale = function (zoom) {
    var zoomIdx = zoom - this.minZoom;
    var res = 0;
    if (this.lods_[zoomIdx]) {
      res = this.lods_[zoomIdx].scale;
    }
    return res;
  };
  
  Projection.WEB_MECATOR = new Projection();
  /**
   * @name TileLayerOptions
   * @class Instances of this class are used in the {@link opt_layerOpts} argument
   *   to the constructor of the {@link TileLayer} class.
   * @property {String} [hosts] host pattern of tile servers if they are numbered. Most browser
   *   has default restrictions on how many concurrent connections can be made to
   *   a single host. One technique to workaround this is to create multiple hosts and rotate them when
   *   loading tiles.
   *   The syntax is <code>prefix[<i>numberOfHosts</i>]suffix</code>, for example, <code>"mt[4].google.com"</code> means
   *   rotate hosts in <code>mt0.google.com, mt1.google.com, mt2.google.com, mt3.google.com</code> (4 hosts).
   * @property {Number} [minZoom] min zoom level.
   * @property {Number} [maxZoom] max zoom level.
   * @property {Number} [opacity] opacity (0-1).
   */
  /** Creates a tile layer from a cached by ArcGIS map service. 
   * <br/> <code> service</code> (required) is the underline {@link MapService}
   * <br/> <code>opt_layerOpts</code> (optional) is an instance of {@link TileLayerOptions}.
   * @name TileLayer
   * @constructor
   * @class This class (<code>TileLayer</code>) provides access to a cached ArcGIS Server
   * map service. There is no <code>GTileLayer</code> class in Google Maps API V3, this class is kept to allow
   * finer control of zoom levels for each individual tile sets within a map type, such as zoom level range and opacity.
   * @param {MapService} service
   * @param {TileLayerOptions} opt_layerOpts
   */
  function TileLayer(service, opt_layerOpts) {
    opt_layerOpts = opt_layerOpts || {};
    if (opt_layerOpts.opacity) {
      this.opacity_ = opt_layerOpts.opacity;
      delete opt_layerOpts.opacity;
    }
    augmentObject_(opt_layerOpts, this);
    this.mapService_ = (service instanceof MapService) ? service : new MapService(service);
    //In the format of mt[number].domain.com
    if (opt_layerOpts.hosts) {
      var pro = extractString_(this.mapService_.url, '', '://');
      var host = extractString_(this.mapService_.url, '://', '/');
      var path = extractString_(this.mapService_.url, pro + '://' + host, '');
      this.urlTemplate_ = pro + '://' + opt_layerOpts.hosts + path;
      this.numOfHosts_ = parseInt(extractString_(opt_layerOpts.hosts, '[', ']'), 10);
    }
    this.name = opt_layerOpts.name || this.mapService_.name;
    this.maxZoom = opt_layerOpts.maxZoom || 19;
    this.minZoom = opt_layerOpts.minZoom || 0;
    this.dynaZoom = opt_layerOpts.dynaZoom || this.maxZoom;
    if (this.mapService_.loaded_) {
      this.init_(opt_layerOpts);
    } else {
      var me = this;
      G.event.addListenerOnce(this.mapService_, 'load', function () {
        me.init_(opt_layerOpts);
      });
    }
    this.tiles_ = {};
    this.map_ = opt_layerOpts.map;
  }
  
  /**
   * Initialize the tile layer from a loaded map service
   * @param {Object} opt_layerOpts
   */
  TileLayer.prototype.init_ = function (opt_layerOpts) {
    if (this.mapService_.tileInfo) {
      this.projection_ = new Projection(this.mapService_.tileInfo);
      this.minZoom = opt_layerOpts.minZoom || this.projection_.minZoom;
      this.maxZoom = opt_layerOpts.maxZoom || this.projection_.maxZoom;
    }
  };
  
  
  /**
   * Returns a string (URL) for given tile coordinate (x, y) and zoom level
   * @private not meant to be called by client
   * @param {Object} tile
   * @param {Number} zoom
   * @return {String} url
   */
  TileLayer.prototype.getTileUrl = function (tile, zoom) {
    var z = zoom - (this.projection_ ? this.projection_.minZoom : this.minZoom);
    var url = '';
    if (!isNaN(tile.x) && !isNaN(tile.y) && z >= 0 && tile.x >= 0 && tile.y >= 0) {
      var u = this.mapService_.url;
      if (this.urlTemplate_) {
        u = this.urlTemplate_.replace('[' + this.numOfHosts_ + ']', '' + ((tile.y + tile.x) % this.numOfHosts_));
      }
      var prj = this.projection_ || (this.map_ ? this.map_.getProjection() : Projection.WEB_MECATOR);
      if (!prj instanceof Projection) {
        // if use Google's image 
        prj = Projection.WEB_MECATOR;
      }
      var size = prj.tileSize_;
      var numOfTiles = 1 << zoom;
      var gworldsw = new G.Point(tile.x * size.width / numOfTiles, (tile.y + 1) * size.height / numOfTiles);
      var gworldne = new G.Point((tile.x + 1) * size.width / numOfTiles, tile.y * size.height / numOfTiles);
      var bnds = new G.LatLngBounds(prj.fromPointToLatLng(gworldsw), prj.fromPointToLatLng(gworldne));
      var fullBounds = this.mapService_.getFullBounds();
      if (this.mapService_.singleFusedMapCache === false || zoom > this.dynaZoom) {
        // dynamic map service
        var params = {
        'f': 'image'
        };
        params.bounds = bnds;
        params.format = 'png32';
        params.width = size.width;
        params.height = size.height;
        params.imageSR = prj.spatialReference_;
        url = this.mapService_.exportMap(params);
      } else if (fullBounds && !fullBounds.intersects(bnds)){
        url = '';
      } else {
        url = u + '/tile/' + z + '/' + tile.y + '/' + tile.x;
      }
    }
    //log_('url=' + url);
    return url;
  };
  /**
   * set Opacity
   * @param {Number} op (0-1)
   */
  TileLayer.prototype.setOpacity = function (op) {
    this.opacity_ = op;
    var tiles = this.tiles_;
    for (var x in tiles) {
      if (tiles.hasOwnProperty(x)) {
        setNodeOpacity_(tiles[x], op);
      }
    }
  };
  /**
   * get the opacity (0-1) of the tile layer
   * @return {Number}
   */
  TileLayer.prototype.getOpacity = function () {
    return this.opacity_;
  };
  /**
   * get the underline {@link MapService}
   * @return {MapService}
   */
  TileLayer.prototype.getMapService = function () {
    return this.mapService_;
  };
  /**
   * @name MapTypeOptions
   * @class Instance of this class are used in the {@link opt_typeOpts} argument
   *  to the constructor of the {@link MapType} class. See
   *  <a href=http://code.google.com/apis/maps/documentation/javascript/reference.html#MapType>google.maps.MapType</a>.
   * @property {String} [name] map type name
   * @property {Projection} [projection] an instance of {@link Projection}.
   * @property {String} [alt] Alt text to display when this MapType's button is hovered over in the MapTypeControl. Optional.
   * @property {Number} [maxZoom] The maximum zoom level for the map when displaying this MapType. Required for base MapTypes, ignored for overlay MapTypes.
   * @property {Number} [minZoom] The minimum zoom level for the map when displaying this MapType. Optional; defaults to 0.
   * @property {google.maps.Size} [tileSize] The dimensions of each tile.
   */
  // * @property {Number} [radius] Radius of the planet for the map, in meters. Optional; defaults to Earth's equatorial radius of 6378137 meters.
  /**
   * Creates a MapType, with a array of {@link TileLayer}s, or a single URL as shortcut.
   * @name MapType
   * @constructor
   * @class This class implements the Google Maps API's
   * <a href  = http://code.google.com/apis/maps/documentation/javascript/reference.html#MapType>GMapType</a>.
   * It holds a list of {@link TileLayer}s.
   * <p> Note: all tiled layer in the same map type must use same spatial reference and tile scheme.</p>
   * @param {Array.TileLayer|String} tileLayers
   * @param {MapTypeOptions} opt_typeOpts
   */
  function MapType(tileLayers, opt_typeOpts) {
    
    opt_typeOpts = opt_typeOpts || {};
    var i;
    if (opt_typeOpts.opacity) {
      this.opacity_ = opt_typeOpts.opacity;
      delete opt_typeOpts.opacity;
    }
    augmentObject_(opt_typeOpts, this);
    var layers = tileLayers;
    if (isString_(tileLayers)) {
      layers = [new TileLayer(tileLayers, opt_typeOpts)];
    } else if (tileLayers instanceof MapService) {
      layers = [new TileLayer(tileLayers, opt_typeOpts)];
    } else if (tileLayers instanceof TileLayer) {
      layers = [tileLayers];
    } else if (tileLayers.length > 0 && isString_(tileLayers[0])) {
      layers = [];
      for (i = 0; i < tileLayers.length; i++) {
        layers[i] = new TileLayer(tileLayers[i], opt_typeOpts);
      }
    }
    this.tileLayers_ = layers;
    this.tiles_ = {};
    if (opt_typeOpts.maxZoom !== undefined) {
      this.maxZoom = opt_typeOpts.maxZoom;
    } else {
      var maxZ = 0;
      for (i = 0; i < layers.length; i++) {
        maxZ = Math.max(maxZ, layers[i].maxZoom);
      }
      this.maxZoom = maxZ;
    }
    if (layers[0].projection_) {
      this.tileSize = layers[0].projection_.tileSize_;
      this.projection = layers[0].projection_;
    } else {
      this.tileSize = new G.Size(256, 256);
    }
    if (!this.name) {
      this.name = layers[0].name;
    }
    
  }
  
  /**
   * Get a tile for given tile coordinates Returns a tile for the given tile coordinate (x, y) and zoom level.
   * This tile will be appended to the given ownerDocument.
   * @private not meant to be called directly.
   * @param {Point} tileCoord
   * @param {Number} zoom
   * @return {Node}
   */
  MapType.prototype.getTile = function (tileCoord, zoom, ownerDocument) {
    var div = ownerDocument.createElement('div');
    var tileId = '_' + tileCoord.x + '_' + tileCoord.y + '_' + zoom;
    for (var i = 0; i < this.tileLayers_.length; i++) {
      var t = this.tileLayers_[i];
      if (zoom <= t.maxZoom && zoom >= t.minZoom) {
        var url = t.getTileUrl(tileCoord, zoom);
        if (url) {
          var img = ownerDocument.createElement(document.all ? 'img' : 'div');//IE does not like img
          img.style.border = '0px none';
          img.style.margin = '0px';
          img.style.padding = '0px';
          img.style.overflow = 'hidden';
          img.style.position = 'absolute';
          img.style.top = '0px';
          img.style.left = '0px';
          img.style.width = '' + this.tileSize.width + 'px';
          img.style.height = '' + this.tileSize.height + 'px';
          //log_(url);
          if (document.all) {
            img.src = url;
          } else {
            img.style.backgroundImage = 'url(' + url + ')';
          }
          div.appendChild(img);
          t.tiles_[tileId] = img;
          if (t.opacity_ !== undefined) {
            setNodeOpacity_(img, t.opacity_);
          } else if (this.opacity_ !== undefined) {
            // in FF it's OK to set parent div just once but IE does not like it.
            setNodeOpacity_(img, this.opacity_);
          }
        } else {
          // TODO: use a div to display NoData
        }
      }
    }
    this.tiles_[tileId] = div;
    div.setAttribute('tid', tileId);
    return div;
  };
  MapType.prototype['getTile'] = MapType.prototype.getTile;
  /**
   * Release tile and cleanup
   * @private not meant to be called directly.
   * @param {Node} node
   */
  MapType.prototype.releaseTile = function (node) {
    if (node.getAttribute('tid')) {
      var tileId = node.getAttribute('tid');
      if (this.tiles_[tileId]) {
        delete this.tiles_[tileId];
      }
      for (var i = 0; i < this.tileLayers_.length; i++) {
        var t = this.tileLayers_[i];
        if (t.tiles_[tileId]) {
          delete t.tiles_[tileId];
        }
      }
    }
  };
   MapType.prototype['releaseTile'] = MapType.prototype.releaseTile;
  /**
   * Set Opactity
   * @param {Number} op
   */
  MapType.prototype.setOpacity = function (op) {
    this.opacity_ = op;
    var tiles = this.tiles_;
    for (var x in tiles) {
      if (tiles.hasOwnProperty(x)) {
        var nodes = tiles[x].childNodes;
        for (var i = 0; i < nodes.length; i++) {
          setNodeOpacity_(nodes[i], op);
        }
      }
    }
  };
  /**
   * get opacity
   * @return {Number}
   */
  MapType.prototype.getOpacity = function () {
    return this.opacity_;
  };
  /**
   * get list of {@link TileLayer} in this map type
   * @return {Array.TileLayer}
   */
  MapType.prototype.getTileLayers = function () {
    return this.tileLayers_;
  };
 

  
  /**
   * @name MapOverlayOptions
   * @class Instance of this class are used in the {@link opt_ovelayOpts} argument
   *  to the constructor of the {@link MapOverlay} class.
   * @property {Number} [opacity  = 1.0] Opacity of map image from 0.0 (invisible) to 1.0 (opaque)
   * @property {ExportMapOptions} [exportOptions] See {@link ExportMapOptions}
   * @property {google.maps.Map} [map] map to attach to.
   */
  /**
   * Creates an Map Overlay using <code>url</code> of the map service and optional {@link MapOverlayOptions}.
   * <li/> <code> service</code> (required) is url of the underline {@link MapService} or the MapService itself.
   * <li/> <code>opt_overlayOpts</code> (optional) is an instance of {@link MapOverlayOptions}.
   * @name MapOverlay
   * @class This class (<code>MapOverlay</code>) extends the Google Maps API's
   * <a href  = http://code.google.com/apis/maps/documentation/reference.html#OverlayView>OverlayView</a>
   * that draws map images from data source on the fly. It is also known as "<b>Dynamic Maps</b>".
   * It can be added to the map via <code>setMap(map) </code> method.
   * The similar class in the core Map API is <a href  = http://code.google.com/apis/maps/documentation/javascript/reference.html#GroundOverlay>google.maps.GroundOverlay</a>,
   * however, the instance of this class always cover the viewport exactly, and will redraw itself as map moves.
   * @constructor
   * @param {String|MapService} service
   * @param {MapOverlayOptions} opt_overlayOpts
   */
  function MapOverlay(service, opt_overlayOpts) {
    opt_overlayOpts = opt_overlayOpts || {};
    this.mapService_ = (service instanceof MapService) ? service : new MapService(service);
    
    this.minZoom  = opt_overlayOpts.minZoom;
    this.maxZoom  = opt_overlayOpts.maxZoom;
    this.opacity_ = opt_overlayOpts.opacity || 1;
    this.exportOptions_ = opt_overlayOpts.exportOptions || {};
    this.drawing_ = false;
    // do we need another refresh. Normally happens bounds changed before server returns image.
    this.needsNewRefresh_ = false;
    this.overlay_ = null;
    this.div_ = null;
    // Once the LatLng and text are set, add the overlay to the map.  This will
    // trigger a call to panes_changed which should in turn call draw.
    if (opt_overlayOpts.map) {
      this.setMap(opt_overlayOpts.map);
    }
    this.map_ = null;
    this.listeners_= [];
  }

  MapOverlay.prototype = new G.OverlayView();
  /**
   * Called by API not by app code.
   * Handler when overlay is added. Interface method.
   * This will be called after setMap(map) is called.
   */
  MapOverlay.prototype.onAdd = function() {
    var me = this;
    this.listeners_.push(G.event.addListener(this.getMap(), 'bounds_changed', callback_(this.refresh, this)));
    this.listeners_.push(G.event.addListener(this.getMap(), 'dragstart', function(){
      me.dragging = true; 
    }));
    this.listeners_.push(G.event.addListener(this.getMap(), 'dragend', function(){
      me.dragging = false; 
    }));
    var map = this.getMap();
    map.agsOverlays = map.agsOverlays || new G.MVCArray();
    map.agsOverlays.push(this);
    setCopyrightInfo_(map);
    this.map_ = map;
  };
  MapOverlay.prototype['onAdd'] = MapOverlay.prototype.onAdd;
  /** 
   * Called by API not by app code.
   * Handler when overlay is removed.
   */
  MapOverlay.prototype.onRemove = function() {
    for (var i = 0, j = this.listeners_.length; i < j; i++){
      G.event.removeListener(this.listeners_[i]);
    }
    //G.event.removeListener(this.zoomChangedListener_);
    //this.div_.parentNode.removeChild(this.div_);
    //this.div_ = null;
    if (this.overlay_) this.overlay_.setMap(null);
    var map = this.map_;// getMap();
    var agsOvs = map.agsOverlays;
    if (agsOvs) {
      for (var i = 0, c = agsOvs.getLength(); i < c; i++) {
        if (agsOvs.getAt(i) == this) {
          agsOvs.removeAt(i);
          break;
        }
      }
    }
    setCopyrightInfo_(map);
    this.map_ = null;
  };
  MapOverlay.prototype['onRemove'] = MapOverlay.prototype.onRemove;
  /**
   * Called by API not by app code.
   * See OverlayView.draw in core API docs.
   */
  MapOverlay.prototype.draw = function () {
    if (!this.drawing_ || this.needsNewRefresh_ === true) {
      this.refresh(); 
    }
  };

  MapOverlay.prototype['draw'] = MapOverlay.prototype.draw;
  /**
   * Gets Image Opacity. return <code>opacity</code> between 0-1.
   * @return {Number} opacity
   */
  MapOverlay.prototype.getOpacity = function () {
    return this.opacity_;
  };
  /**
   * Sets Image Opacity. parameter <code>opacity</code> between 0-1.
   * @param {Number} opacity
   */
  MapOverlay.prototype.setOpacity = function (opacity) {
    var op = Math.min(Math.max(opacity, 0), 1);
    this.opacity_ = op;
    if (this.overlay_) {
      setNodeOpacity_(this.overlay_.div_, op);
    }
  };
  /**
   * Gets underline {@link MapService}.
   * @return {MapService} MapService
   */
  MapOverlay.prototype.getMapService = function () {
    return this.mapService_;
  };
  /**
   * Refresh the map image in current view port.
   */
  MapOverlay.prototype.refresh = function () {
    if (this.drawing_ === true) {
      this.needsNewRefresh_ = true;
      return;
    }
    var m = this.getMap();
    var bnds = m ? m.getBounds() : null;
    if (!bnds) {
      return;
    }
    var params = this.exportOptions_;
    params.bounds = bnds;
    var sr = WEB_MERCATOR;
    // V3 no map.getSize()
    var s = m.getDiv();
    params.width = s.offsetWidth;
    params.height = s.offsetHeight;
    if (s.offsetWidth == 0 || s.offsetHeight ==0){
      return;
    }
    var prj = m.getProjection(); // note this is not same as this.getProjection which returns MapCanvasProjection
    if (prj && prj instanceof Projection) {
      sr = prj.spatialReference_;
    }
    params.imageSR = sr;
    /**
     * This event is fired before the the drawing request was sent to server.
     * @name MapOverlay#drawstart
     * @event
     */
    triggerEvent_(this, 'drawstart');
    var me = this;
    this.drawing_ = true;
    if (!this.dragging && this.overlay_){
      this.overlay_.setMap(null);
      this.overlay_ = null;
    }
    //this.div_.style.backgroundImage = '';
    
    this.mapService_.exportMap(params, function (json) {
      me.drawing_ = false;
      
      if (me.needsNewRefresh_ === true) {
        me.needsNewRefresh_ = false;
        me.refresh();
        return;
      }
      if (json.href) {
        if (me.overlay_) {
          me.overlay_.setMap(null);
          me.overlay_ = null;
        }
       me.overlay_ = new ImageOverlay(json.bounds, json.href, me.map_, me.opacity_);
       
      }
      /**
       * This event is fired after the the drawing request was returned by server.
       * @name MapOverlay#drawend
       * @event
       */
      triggerEvent_(me, 'drawend');
    });
  };
  

  /**
   * Check if the overlay is visible, and within zoomzoom range and current map bounds intersects with it's fullbounds.
   * @return {Boolean} visible
   */
  MapOverlay.prototype.isHidden = function () {
    return !(this.visible_ && this.isInZoomRange_());
  };
  /**
   * If this in zoom range
   * @private
   * @return {Boolean}
   */
  MapOverlay.prototype.isInZoomRange_ = function () {
    var z = this.getMap().getZoom();
    if ((this.minZoom !== undefined && z < this.minZoom) ||
    (this.maxZoom !== undefined && z > this.maxZoom)) {
      return false;
    }
    return true;
  };
  
  /**
   * Makes the overlay visible.
   */
  MapOverlay.prototype.show = function () {
    this.visible_ = true;
    this.div_.style.visibility = 'visible';
    this.refresh();
  };
  /**
   * Hide the overlay
   */
  MapOverlay.prototype.hide = function () {
    this.visible_ = false;
    this.div_.style.visibility = 'hidden';
  };
  
  /**
   * @class simply an image overaly. Added due to some unknown problems related to 
   * overlayLayer pane after bounds change since gmaps API v3.4. 
   * this class is based on sample code USGSOverlay
   * @constructor
   * @param {Object} bounds
   * @param {Object} url
   * @param {Object} map
   */
  function ImageOverlay(bounds, url, map, op) {
    this.bounds_ = bounds;
    this.url_ = url;
    this.map_ = map;
    this.div_ = null;
    this.op_ = op;
    this.setMap(map);
  }
  
  ImageOverlay.prototype = new G.OverlayView();
  ImageOverlay.prototype.onAdd = function() {
    var div = document.createElement('DIV');
    div.style.border = "none";
    div.style.borderWidth = "0px";
    div.style.position = "absolute";
    var s = this.map_.getDiv();
    div.style.width = s.offsetWidth + 'px';
    div.style.height =  s.offsetHeight + 'px';
    
    div.style.backgroundImage = 'url(' + this.url_ + ')';
    
    // Set the overlay's div_ property to this DIV
    this.div_ = div;
    
    // We add an overlay to a map via one of the map's panes.
    // We'll add this overlay to the overlayImage pane.
    var panes = this.getPanes();
    setNodeOpacity_(div, this.op_);
    panes.overlayLayer.appendChild(div);
  };
  ImageOverlay.prototype.draw = function() {
  
    // Size and position the overlay. We use a southwest and northeast
    // position of the overlay to peg it to the correct position and size.
    // We need to retrieve the projection from this overlay to do this.
    var overlayProjection = this.getProjection();
    
    // Retrieve the southwest and northeast coordinates of this overlay
    // in latlngs and convert them to pixels coordinates.
    // We'll use these coordinates to resize the DIV.
    var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
    var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
    
    // Resize the image's DIV to fit the indicated dimensions.
    var div = this.div_;
    div.style.left = sw.x + 'px';
    div.style.top = ne.y + 'px';
    //div.style.width = (ne.x - sw.x) + 'px';
    //div.style.height = (sw.y - ne.y) + 'px';
  };
  ImageOverlay.prototype.onRemove = function() {
    this.div_.parentNode.removeChild(this.div_);
    this.div_ = null;
  }
  /**
 * Creates a copyright control
 * @name CopyrightControl
 * @class put a copyright notice at bottom rigth corner.
 * @constructor
 * @param {google.maps.Map} map
 */
  function CopyrightControl(map){
    // reason to put div creation out is allow MapOverlay tigger it if this control is not created.
    this.map_ = map;
    setCopyrightInfo_(map);
  }
  
  /**
   * refresh copyright text 
   */
  CopyrightControl.prototype.refresh = function(){
    setCopyrightInfo_(this.map_);
  };
  
  gmaps.ags = {
    SpatialReference: SpatialReference,
    Geographic: Geographic,
    LambertConformalConic: LambertConformalConic,
    SphereMercator: SphereMercator,
    TransverseMercator: TransverseMercator,
    SpatialRelationship: SpatialRelationship,
    GeometryType: GeometryType,
    SRUnit : SRUnit,
    Catalog: Catalog,
    MapService: MapService,
    Layer: Layer,
    GeocodeService: GeocodeService,
    GeometryService: GeometryService,
    GPService: GPService,
    GPTask: GPTask,
    RouteTask: RouteTask,
    Util: Util,
    Config: Config,
    Projection: Projection,
    TileLayer: TileLayer,
    MapOverlay: MapOverlay,
    MapType: MapType,
    CopyrightControl:CopyrightControl
  };