text: (function(EsriLeaflet){
  
    // shallow object clone for feature properties and attributes
    // from http://jsperf.com/cloning-an-object/2
    function clone(obj) {
      var target = {};
      for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
          target[i] = obj[i];
        }
      }
      return target;
    }
  
    // checks if 2 x,y points are equal
    function pointsEqual(a, b) {
      for (var i = 0; i < a.length; i++) {
        if (a[i] !== b[i]) {
          return false;
        }
      }
      return true;
    }
  
    // checks if the first and last points of a ring are equal and closes the ring
    function closeRing(coordinates) {
      if (!pointsEqual(coordinates[0], coordinates[coordinates.length - 1])) {
        coordinates.push(coordinates[0]);
      }
      return coordinates;
    }
  
    // determine if polygon ring coordinates are clockwise. clockwise signifies outer ring, counter-clockwise an inner ring
    // or hole. this logic was found at http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-
    // points-are-in-clockwise-order
    function ringIsClockwise(ringToTest) {
      var total = 0,i = 0;
      var rLength = ringToTest.length;
      var pt1 = ringToTest[i];
      var pt2;
      for (i; i < rLength - 1; i++) {
        pt2 = ringToTest[i + 1];
        total += (pt2[0] - pt1[0]) * (pt2[1] + pt1[1]);
        pt1 = pt2;
      }
      return (total >= 0);
    }
  
    // ported from terraformer.js https://github.com/Esri/Terraformer/blob/master/terraformer.js#L504-L519
    function vertexIntersectsVertex(a1, a2, b1, b2) {
      var uaT = (b2[0] - b1[0]) * (a1[1] - b1[1]) - (b2[1] - b1[1]) * (a1[0] - b1[0]);
      var ubT = (a2[0] - a1[0]) * (a1[1] - b1[1]) - (a2[1] - a1[1]) * (a1[0] - b1[0]);
      var uB  = (b2[1] - b1[1]) * (a2[0] - a1[0]) - (b2[0] - b1[0]) * (a2[1] - a1[1]);
  
      if ( uB !== 0 ) {
        var ua = uaT / uB;
        var ub = ubT / uB;
  
        if ( 0 <= ua && ua <= 1 && 0 <= ub && ub <= 1 ) {
          return true;
        }
      }
  
      return false;
    }
  
    // ported from terraformer.js https://github.com/Esri/Terraformer/blob/master/terraformer.js#L521-L531
    function arrayIntersectsArray(a, b) {
      for (var i = 0; i < a.length - 1; i++) {
        for (var j = 0; j < b.length - 1; j++) {
          if (vertexIntersectsVertex(a[i], a[i + 1], b[j], b[j + 1])) {
            return true;
          }
        }
      }
  
      return false;
    }
  
    // ported from terraformer.js https://github.com/Esri/Terraformer/blob/master/terraformer.js#L470-L480
    function coordinatesContainPoint(coordinates, point) {
      var contains = false;
      for(var i = -1, l = coordinates.length, j = l - 1; ++i < l; j = i) {
        if (((coordinates[i][1] <= point[1] && point[1] < coordinates[j][1]) ||
             (coordinates[j][1] <= point[1] && point[1] < coordinates[i][1])) &&
            (point[0] < (coordinates[j][0] - coordinates[i][0]) * (point[1] - coordinates[i][1]) / (coordinates[j][1] - coordinates[i][1]) + coordinates[i][0])) {
          contains = !contains;
        }
      }
      return contains;
    }
  
    // ported from terraformer-arcgis-parser.js https://github.com/Esri/terraformer-arcgis-parser/blob/master/terraformer-arcgis-parser.js#L106-L113
    function coordinatesContainCoordinates(outer, inner){
      var intersects = arrayIntersectsArray(outer, inner);
      var contains = coordinatesContainPoint(outer, inner[0]);
      if(!intersects && contains){
        return true;
      }
      return false;
    }
  
    // do any polygons in this array contain any other polygons in this array?
    // used for checking for holes in arcgis rings
    // ported from terraformer-arcgis-parser.js https://github.com/Esri/terraformer-arcgis-parser/blob/master/terraformer-arcgis-parser.js#L117-L172
    function convertRingsToGeoJSON(rings){
      var outerRings = [];
      var holes = [];
      var x; // iterator
      var outerRing; // current outer ring being evaluated
      var hole; // current hole being evaluated
  
      // for each ring
      for (var r = 0; r < rings.length; r++) {
        var ring = closeRing(rings[r].slice(0));
        if(ring.length < 4){
          continue;
        }
        // is this ring an outer ring? is it clockwise?
        if(ringIsClockwise(ring)){
          var polygon = [ ring ];
          outerRings.push(polygon); // push to outer rings
        } else {
          holes.push(ring); // counterclockwise push to holes
        }
      }
  
      var uncontainedHoles = [];
  
      // while there are holes left...
      while(holes.length){
        // pop a hole off out stack
        hole = holes.pop();
  
        // loop over all outer rings and see if they contain our hole.
        var contained = false;
        for (x = outerRings.length - 1; x >= 0; x--) {
          outerRing = outerRings[x][0];
          if(coordinatesContainCoordinates(outerRing, hole)){
            // the hole is contained push it into our polygon
            outerRings[x].push(hole);
            contained = true;
            break;
          }
        }
  
        // ring is not contained in any outer ring
        // sometimes this happens https://github.com/Esri/esri-leaflet/issues/320
        if(!contained){
          uncontainedHoles.push(hole);
        }
      }
  
      // if we couldn't match any holes using contains we can try intersects...
      while(uncontainedHoles.length){
        // pop a hole off out stack
        hole = uncontainedHoles.pop();
  
        // loop over all outer rings and see if any intersect our hole.
        var intersects = false;
        for (x = outerRings.length - 1; x >= 0; x--) {
          outerRing = outerRings[x][0];
          if(arrayIntersectsArray(outerRing, hole)){
            // the hole is contained push it into our polygon
            outerRings[x].push(hole);
            intersects = true;
            break;
          }
        }
  
        if(!intersects) {
          outerRings.push([hole.reverse()]);
        }
      }
  
      if(outerRings.length === 1){
        return {
          type: 'Polygon',
          coordinates: outerRings[0]
        };
      } else {
        return {
          type: 'MultiPolygon',
          coordinates: outerRings
        };
      }
    }
  
    // This function ensures that rings are oriented in the right directions
    // outer rings are clockwise, holes are counterclockwise
    // used for converting GeoJSON Polygons to ArcGIS Polygons
    function orientRings(poly){
      var output = [];
      var polygon = poly.slice(0);
      var outerRing = closeRing(polygon.shift().slice(0));
      if(outerRing.length >= 4){
        if(!ringIsClockwise(outerRing)){
          outerRing.reverse();
        }
  
        output.push(outerRing);
  
        for (var i = 0; i < polygon.length; i++) {
          var hole = closeRing(polygon[i].slice(0));
          if(hole.length >= 4){
            if(ringIsClockwise(hole)){
              hole.reverse();
            }
            output.push(hole);
          }
        }
      }
  
      return output;
    }
  
    // This function flattens holes in multipolygons to one array of polygons
    // used for converting GeoJSON Polygons to ArcGIS Polygons
    function flattenMultiPolygonRings(rings){
      var output = [];
      for (var i = 0; i < rings.length; i++) {
        var polygon = orientRings(rings[i]);
        for (var x = polygon.length - 1; x >= 0; x--) {
          var ring = polygon[x].slice(0);
          output.push(ring);
        }
      }
      return output;
    }
  
    // convert an extent (ArcGIS) to LatLngBounds (Leaflet)
    EsriLeaflet.Util.extentToBounds = function(extent){
      var sw = new L.LatLng(extent.ymin, extent.xmin);
      var ne = new L.LatLng(extent.ymax, extent.xmax);
      return new L.LatLngBounds(sw, ne);
    };
  
    // convert an LatLngBounds (Leaflet) to extent (ArcGIS)
    EsriLeaflet.Util.boundsToExtent = function(bounds) {
      bounds = L.latLngBounds(bounds);
      return {
        'xmin': bounds.getSouthWest().lng,
        'ymin': bounds.getSouthWest().lat,
        'xmax': bounds.getNorthEast().lng,
        'ymax': bounds.getNorthEast().lat,
        'spatialReference': {
          'wkid' : 4326
        }
      };
    };
  
    EsriLeaflet.Util.arcgisToGeojson = function (arcgis, idAttribute){
      var geojson = {};
  
      if(typeof arcgis.x === 'number' && typeof arcgis.y === 'number'){
        geojson.type = 'Point';
        geojson.coordinates = [arcgis.x, arcgis.y];
      }
  
      if(arcgis.points){
        geojson.type = 'MultiPoint';
        geojson.coordinates = arcgis.points.slice(0);
      }
  
      if(arcgis.paths) {
        if(arcgis.paths.length === 1){
          geojson.type = 'LineString';
          geojson.coordinates = arcgis.paths[0].slice(0);
        } else {
          geojson.type = 'MultiLineString';
          geojson.coordinates = arcgis.paths.slice(0);
        }
      }
  
      if(arcgis.rings) {
        geojson = convertRingsToGeoJSON(arcgis.rings.slice(0));
      }
  
      if(arcgis.geometry || arcgis.attributes) {
        geojson.type = 'Feature';
        geojson.geometry = (arcgis.geometry) ? EsriLeaflet.Util.arcgisToGeojson(arcgis.geometry) : null;
        geojson.properties = (arcgis.attributes) ? clone(arcgis.attributes) : null;
        if(arcgis.attributes) {
          geojson.id =  arcgis.attributes[idAttribute] || arcgis.attributes.OBJECTID || arcgis.attributes.FID;
        }
      }
  
      return geojson;
    };
  
    // GeoJSON -> ArcGIS
    EsriLeaflet.Util.geojsonToArcGIS = function(geojson, idAttribute){
      idAttribute = idAttribute || 'OBJECTID';
      var spatialReference = { wkid: 4326 };
      var result = {};
      var i;
  
      switch(geojson.type){
      case 'Point':
        result.x = geojson.coordinates[0];
        result.y = geojson.coordinates[1];
        result.spatialReference = spatialReference;
        break;
      case 'MultiPoint':
        result.points = geojson.coordinates.slice(0);
        result.spatialReference = spatialReference;
        break;
      case 'LineString':
        result.paths = [geojson.coordinates.slice(0)];
        result.spatialReference = spatialReference;
        break;
      case 'MultiLineString':
        result.paths = geojson.coordinates.slice(0);
        result.spatialReference = spatialReference;
        break;
      case 'Polygon':
        result.rings = orientRings(geojson.coordinates.slice(0));
        result.spatialReference = spatialReference;
        break;
      case 'MultiPolygon':
        result.rings = flattenMultiPolygonRings(geojson.coordinates.slice(0));
        result.spatialReference = spatialReference;
        break;
      case 'Feature':
        if(geojson.geometry) {
          result.geometry = EsriLeaflet.Util.geojsonToArcGIS(geojson.geometry, idAttribute);
        }
        result.attributes = (geojson.properties) ? clone(geojson.properties) : {};
        if(geojson.id){
          result.attributes[idAttribute] = geojson.id;
        }
        break;
      case 'FeatureCollection':
        result = [];
        for (i = 0; i < geojson.features.length; i++){
          result.push(EsriLeaflet.Util.geojsonToArcGIS(geojson.features[i], idAttribute));
        }
        break;
      case 'GeometryCollection':
        result = [];
        for (i = 0; i < geojson.geometries.length; i++){
          result.push(EsriLeaflet.Util.geojsonToArcGIS(geojson.geometries[i], idAttribute));
        }
        break;
      }
  
      return result;
    };
  
    EsriLeaflet.Util.responseToFeatureCollection = function(response, idAttribute){
      var objectIdField;
  
      if(idAttribute){
        objectIdField = idAttribute;
      } else if(response.objectIdFieldName){
        objectIdField = response.objectIdFieldName;
      } else if(response.fields) {
        for (var j = 0; j <= response.fields.length - 1; j++) {
          if(response.fields[j].type === 'esriFieldTypeOID') {
            objectIdField = response.fields[j].name;
            break;
          }
        }
      } else {
        objectIdField = 'OBJECTID';
      }
  
      var featureCollection = {
        type: 'FeatureCollection',
        features: []
      };
      var features = response.features || response.results;
      if(features.length){
        for (var i = features.length - 1; i >= 0; i--) {
          featureCollection.features.push(EsriLeaflet.Util.arcgisToGeojson(features[i], objectIdField));
        }
      }
  
      return featureCollection;
    };
  
      // trim whitespace and add a tailing slash is needed to a url
    EsriLeaflet.Util.cleanUrl = function(url){
      url = url.replace(/\s\s*/g, '');
  
      //add a trailing slash to the url if the user omitted it
      if(url[url.length-1] !== '/'){
        url += '/';
      }
  
      return url;
    };
  
    EsriLeaflet.Util.geojsonTypeToArcGIS = function (geoJsonType) {
      var arcgisGeometryType;
      switch (geoJsonType) {
      case 'Point':
        arcgisGeometryType = 'esriGeometryPoint';
        break;
      case 'MultiPoint':
        arcgisGeometryType = 'esriGeometryMultipoint';
        break;
      case 'LineString':
        arcgisGeometryType = 'esriGeometryPolyline';
        break;
      case 'MultiLineString':
        arcgisGeometryType = 'esriGeometryPolyline';
        break;
      case 'Polygon':
        arcgisGeometryType = 'esriGeometryPolygon';
        break;
      case 'MultiPolygon':
        arcgisGeometryType = 'esriGeometryPolygon';
        break;
      }
      return arcgisGeometryType;
    };
  
  })(EsriLeaflet);